BlogAUG 07, 2026·14 min read

Workflow, Not Agent: Deep Research an Analyst Can Trust.

Deep research demos look perfect while up to six claims in ten are wrong. How we built a biotech diligence pipeline where every claim is traceable, derived, or visibly marked uncertain.

Workflow, Not Agent: Deep Research an Analyst Can Trust
FIG. 01 — BLOG · Workflow, Not Agent: Deep Research an Analyst Can Trust

Deep research is easy to demo and hard to trust. We build AI reports for biotech investors, where the accuracy of the information is key. Here is what we learned building a research system around one rule: every material claim is traceable, derived, or visibly marked uncertain. Never a confident guess.

The demo always works

Every deep research demo looks great. You paste in a question, wait a few minutes, and get back a confident, well-structured report with citations at the bottom. Honestly, it is genuinely impressive. It is also the most dangerous kind of software: a system whose failures are invisible.

Deep research audit: 94%+ of citation links are valid, yet factual accuracy sits between 39% and 77%

A 2026 audit of frontier deep research agents found the links they produce are almost always valid (94%+) and topically relevant (80%+). Factual accuracy sits anywhere between 39% and 77%. Read that again. The citations check out, and up to six claims out of ten are still wrong.

Salesforce researchers ran their own audit on deep research modes of GPT-5, Perplexity, Copilot, and Gemini and got citation accuracy ranging from 40% to 80%. Notably, one flagship system produced reports in which 97.5% of statements were not supported by the sources they cited.

A fabricated fact and a found fact look identical on the page. Deep research is easy to demo, hard to trust.

Our problem looked different from the demos

We build an AI platform for biotech investment analysis. The central artifact is a structured profile of a drug asset: efficacy numbers, competitors, market size, regulatory status, valuation inputs, etc. An analyst reads it cell by cell and acts on the numbers.

There is no open-ended chat question here, no clarifying questions, no improvised web browsing, and no free-form essay at the end. The schema is fixed, and the source families in biotech are known: the FDA label, ClinicalTrials.gov, openFDA approval records, epidemiology tables, and SEC filings.

What we do not know in advance is which record wins. Good sources disagree all the time. The label reports one data cutoff, the paper reports another. Two trials measure the same endpoint in different patients. So our source set is not a list, it is an order plus a rule for conflicts:

  • Family order. FDA label first, then posted ClinicalTrials.gov results, then SEC filings, then papers, then press releases.
  • Match first, then date. Two numbers only compete if they measure the same endpoint in the same population. When they do, the later cutoff wins.
  • If it stays unresolved, keep it unresolved. Two good sources, two different numbers, both survive the rule: show both, mark the cell as conflicting, never quietly pick one.

Deep research agent vs our research workflow: an open-ended, non-deterministic loop against a fixed schema with ordered sources and deterministic steps

We know which families the truth should live in. And that exposes the real problem, the one no demo shows: often, it does not live anywhere. The trial has not posted results. The company is pre-revenue, so there is no product revenue in any SEC filing. No drug in the same class has been approved yet, so there is no competitor record to find.

What does a language model do when the answer is absent from every source you gave it? It answers anyway.

Why models fill the gaps

This is not a bug you can scold out of the model. As an OpenAI paper put it, models hallucinate because training and evaluation reward guessing over acknowledging uncertainty - a model that guesses scores better than one that abstains, so guessing is what it learns. A recent taxonomy of deep research failures found hallucinations creep in at every stage of the pipeline (retrieval, synthesis, citation), not just in the final write-up.

Trust must be structural, not behavioral. You don't ask the model to be honest, you build a system where an ungrounded claim is caught by a gate instead of by a reader. No matter how good it sounds.

Our pipeline is built on three commitments:

  1. Fix the shape - named, gated steps, not a free loop.
  2. Fix the world - fetch once, reason inside it.
  3. Fix the claims - grounded or dropped.

1. Fix the shape - named, gated steps, not a free loop

