Skip to content
advanced

Designing a Deep Research Agent: Search, Evidence, Verification, and Bounded Revision

You have seen the failure. A research agent returns a polished report, the citations look real, and the first URL you check resolves to a page that does…

Published 2026-09-11Updated 2026-09-1215 min read
Stunning aerial shot of lush green fields creating a serene patchwork landscape.
Stunning aerial shot of lush green fields creating a serene patchwork landscape. Photo by K on Pexels.

A research agent is only as trustworthy as its weakest claim-to-evidence link.

You have seen the failure. A research agent returns a polished report, the citations look real, and the first URL you check resolves to a page that does not say what the report claims. The link is not broken. The reasoning is. Somewhere between retrieval and prose, a sentence acquired a citation it never earned.

That failure defines the design problem. The weak mental model treats research as search more, then write. The stronger model treats research as a claim ledger under budget: every externally checkable claim maps to a recorded span or to an explicit unresolved status, and every loop iteration must either buy evidence or terminate. I have watched teams add more search tools, more sub-agents, and more orchestration to a pipeline whose real defect was that it stored prose instead of records. More retrieval raises the chance of finding a supporting document. It does not raise the chance the agent noticed the contradiction sitting in the document it already had.

Why Research Loops Fail Differently Than Other Agent Loops

You already know the act/observe/feedback/terminate contract, and you know how corrective retrieval triggers rewriting, fallback retrieval, or abstention when evidence looks insufficient. A research loop specializes that contract in one direction: it produces a long-lived artifact whose quality is judged after the run, by someone who was not there.

Three failure classes matter here, and they are not the generic loop pathologies.

Unsupported claims. A citation exists, but the cited span does not entail the claim. This is the most damaging failure, because it survives every surface check. The URL resolves. The page is real. The claim is still wrong.

Coverage gaps. A sub-question was never answered and never declared. The report reads as complete because nothing in it announces the hole. Silent omission is worse than an explicit "unresolved" label, because the reader cannot tell the difference between "no evidence exists" and "the agent stopped looking."

Unbounded drift. The agent keeps searching because searching feels like progress. Tool calls accumulate, new evidence records do not, and the run ends when the budget dies rather than when the question is answered.

The observable consequence that proves the mechanism works is simple: a second party can re-derive every claim in the report from the report's own evidence records, without re-running the search. Single-pass retrieval and one-shot synthesis cannot produce that property, because they never materialize the join between a claim and a span. A single-pass pipeline is genuinely sufficient for narrow factual lookups and internal corpora with known provenance. The moment the output needs to be defended, you need the ledger.

The State Contract: Question Graph, Evidence Ledger, Claim Table

Three persistent structures carry the loop. Most implementations quietly go wrong here by storing prose instead of records, and no amount of downstream orchestration repairs that.

Question graph. The decomposed sub-questions, their dependencies, and their status: open, answered, blocked, or abandoned-with-reason. Status must be explicit state, not something you infer by re-reading the transcript. If the agent cannot answer "which sub-questions are still open" with a query, it cannot decide what to search for next.

Evidence ledger. One record per retrieved span. Store spans, not summaries. A summary is a derived artifact; it must be regenerable from the span, never the other way around.

Claim table. Each atomic claim mapped to supporting evidence IDs, contradicting evidence IDs, and a verification status. This is the join between research and writing, and it is the structure that makes verification cheap.

Atomicity is the load-bearing decision. A paragraph-level citation cannot be verified, only believed. When a claim spans three sentences, you cannot tell which part the source supports, and revision becomes a rewrite instead of a repair. Claim granularity is what makes verification a narrow, diagnosable operation and revision a surgical one.

The unit of control is the atomic claim, not the sentence. A single sentence can carry two claims with different evidence requirements: "The vendor supports SSO on Business and Enterprise tiers, and it costs $12 per seat." That is two claims — one about tier availability, one about price — and they may resolve to different spans, different source classes, and different freshness windows. Split before verification, or you will verify half a sentence and cite the whole thing.

The ledger must survive process restarts and context truncation, because the loop will outlive any single context window. Serialize it. A minimal schema:

# question graph
{"id": "q3", "text": "Does the vendor support SSO on the entry tier?",
 "depends_on": ["q1"], "status": "open", "attempted_queries": []}

# evidence ledger: spans, not summaries; append-only
{"id": "e17", "url": "...", "retrieved_at": "2026-02-11T09:14Z",
 "span": "Single sign-on is available on Business and Enterprise plans.",
 "query": "vendor pricing page SSO tier", "source_class": "vendor_docs"}

