The whole design, in writing
Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.
The big idea
Search answers queries. Research answers questions.
"What's the capital of France?" is a search. "Which vector database should we bet on, and why?" is research: it decomposes into dozens of sub-questions, needs sources that disagree to be reconciled, and ends in a cited report — not a snippet. Every major lab shipped a "deep research" agent for exactly this gap. What has to exist between a search box and a trustworthy report?
An orchestrated crew: a lead agent that plans and delegates, parallel sub-researchers with clean contexts, a shared evidence store of claims-with-sources, a budget that ends the loop, and a synthesis pass whose citations are verified, not decorative.
What the new pieces do
- Userclient
- Asks something no single search answers: "compare the top 5 vector DBs for our scale and recommend one, with evidence."
Step 1 · The skeleton
A lead that plans, not a loop that wanders
Hand the whole question to one agent with a search tool and it wanders: chases the first interesting thread, forgets half the question, and declares victory when the context fills. Research needs structure before it needs searches. What's the first component?
The user asks a broad, hard question. What happens first?
Unplanned search produces a random walk through the internet. The agent burns its budget on whatever the first results made interesting, not what the question needs.
"Compare vector DBs" becomes: performance benchmarks, pricing, operational maturity, ecosystem, migration cost — a checklist the rest of the system executes and audits against.
Decomposition is the research skill. Making the user do it is making the user the researcher.
The Research Lead owns the question: it decomposes it into sub-questions, decides what "covered" looks like, and delegates. Planning first turns an open-ended wander into an auditable checklist — and gives every later component something to measure against.
Why this piece earns its place
The plan has to survive being read by something that did not write it, so write it as structured records — one record per sub-question, carrying a stable id — rather than prose the lead re-derives each round. Ids are what let a later round point at the same lane an earlier round filed its claims under, and what gives a re-run something to diff against; a decomposition that quietly rewords itself between rounds makes every comparison across rounds meaningless. The subtler job is deciding what travels into each record. The original question carries constraints the sub-questions do not — at our scale, we are on Postgres today — and a worker never sees anything but its own brief, so a constraint left behind in the phrasing reaches no lane at all and comes back as five lanes of well-researched, mildly wrong evidence. Copy it in at decomposition time. And the failure you cannot see is a plan that misses a dimension outright: every lane covered, the budget under, every citation verified, and the report confidently incomplete, with nothing anywhere in the trace to flag it. Which is why putting the plan in front of the user before dispatch is worth the extra turn.
What the new pieces do
- Research Leadbackend
- The orchestrating agent: decomposes the question, spawns sub-researchers, watches for gaps, and decides when the evidence is sufficient.
Step 2 · Divide the question
Parallel sub-researchers, isolated contexts
Five sub-questions, each needing dozens of searches and long documents read. Serially, that's slow. In one shared context, it's worse than slow: benchmarks drown pricing, pricing drowns ecosystem, and the window rots before the report starts. How is the work split?
How should the five sub-questions be worked?
Slow, and each sub-question inherits the context debris of the previous ones. By sub-question four, the window is a landfill.
The sub-questions contaminate each other — retrieved pricing pages distract benchmark reasoning, and the window overflows long before coverage.
Isolation is the point: each worker reads deeply within its lane and returns distilled findings. Fan-out buys speed; clean windows buy quality.
The lead spawns Sub-Researchers — one per sub-question, each with a fresh, task-scoped context. They run in parallel, dig deep in their lane, and report back findings. This is the isolate operation of context engineering, applied as an org chart.
Why this piece earns its place
Fan-out looks free and is not. Concurrency is bounded by the search API's rate limit and by spend, not by the number of sub-questions, so the lead is really a scheduler — a dispatch queue with a concurrency cap — rather than a loop that launches everything at once and finds the ceiling as 429s halfway through a round. Isolation also changes what a failure looks like on the way out. A worker that times out, loops, or returns nothing does not error the request: it produces a thin lane, which is exactly what an honestly researched dead end produces too. Nothing in the evidence tells those apart, and they want opposite responses — retry the crashed lane, but write the material here is genuinely scarce about the real one. So a worker's terminal state is recorded as a fact about the run, stored beside its claims rather than inferred from how few of them there are. Each brief also has to name its neighbours, so a worker that turns up somebody else's sub-question hands the thread over instead of following it: two workers half-answering one lane pay twice and leave both halves looking staffed.
- 1 laneper sub-researcher
- clean windowper lane
- findings upnever transcripts
What the new pieces do
- Sub-Researchersservice
- Worker agents, each owning one sub-question with its own clean context. They search, read, and return findings — not transcripts.
Step 3 · The grind
Search, read, note — repeat
Inside one lane, what does a sub-researcher actually do? First results are rarely the answer: the good benchmark is on page two of a blog linked from a forum post. One-shot retrieval produces one-shot understanding.
How does a sub-researcher work its sub-question?
That’s a search engine with extra steps. Snippets are teasers; the substance lives inside documents the first query didn’t even surface.
Each round’s findings sharpen the next round’s queries — the agent follows leads like a human researcher, going deeper as its understanding compounds.
Breadth without reading is noise, and 100 pages is a context-rot machine. Reading and distilling per-source is what turns pages into evidence.
Each worker runs the search-read-note loop: query the Search/Fetch tools, open the promising results, read them properly, extract claims, and let each finding refine the next query. Depth comes from iteration — queries get smarter as understanding compounds.
Why this piece earns its place
The tool layer lies more often than it errors. A fetch returns 200 with a body that is a consent wall, a JavaScript shell, a paywall preview or a not-found page the site renders as content, and a worker will extract clean, well-formed claims out of any of them — attributed to a real URL on a real domain, and sourced from a cookie banner. Nothing about that request looks wrong afterwards. So the fetch layer owes the worker a check on what came back before extraction runs, and a lane that cannot get past it should say the document was unreachable rather than quietly settle for the snippet it already had. The layer owes one more thing: a cache keyed by URL that holds the retrieved text, not a pointer to it. Lane boundaries partition the questions, not the web — one good review article is legitimately relevant to two lanes — and step 7 re-reads cited sources after the draft exists. Holding the text means each retrieval is paid for once, and it is what makes the trace replayable at all; a stored URL replays whatever that page says today.
What the new pieces do
- Search / Fetchservice
- The tool layer: search engines, site fetchers, internal doc stores, structured APIs. Every result carries its source URL.
Step 4 · Write it down
The evidence store: claims, with receipts
A worker just read thirty pages. What flows upward — and what survives the loop? Raw transcripts overflow every window, and "I remember reading somewhere that…" is how hallucinated citations are born.
What does a sub-researcher persist from its reading?
Terabytes of context nobody re-reads. Storage isn’t the problem — attention is. Raw text buries the claims the report needs.
"Qdrant p99 = 12ms at 10M vectors — [benchmark URL, 2026-03]" is usable, checkable and compact. The report gets built FROM this store, so citations are structural, not decorative.
Summaries lose the pairing between specific claims and specific sources — exactly the linkage the citation checker needs later.
Workers write to a shared Evidence Store: atomic claims, each with source URL, quote, confidence and timestamp. Contradictions become visible (two claims, same topic, different numbers), and the eventual report can only say what the store can back.
Why this piece earns its place
The evidence store is the only place workers who share no context meet, so its defining property is concurrent writers, not size. Make it append-only: no worker edits or deletes another's claim, and a correction is a new row that supersedes the old one. That removes write coordination between lanes entirely — nothing to lock, nothing to merge — and it keeps the trace able to show what the report was built from at the moment it was written, which an edited row destroys. Then validate the schema at write time. A claim written without its quote costs nothing when it lands and surfaces three steps later as something nobody can check, and the repair is not an edit: it is a worker sent back to a source it finished reading rounds ago. Confidence deserves a definition rather than a number. Make it a property of the claim-source relation — the source states this directly, implies it, or the worker inferred it — because the honesty pass only ever asks whether a source supports a sentence, and an inferred claim cannot pass that question however sure the worker felt writing it.
What the new pieces do
- Evidence Storestore
- The shared notebook: atomic claims, each paired with its source, confidence and timestamp. The report is built from this, not from memory.
Step 5 · Know when to stop
Gap-driven iteration, under a budget
Research never feels finished — there's always one more source. Meanwhile every round costs real tokens and real minutes. Who decides between "keep digging" and "we know enough"?
When does the research loop end?
The internet never runs out of pages. Novelty-exhaustion is a stopping rule for crawlers, not researchers.
Question difficulty varies wildly — a fixed count starves hard questions and gold-plates easy ones. The budget should be a ceiling, not the plan.
Gap-driven iteration: "pricing lane is thin, benchmarks conflict — one more targeted round." The depth budget (rounds, tokens, dollars) caps it regardless.
Between rounds, the Lead reads the evidence store against its plan: covered, thin, or conflicting? Real gaps trigger targeted follow-up rounds; the Depth Budget — rounds, tokens, wall-clock, dollars — hard-caps the whole affair, with everything logged to the Trace.
Why this piece earns its place
A budget an agent is asked to respect in its prompt is not a budget. Tell a model it has spent enough and it will reason its way to one more round for a good cause, so the accounting belongs in the harness that dispatches tool calls and meters tokens, with the lead reading the remaining balance as an input to its next decision rather than being trusted to subtract. The ceiling itself belongs in the request, not the config: the same architecture has to serve a question worth a quick sweep and a question worth a long dig, and depth is the one knob a user should actually feel. Which forces the part teams skip — every point in the loop has to be shippable. Running out of budget cannot be an error, because the ceiling exists precisely to be hit. The terminal state is a report written from whatever the store holds, with the thin lanes named inside it: we found little on migration cost is a useful sentence and an honest one. A system that can only produce output after full coverage has a budget in name only.
What the new pieces do
- Depth Budgetservice
- Caps research rounds, token spend and wall-clock time. Research has diminishing returns; the budget decides when good enough is enough.
- Trace / Evalsbus
- Full record of queries, sources, notes and drafts — for debugging, cost tracking, and evaluating research quality over time.
Back of the envelope
- coverage audit
- each round: covered / thin / conflicting per lane
- targeted follow-ups
- new rounds only for real gaps
- hard budget
- rounds + tokens + $ + wall-clock ceilings
- full trace
- every query and claim, replayable
Step 6 · Write the report
Synthesis from evidence, not from memory
Time to write. The tempting shortcut: hand the model the question and let it write from everything it "learned along the way." That's how you get a beautiful report with claims nobody actually found.
What does the synthesizer write FROM?
Context memory blurs what was read with what was inferred. Claims mutate in transit; citations attach to the wrong facts.
The report is a rendering of the store: structure from the coverage plan, claims from the evidence, disagreements surfaced explicitly with both sources shown.
Re-reading raw sources at synthesis time re-introduces the context overload the whole pipeline was built to avoid — and drops the other forty sources on the floor.
The Synthesizer drafts the report from the Evidence Store: the coverage plan gives the structure, stored claims give the substance, and every sentence carries its citation. Where sources conflict, the report says so — presenting both, with dates and authority, instead of silently averaging.
Why this piece earns its place
A report long enough to be worth reading does not fit in one generation, and the store is bigger than the draft. So synthesis runs section by section, each one handed the claims for its lane and nothing from the others — the context discipline the workers used, applied at write time. That buys quality and creates one new problem: the seams. Sections written independently repeat each other, hedge the same finding two different ways, and build toward a conclusion nobody wrote. So a final pass reads the whole draft, and that pass may cut and rewrite, never add. A sentence it introduces carries no claim id, so nothing downstream can resolve it and it gets cut a step later, after you have paid to write it. Emitting citations as resolvable claim ids rather than prose attributions pays a second time when a follow-up round lands: the ids say which sections were built on the lane that just changed, so those get re-rendered and the rest of the draft is left alone. Without them, one more round on one lane means writing the whole report again.
What the new pieces do
- Synthesizerservice
- Writes the report from the evidence store — every claim traced to a stored source, contradictions surfaced rather than smoothed over.
Step 7 · The honesty pass
Verify every citation actually says that
The draft cites forty sources. Models are excellent at producing citations that look right — real URLs, plausible attributions — that don't quite say what the sentence claims. Ship that, and one wrong citation poisons trust in the other thirty-nine.
The draft is written and cited. Ship it?
Claims drift during writing: numbers get rounded, hedges get dropped, "one benchmark shows" becomes "benchmarks show." The last mile is where honesty leaks.
A separate check re-reads the stored quote (or the live source) and asks one question per citation: does this actually support this sentence? Failures get cut, weakened, or re-researched.
The author grading its own claims, holding the same context that produced the drift — a rubber stamp. Verification wants fresh eyes and a narrow question.
The Citation Check walks every claim-citation pair with a narrow prompt: does the source support the sentence? Unsupported claims are cut or sent back for another research round. Only then does the report — with its now-load-bearing citations — go to the user.
Why this piece earns its place
Two things nobody builds until they have been burned by them. The first is that this pass is the only diagnosis the system ever produces, and only if it records which of two comparisons failed. A citation has three parts — the sentence, the stored claim with its quote, and the source — so a failure is either the sentence having moved away from the claim it cites or the claim having never been supported by the source it names. One verdict per citation collapses those together, and they want opposite repairs: rewrite the sentence, or send that lane back to the source. Split the failures that way, by lane and by domain, and the number stops being a score and starts naming the step to go and fix. Second, a verifier that passes everything is indistinguishable from a verifier that is broken, and the symptom of a prompt regression here is silence: every citation approved, every report shipped, trust quietly gone. So plant known-unsupported pairs and confirm they still fail. The last note is budgetary. This is one call per citation, parallel and individually cheap but real in aggregate, and it runs after the research spend — reserve it up front, or the honesty pass becomes the thing that gets dropped when a question runs long.
- every citationindependently re-checked
- fail → cutor re-research, never ship
- trustis the product
What the new pieces do
- Citation Checkservice
- The honesty pass: re-reads each cited source and confirms it supports the sentence citing it. Unsupported claims get cut or re-researched.
The payoff
You built a deep research agent
From a search box to a research organization: a lead that decomposes and audits, parallel sub-researchers with clean contexts, an iterative search-read-note grind, an evidence store of claims-with-receipts, gap-driven rounds under a hard budget, synthesis rendered from evidence, and citations that survive an independent check.
Now poison a source and watch the hygiene stack respond — source tracking, contradiction surfacing, authority weighting, and the citation check that refuses to let confident nonsense reach the report.
Everything you assembled, in order
- Decompose first — the plan is the rubric everything audits against
- Sub-researchers — parallel lanes, isolated contexts, findings up
- Search-read-note — iterate; reading documents is the moat
- Evidence store — atomic claims paired with sources, always
- Gap-driven rounds — audit coverage, follow up only real gaps
- Depth budget — rounds/tokens/$ ceilings end the endless
- Synthesis = rendering — the report is a view over the evidence
- Citation check — every claim re-verified against its source