We are not alone in this conclusion. When Anthropic's Barry Zhang gave his "How We Build Effective Agents" talk at the AI Engineer Summit, two of his three takeaways were "don't build agents for everything" and "keep it simple". A recent AI Engineer workshop on building deep research systems frames it as a spectrum (workflow → single agent with tools → multi-agent) with one rule: stay as far left as possible, and move right only when the task forces you to. In practice, most useful agents are really workflows with retrieval bolted on. Which is not a failure. It is what makes them shippable.

But a workflow by itself is just a container. Five properties do the real work, and a workflow is simply the cheapest place to enforce all five:

  • Bounded retrieval. A known set of sources, fetched once.
  • Frozen evidence. The reasoning step cannot go back for more.
  • A fixed schema. No room to write around a gap.
  • Provenance. Every value knows its run and its source record.
  • Rejection gates. A claim that fails a check is dropped, not softened.

An agent can do all five too, it is just harder, because each one is a limit on a loop that was built to be free.

Our pipeline runs as a durable workflow (we use Inngest) of about twenty named steps, each memoized:

resolve anchors   → fetch-fda-label, lookup-icd10-codes, resolve-indication
fetch sources     → query-competitors, query-asset-trials, fetch-biomarkers
freeze            → assemble-sot-bundle
reason            → efficacy, positioning, valuation (no network access)
emit              → full structural output

Each step retries independently, and its result is memoized, so a crash mid-run replays cleanly instead of re-fetching the world. Every report carries its run ID. Emissions are keyed by a content-based dedup key, so a retry refreshes the same row instead of forking a duplicate. When an analyst asks "where did this number come from?", the answer is a specific step in a specific run. Not a vibe.

The LLM still does the hard cognitive work. It just doesn't get to drive.

2. Fix the world - fetch once, reason inside it

Authoritative sources are still incomplete, and absence is exactly where memory leaks in. If a trial has no posted results, the model will simply "remember" some plausible numbers. The defense is to make the boundary between the world and the reasoning physical.

Our runs are split into two layers.

Two-layer pipeline: layer 1 touches the network exactly once and freezes a content-hashed SoT bundle; layer 2 reasons inside it with no network access

Layer 1 touches the network exactly once. It fetches full records (the FDA label, ClinicalTrials.gov studies with their outcome measures, competitor approvals, epidemiology rows and SEC company facts) and freezes them into what we call the SoT bundle, for source of truth: a single, content-hashed object. The hash means identical content produces an identical bundle, so runs are reproducible, and the reasoning layer is unit-testable without ever mocking a network call.

Layer 2 is a pure function of the bundle. Reasoning steps get the bundle and nothing else. No network. No retrieval. If a fact is not in the bundle, it does not exist for this run.

We call this closed-world research. It is the same idea behind DeepMind's FACTS Grounding benchmark, which judges a model only on whether its response is grounded in the document it was given. We just enforce it in the architecture instead of measuring it after the fact.

One rule keeps a closed world honest: absence from the bundle means unknown, not false. And unknown is not one thing. A single "-" hides four different situations, and the analyst should treat each one differently. So every section carries a status instead:

StatusMeaningWhat the analyst does
founda source gave us a valueclick the citation
conflicting

two good sources, two different numbers, both survive the tie-break

both are shown, the disagreement itself is the signal

not_reported

the record exists, the value was never published (a trial with no posted results)

not a negative, check back after readout
none_found

we queried the right source and it is genuinely empty

treat as a real negative; the source we queried is named

not_searched

skipped on purpose (a Phase 1 asset has no FDA label)

nothing out of scope for this asset
errorthe fetch failed: timeout, quota, exceptionre-run; this cell proves nothing

The closed world doesn't stop the model from trying to fill gaps from memory. It makes the attempt detectable, because now every claim can be checked against a finite, known set of text.

3. Fix the claims - grounded or dropped

You can't just prompt your way to a trustworthy system, you have to enforce it structurally. Here are three mechanisms for doing that, in increasing order of strictness.

Claims must point to text