# claim table: the join, versioned per verification pass
{"id": "c4", "text": "SSO requires the Business tier or above.",
 "supports": ["e17"], "contradicts": [], "status": "insufficient",
 "pass": 1, "history": []}

Those foreign keys are the whole architecture. Everything else is scheduling.

The Lifecycle Invariant

Bounded revision depends on preserving prior judgments and making progress measurable, so the transition rules need to be explicit:

  • Evidence records are append-only. You never edit a span. If a page changed, you record a new span with a new timestamp and mark the old one stale.
  • Claim text and verification passes are versioned. A status is terminal only within a pass. Re-verification opens a new pass and appends to history.
  • Legal transitions within a pass: insufficient → verified | contradicted | unverifiable. verified and contradicted are terminal for that pass.
  • Across passes: if a claim flips between verified and contradicted, the controller marks it contested and stops repairing it.
  • Progress signal: the controller compares the count of unresolved claims between passes. Flat or rising counts mean the loop is not learning.

Knowledge check

Check your understanding

Answer this question before you continue.

A sentence says, “The vendor supports SSO on Business and Enterprise tiers, and it costs $12 per seat.” What should the research agent do before verification?
Single Choice

Focus: Identify why atomic claim granularity is required for auditable verification and targeted revision.

Search as Hypothesis Testing, Not Collection

Query generation should be conditioned on the specific open sub-question and the specific claim under dispute, not on the original user prompt. A query derived from the prompt retrieves the same neighborhood repeatedly. A query derived from a disputed claim retrieves the thing that would settle it.

Diversify along axes that matter, not by paraphrasing. Vary the angle, the terminology, and the source class: primary documentation, filings, papers, vendor material. Paraphrasing the same query produces the same results wearing different words, which is how query echo chambers form.

Parallel search units cut wall-clock time and multiply rate-limit pressure. If you run them, partial-failure handling stops being optional: one unit returning nothing must not silently shrink the question graph. Deduplicate before evidence enters the ledger, because syndicated copies inflate apparent corroboration without adding independent support. Tag source class at retrieval time, since "two sources agree" means something very different when both are the same vendor's documentation.

When a sub-question returns nothing usable, mark it blocked and record the queries attempted. A blocked node with a query history is a finding. A dropped node is a lie.

Knowledge check

Check your understanding

Answer this question before you continue.

A claim about an entry-tier SSO requirement has one vendor-documentation result but remains insufficiently supported. Which next action best follows the article’s search strategy?
Scenario Interpretation

Focus: Select search behavior that targets a disputed claim while reducing redundant retrieval and false corroboration.

Recording Evidence So It Can Be Audited Later

The record must be sufficient to re-check the claim without re-running the search: exact span, source identity, retrieval time, and the query that surfaced it.

Retrieval time is load-bearing. Web evidence decays. A claim verified against a page that has since changed is not verified; it is remembered. When the report is read three weeks later, the timestamp is the difference between a citation and a guess.

Quote the smallest span that entails the claim. Over-quoting hides whether the source actually supports the sentence, because a long enough excerpt contains almost anything. When a source conflicts with an existing claim, record it as contradicting evidence rather than discarding it. Contradictions are the highest-value records in the ledger: they are the only records that tell you where the question is genuinely contested.

Non-web sources — uploaded documents, internal APIs, MCP-connected tools — belong in the same ledger, but only with source-class tags. Mixing them untagged corrupts every later corroboration count, because internal documentation and public documentation are not independent witnesses.

Verification: Deciding What the Evidence Actually Supports

Three checks get conflated constantly. Keep them separate.

Entailment asks whether this span supports this claim. Corroboration asks whether independent sources agree. Freshness asks whether the source is current enough for this claim type. A claim can pass one and fail the others, and the failure modes are different.

Entailment checking is a separate model call with a narrow contract: given a claim and a span, return supported, contradicted, or insufficient. Keep it narrow so its errors are diagnosable. A verifier that also summarizes, scores relevance, and suggests queries produces verdicts you cannot attribute.

Corroboration requires source independence, which requires the source-class tags from the ledger. Two pages quoting the same press release are one source wearing two URLs. If your ledger cannot tell you that, your corroboration count is theater.

Be honest about where the gate is unreliable. Numeric claims, claims requiring domain expertise, and claims about events after the model's training cutoff are all places where an entailment check can be confidently wrong. Say so in the output rather than implying the gate is complete.

