Architecture overview
The real pipeline, stage by stage, and who owns each stage — the agent under a rubric, or the deterministic CLI.
A scan is twelve steps. Some are judgements made by a model under a written rubric; the rest are deterministic stages the CLI performs. Knowing which is which is most of understanding the system.
The main repo renders the same chain in one line:
brief ─▶ [plan queries] ─▶ retrieve ─▶ [screen 0–3] ─▶ expand ─▶ [screen] ─▶ coverage ─▶ [gap round, if coverage is thin] ─▶ shortlist ─▶ [rerank] ─▶ verify ─▶ emitBracketed steps are the agent’s; the rest are CLI stages.
The stages
| Stage | Owner | What it does |
|---|---|---|
init | CLI | Creates the run directory and brief.md, and prints the RunInfo the planning agent must respect. --profile belongs here and is recorded in the manifest. |
plan | Agent | Writes queries.json — 6–8 queries and 3–6 sub-criteria, derived from the brief’s purpose. |
retrieve | CLI | Queries every routed source, then dedups, filters, caps and writes the screening batches. |
screen | Agent | Scores every candidate 0–3 against the sub-criteria. |
expand | CLI | Walks the citation graph out from every paper screening kept. |
screen | Agent | Scores the expansion items the same way. |
coverage | CLI | Counts how well each sub-criterion is covered, so the gap round has something to aim at. |
| gap round | Both | At most once. The agent writes queries against the thinnest criteria; retrieve --round 2 and expand --round 2 add what they find. |
shortlist | CLI | Checks that every candidate was screened, then orders and cuts for the reranker. |
rerank | Agent | Scores the shortlist per criterion and overall, and writes the argument for each paper. |
verify | CLI | Checks every ranked paper against the live record and records what did not match. |
emit | CLI | Applies the selection rules and renders the deliverable. |
There is no screen or rerank command in the CLI. Those stages belong to the
agent, and the CLI has no faculty for performing them.
Every stage is idempotent and re-runnable: change queries.json or pass a flag
and re-run from the stage it affects.
Sources and routing
| Source | Used for | Status |
|---|---|---|
| OpenAlex | Primary search, metadata, retraction flag, graph fallback | Implemented. Requires a key. |
| Semantic Scholar | Search, and citation-graph expansion over references, citations and recommendations | Implemented. Key optional, throttled without one. |
| arXiv | Extra source for cs, and for general when a query is a method query | Implemented. |
| PubMed | Routed for biomed | Routed but not built yet. A biomed scan runs on OpenAlex + Semantic Scholar and records unavailable: true in manifest.json. |
| Crossref | DOI verification only; never a retrieval source | Implemented. Verification degrades to OpenAlex if it is unreachable. |
--domain picks the routing: behavioral and general use OpenAlex and Semantic
Scholar; cs adds arXiv; biomed adds PubMed. --sources overrides the map
outright.
That PubMed row is worth reading twice. A routed source that is not built is recorded as unavailable in the manifest rather than dropped quietly — the same rule that governs every other kind of loss in the pipeline.
The gap round
The gap round runs at most once, and on standard only when coverage reports
that criteria are unevenly covered or that a query came back nearly empty.
Otherwise it is skipped and the report says so. On quick it never runs; on
deep it always does.
The mechanism: coverage counts, per sub-criterion, how many papers screening
kept. The agent writes one or two queries against the criteria that came back
thinnest. retrieve --round 2 and expand --round 2 add what they find.
A second round adds; it never subtracts. Round one’s papers and their scores are never discarded — the pool only grows. Coverage takes one snapshot per round, so what the gap round recovered is visible as a delta.
What bounds a run
Two dials, both recorded in the manifest.
Purpose — build, research or orient — decides which sub-criteria the plan
derives, what screening counts as relevant, and what why_it_matters has to
argue.
Profile — quick, standard or deep — sets per-query depth, the pool cap,
the out-of-window total and whether the gap round runs.
| Profile | Per query | Pool cap | Out-of-window total | Gap round |
|---|---|---|---|---|
quick | 20 | 250 | 12 | never |
standard (default) | 40 | 450 | 20 | when coverage is uneven |
deep | 40 | 450 × sources ÷ 2 | 30 | always |
An explicit flag still overrides the profile: --per-query 60 means 60 whatever
the profile says. The out-of-window total is a budget for the whole run, not a
per-stage allowance — caps are totals, not allowances per stage.
Where the time goes
Wall clock tracks the candidate pool, because screening is the long pole — it is the agent reading the pool.
| Pool | Screening batches | Wall clock |
|---|---|---|
~675 candidates — cs, 3 sources at the default cap | ~27 | 37 min |
| ~250 candidates — 2 sources at the default cap | ~10 | 22 min |
~120 candidates — --max-candidates 120 | 5 | 19 min |
OpenAlex bills roughly $0.008 either way; pool size costs almost nothing in API money. Agent tokens are the real cost, and they do not fall as fast as the pool does: the 120-candidate run still cost about $5.70 in frontier-model tokens and ran only three minutes quicker than the 250-candidate one, because reranking and fixed stage overhead dominate once screening is small.
--max-candidates is the right dial for a runaway cs pool, not a way to make a
scan cheap.
Read on
- The CLI engine — how the deterministic stages behave.
- The agent layer — the rubrics and the screening scale.
- Verification — the last stage before emit.
Last updated Aug 21, 2026