Research on factuality converged on a key insight: you can't verify an essay, but you can verify an atomic claim. FActScore decomposes long text into individual facts and checks each one (ChatGPT scored just 58% on biographies under that lens). We push the same idea upstream, into generation itself.

Every reasoning step has to emit each value together with the verbatim quote it came from:

{
  "value": "18.4 months",
  "sourceQuote": "median overall survival was 18.4 months (95% CI ...)"
}

A verifier keeps the claim only if the quote appears, character for character, in the bundle. For numbers, the number must also occur inside that quote. The model cannot launder a memorized fact past this check, because it has to point at real text that a run actually fetched.

Citations are an allowlist, not decoration

The 2026 audit above found exactly the failure we designed against: near-perfect links attached to wrong claims. A valid URL proves nothing by itself. So citations in our pipeline are not trusted output - they are checked against a per-run catalog of the sources the workflow actually fetched.

The rules are simple and strict:

  • A citation URL survives only on an exact match against the catalog. Fabricated or altered links (invented NCT or PubMed IDs) get dropped.
  • A bare text label either resolves to a real deep link (a trial ID becomes its actual ClinicalTrials.gov study page) or is deleted.

The field is reaching the same conclusion. A 2026 study of 53,090 citation URLs from ten commercial LLMs and deep-research agents found 3–13% hallucinated outright, and showed that simple URL-verification tooling cuts unresolvable citations to under 1%.

But an allowlist answers one question only: did we fetch this source in this run? Worth having - it kills invented NCT and PubMed IDs. It is also the easiest of the 5 checks, and treating it as all five is how a report gets trusted for the wrong reason:

CheckThe questionToday
source validitydid we fetch this URL this run?yes, exact match against the catalog
evidence presencedoes the quoted text contain the value?yes, on the efficacy fields
claim entailmentdoes the text actually support the claim?no, this is the real gap
source authorityis this the right kind of source for this field?partly, the family order above
temporal validityis this the current number or an old cutoff?partly, we record the cutoff, we do not gate on it

Entailment is the check that turns "the quote exists" into "the claim holds." It is what we build next: a separate step that judges (claim, quote) pairs and is scored against an analyst - never the model grading its own work.

Not every claim is a fact - label it

Not everything a diligence report says can be a fact. Pretending otherwise is just another way to lie. Three labels turned out to be too blunt: "inference" was covering four different things. So we use six:

BasisMeans
extractedcopied from a source, with the quote
derivedcalculated from extracted numbers by a fixed rule
inferred

model judgement over the bundle, with no calculation behind it

modeled

comes out of assumptions - valuation, market size, uptake

hypothesisplausible, but this bundle does not support it
unknownnot in the bundle (see the statuses above)

Valuation is the one that matters most. A discounted cash flow is not a fact and not an inference. It is assumptions with a number on top. Unless it is labeled modeled with those assumptions visible, it is the most dangerous cell in the report: it looks like data and behaves like an opinion. Today, the code still ships three labels (fact / inference / hypothesis). The six-label version is our next schema change, and the reason for it is that it is modeled.

What the analyst sees

Back to where this post started: the demo always works. It comes back full and confident, with an answer in every box. Our report is the opposite, it looks plain and has gaps, and both are on purpose. The gaps are the point.

Every empty cell says which of four kinds of emptiness it is. The value is not published yet, the source we checked is genuinely empty, the field was skipped on purpose, or the fetch failed and needs a re-run. Each kind tells the analyst to do something different.

Rows that are not plain facts say so. A badge marks them as inference or hypothesis, so a judgment never dresses up as data. And every citation is a real link from this run - click it, and the quote behind the number is on that page.

Empty is better than wrong. An empty cell is easy to fix. The analyst sees the gap and checks the source themselves. A confident wrong cell is worse. The analyst acts on it. And when the error finally comes out, they stop trusting every other number in the report. Trust is built cell by cell - and lost all at once.

Deep research agents win on first impression. Diligence tools live and improve over time.

Further Reading

If you want to go deeper on any of the threads in this post:

Talks

Articles

FILED UNDER · BLOG · TECHNICAL