Evaluating the Verifier

A verification gate that is never itself tested just relocates unsupported confidence from the writer to the verifier. Keep a small labeled set of claim/span pairs covering four cases: genuine support, contradiction, insufficiency, and source contamination (a span that mentions the topic but does not entail the claim). Measure false-support rate specifically — the cases where the verifier says supported and a human disagrees. That is the error that reaches the reader.

Route high-impact claims — numbers, regulatory statements, anything the report's conclusion depends on — to a second check or human review when the verifier's confidence is low or the claim sits in a known-weak category. Keep this scoped to the verifier contract. You are not building a general evaluator here; you are deciding whether this gate is trustworthy enough to gate on.

The cost shape matters for budgeting: verification scales with claim count, not search count. A run that searches fifty times and produces twelve claims pays for twelve verifications. A run that searches ten times and produces forty claims pays for forty.

Knowledge check

Check your understanding

Answer this question before you continue.

A current vendor span directly supports a pricing claim, but it is the only source and the price is time-sensitive. Which conclusion is justified?
Question 1 of 2Comparison Reasoning

Focus: Distinguish entailment, corroboration, and freshness as separate verification dimensions.

Why should a verifier’s evaluation set specifically measure false-support rate?
Question 2 of 2Misconception Check

Focus: Prioritize false-support measurement when evaluating whether a verification gate is safe to trust.

Bounded Revision: Repair the Claim, Not the Report

Flowchart of a claim entering verification, branching to verified, contradicted, or insufficient states, routing failed claims to targeted repairs that append evidence before re-verification, and terminating on resolution, no progress, or a contested status.
Bounded revision keeps verified work intact: each failed claim gets a typed repair, a new ledger record, and another verification pass until it resolves or is explicitly stopped.

Revision triggers should be typed, because each maps to a different repair:

TriggerRepair action
Contradicted claimRe-search for adjudicating evidence; update claim row
Insufficient evidenceSearch for an independent source in a different class
Uncovered sub-questionGenerate a targeted query from the sub-question text
Stale sourceRe-fetch the source; re-verify against the current span

Targeted repair beats regeneration. Re-search the specific claim, re-verify against the new span, update the claim row. Do not rewrite the whole report because one claim failed. Full-report regeneration is the expensive failure mode: it discards verified work, resets verification status, and makes improvement unmeasurable between iterations. You cannot tell whether iteration four is better than iteration three if iteration four threw away iteration three's evidence.

The stopping rule: terminate when no claim is in a repairable state, or when the marginal iteration stops reducing the count of unresolved claims. Track that count per iteration. It is the loop's progress signal, and it is the number that tells you whether another search is worth buying.

Guard against oscillation. If the same claim flips between verified and contradicted across passes, stop and mark it contested. A claim that will not settle is a finding about the evidence base, not a bug in the loop.

And know when to stop revising and start disclosing. A report that names its contested and unverifiable claims is more useful than one that keeps searching for a resolution that does not exist.

One Claim, Two Iterations, End to End

Here is the full loop for a single claim, with the ledger delta visible at each step.

Iteration 1
  q3 open: "Does the vendor support SSO on the entry tier?"
  search("vendor pricing SSO tier")
    -> e17 recorded (vendor_docs, span quoted, timestamp set)
  claim c4 created: "SSO requires the Business tier or above."
    c4.supports = [e17], c4.status = "insufficient"   # single source, no corroboration
  controller: unresolved_claims = 1, delta = 0 -> continue

