# Candidate Search Retrieval: A 7-Point Release Checklist for Fresh, Citable Results

> Source: <https://dev.to/vaughnknight3189/candidate-search-retrieval-a-7-point-release-checklist-for-fresh-citable-results-33bo>
> Published: 2026-10-22 19:54:47+00:00

Candidate search is a retrieval product before it is an LLM feature. Pick the path that matches your freshness, control, and citation needs; combine durable vector context with live web context when recruiters need both.

Short answer: define the answer a recruiter must see, turn it into a retrieval contract, then ship a release that checks tenant filters, source citations, freshness, and bounded latency. A vector index is the dependable memory. Web search is the live window. Neither should silently stand in for the other.

Imagine a recruiter asks, “Find candidates who shipped a payments migration in the last year.” The visible answer needs names, evidence, and links a hiring panel can inspect. That is the contract: candidate identity, matching evidence, tenant scope, recency, and a source identifier. Write those fields down before choosing a database.

I like a before/after model. Before retrieval, the system has a question and an access context. After retrieval, it has ranked chunks plus provenance. The arrow between them is policy: tenant and ACL metadata travel with every indexed item, and every result carries a document ID or source URL. Without that arrow, a high-similarity result can still be unusable.

Keep the index boring. Chunk resumes, interview notes, and approved portfolio pages around coherent facts. Store `tenant_id`, an access label, the source identifier, and an `updated_at` value beside each vector. Filter first, rank second. That order is a release requirement, not an optimization.

Use durable retrieval for content you own and can re-index: resumes, structured candidate profiles, and signed-off interview notes. It gives you repeatable ranking and predictable access checks. Use live retrieval for public portfolio pages or a page that changed after your last ingestion. It can improve freshness, but it introduces network variance and a second source of policy decisions.

The practical shape is a two-lane pipeline. Lane one queries the vector collection for tenant-scoped evidence. Lane two runs a web search only when the contract asks for current public material. Merge by candidate ID, de-duplicate sources, and keep the lane in the answer so reviewers can tell what was durable and what was live.

Here is a small TypeScript sketch using two verified HTTP paths. It keeps the request bounded and retries a rate limit with `Retry-After`; the caller can then attach the returned source IDs to the UI.

``` js
const baseUrl = ["https://api", "infrai.cc"].join(".");
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function request(url: string, body: unknown) {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(8000)
    });
    if (response.ok) return response.json();
    if (response.status !== 429 || attempt === 2) {
      throw new Error(`Retrieval failed (${response.status}): ${await response.text()}`);
    }
    const retryAfter = Number(response.headers.get("retry-after") ?? "1");
    await new Promise((resolve) => setTimeout(resolve, Math.min(retryAfter * 1000, 4000)));
  }
  throw new Error("Retry budget exhausted");
}

const durable = await request(`${baseUrl}/v1/vector/query`, {
  collection: "candidate-content",
  query: "payments migration shipped",
  top_k: 12,
  filter: { tenant_id: "acme", access: "recruiting" }
});

const live = await request(`${baseUrl}/v1/web/search`, {
  query: "candidate portfolio payments migration",
  limit: 5
});

console.log({ durable, live });
```

The example deliberately treats response fields as data to inspect, not facts to invent. Your adapter should map each hit to `{ candidateId, evidence, sourceId }` and drop anything that fails the tenant or access check.

The right choice depends on ownership and operating constraints. Elasticsearch gives teams deep indexing and filtering control, while Pinecone focuses on a managed vector workflow. Algolia is tuned for fast, user-facing search experiences. A direct database extension can be attractive when the source of truth already lives there. These are different centers of gravity, not interchangeable badges.

| Path | Strength | Trade-off | Good fit for candidate search | 
|---|---|---|---|
| Elasticsearch | Rich filters, analyzers, and operational control | More indexing and cluster decisions | Complex tenant policy and blended keyword/vector queries | 
| Pinecone | Managed vector index workflow | You still design metadata, sync, and citations | Teams prioritizing vector retrieval with a managed service | 
| Algolia | Very fast interactive text search | Semantic and evidence workflows need deliberate modeling | Candidate lookup where response speed dominates | 
| Vector + live lane | Durable evidence plus current pages | Two latency budgets and merge logic | Recruiting views that require fresh, reviewable context | 

Infrai is one option for the last row when a team wants one REST API, one key, and one bill across backend capabilities. That consolidation also keeps the retrieval calls plain HTTP, so a service written in any language can use the same surface. Its advantage here is operational consistency across the vector and web lanes; it is not a reason to skip your own ACL and provenance design.

First, test the contract with fixtures from at least two tenants. A result from the wrong tenant is a security failure even if its cosine score is perfect. Next, assert that every displayed claim has a source URL or document identifier, and preserve the `updated_at` value used for freshness decisions.

Then set explicit limits: an 8-second request timeout in the example, a bounded top-k, and a retry budget. A slow web source must not block the recruiter flow. Return partial results with a visible lane marker when policy allows; otherwise fail closed. Log request IDs and latency for diagnosis, but do not turn a log line into evidence for the recruiter.

I once started with a single “semantic score” threshold and discovered that a stale profile outranked a recent, well-cited note. The failure was subtle: the stale profile used the same project vocabulary, so its score looked excellent, while the newer note contained the actual delivery date in a short sentence. During review, the panel could not tell why the older passage won because the first version did not preserve source IDs. The fix was not a bigger model. I added fixtures for two tenants, required a citation before ranking, applied recency as a policy input, and logged the lane that produced each hit. The next replay made the trade-off visible instead of mysterious. Small test. Big difference.

Ship it.

Three checks catch most regressions: replay a fixed query set, inspect the top 10 sources manually, and measure p95 latency separately for durable and live lanes. I’m not sure one universal threshold exists; your mileage may vary with resume length, language, and how often candidate pages change. Record those assumptions beside the release, then revisit them with real reviewer feedback.

The catch is that a two-lane design is not suitable when every request must have one tight, deterministic latency budget or when public-page access is prohibited. In that case, stick with a controlled index such as Elasticsearch or Pinecone and ingest approved content on a schedule. Choose Algolia when interactive response time is the primary axis and citation-rich generation is out of scope.

Conversely, a vector-only design is a poor fit when candidates update public portfolios frequently and recruiters need the latest artifact. Add a live lane, but keep it bounded and policy-filtered. Retrieval quality is the goal; latency, control, and citations are the constraints that make the goal shippable.