Iteration 2
  trigger: insufficient evidence on c4
  repair: search for independent source in a different class
  search("SSO tier requirements independent review")
    -> e22 recorded (analysis, span contradicts e17's tier boundary)
  verify(c4, e22) -> "contradicted"
    c4.contradicts = [e22], c4.status = "contradicted", c4.pass = 2
  controller: unresolved_claims = 1, delta = 0 -> continue

Iteration 3
  trigger: contradicted claim on c4
  repair: re-search for adjudicating evidence
  search("vendor SSO plan comparison 2026")
    -> e31 recorded (vendor_docs, updated pricing page)
  verify(c4, e31) -> "supported"
    c4.supports = [e17, e31], c4.status = "verified", c4.pass = 3
  controller: unresolved_claims = 0, delta = -1 -> terminate, goal satisfied

The controller's decision is a comparison of two numbers: unresolved claims this pass versus last pass. When that delta stops going negative, the loop is no longer buying progress, and the budget is better spent on disclosure than on another search.

Report Rendering Reads the Ledger

The ledger is only authoritative if synthesis consumes it. The report generator should read claim rows, not free-form notes:

  • verified claims render with evidence IDs attached.
  • contested claims render with both supporting and contradicting evidence IDs and an explicit label.
  • unverifiable and budget-exhausted claims render with an explicit unresolved label.
  • Claims with no evidence row never render as assertions.

If your synthesis step can write a sentence that has no corresponding claim row, the ledger is an audit log sitting beside the report rather than the thing that produces it. That is the seam where the architecture either holds or quietly reverts to search-more-then-write.

Termination should be an enforced contract, not an emergent behavior. Four dimensions need caps, and capping only one leaves the others free to run away: tool calls, wall-clock time, token spend, and iteration count.

Iteration count alone is a weak cap, because one iteration can contain fifty searches. Pair it with a tool-call ceiling.

Termination conditions, in priority order:

  1. Goal satisfied — all sub-questions resolved or explicitly declared unresolved.
  2. Budget exhausted — any dimension hits its cap.
  3. No progress detected — unresolved-claim count flat across two iterations.
  4. Hard error — unrecoverable tool or model failure.

Partial results are a valid terminal state. Design the output contract so a budget-exhausted run still emits a report with its unresolved claims labeled. A truncated report that says what it does not know beats a complete-looking report that hides the same gap.

Budget search and verification separately, because their costs are asymmetric:

DimensionCapOn exhaustion
Tool calls (search)40Stop searching; verify what exists
Verification calls60Mark remaining claims unverified
Wall clock15 minEmit partial report with status
Iterations6Emit partial report with status

Observability: What to Log So Failures Are Diagnosable

A research run is too long and too expensive to debug by re-running. Log per iteration: open sub-question count, unresolved claim count, tool calls spent, and the trigger that caused the iteration. That four-number series is the loop's vital signs.

Log every verification decision with its inputs, so a wrong verdict can be attributed to the entailment check rather than to the evidence. Log rejected evidence and the reason for rejection — the discarded material is where query echo chambers become visible.

Three failure signatures to watch:

  • Flat unresolved-claim count across iterations. The agent is searching without learning. The queries are not targeting the open claims.
  • Rising tool calls with falling new evidence records. The agent is re-fetching. Deduplication is failing or the query generator is looping.
  • Claim status flapping. Oscillation. Apply the contested-claim rule.

End-to-end re-runs are the wrong debugging tool here. Inspect the ledger and the per-iteration series instead.

A Minimal Implementation Order

Start with the ledger and claim table, one search tool, and one entailment check. No planner, no sub-agents, no framework. Run it on one narrow question with a hard tool-call cap of a handful of calls. Inspect the ledger by hand. If the records are not auditable by eye, no amount of orchestration will fix them.

Add the question graph only when a single question demonstrably needs decomposition. Add parallel research units only after rate limits and partial-failure handling are in place. Add a second verification pass or a stronger verifier model only when the trace shows entailment errors, not preemptively.

And know when not to build this at all. Single-source internal lookups, latency-sensitive interactive features, and cases where a hosted research agent's output contract already fits do not need a custom ledger. Build custom when you need control over sources, provenance, or regulatory traceability — that is the decision boundary, and it is worth stating explicitly before you write a line of orchestration code.

The first thing to build is the ledger, not the planner. Instrument a single-question run with a hard tool-call cap, dump the claim table, and hand-audit three claims against their recorded spans. If any claim cannot be traced, the loop is not ready for more sources. It is ready for a stricter recording contract.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

At the end of an iteration, all claims are either verified, contradicted, contested, or explicitly unverifiable, and no claim is repairable. What should the controller do?
Question 1 of 2Output Prediction

Focus: Predict when a bounded research controller should terminate based on unresolved-claim progress and terminal statuses.

A run reaches its tool-call cap while two claims remain unresolved. What is the correct terminal behavior?
Question 2 of 2Scenario Interpretation

Focus: Apply the article’s termination contract when a research run exhausts a budget before resolving every claim.

Related sites

Build the foundations behind advanced AI systems

Use LearnLLMFast for practical LLM application foundations and LearnPyFast for the Python mechanisms that support implementation work.

LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast
Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast

Keep exploring

Related AI engineering tutorials

Continue with adjacent system layers, implementation patterns, and current AI engineering ideas.