Research Scan
Type to search documentation.

Artifacts and schemas

Files are the interface between reasoning and execution. What that means in practice, and why the schema is the source of truth.

The interface between the agent and the engine is a directory. Not an API, not a callback, not a message format — a set of JSON files that both halves read and write, and that you can open in an editor.

This is the design decision that makes everything else portable.

Why files

They are inspectable. A scan that went wrong left evidence. You can read queries.json and see what was actually searched, screen.json and see what scores were actually given, manifest.json and see what was dropped and why.

They are resumable. Every stage is idempotent and re-runnable. Change queries.json and re-run from retrieve. Change a screening score and re-run from expand. Nothing needs to be redone from the top because nothing was held in memory.

They are harness-agnostic. Anything that can read and write JSON can drive the pipeline: Claude Code, Cursor, Codex, an MCP client, or fifty lines of Python. There is no SDK to adopt.

They are diffable. Two runs of the same brief can be compared file by file, which is how the project’s own measurements were made.

The run directory

research/scans/2026-08-19-defaults-savings/
├── brief.md the input
├── queries.json [agent] the query plan
├── candidates.json [CLI] the retrieved pool
├── screen-batches/
│ ├── 01.json retrieved candidates, batch 1
│ ├── x01.json expansion items
│ ├── r01.json gap round, retrieved
│ └── xr01.json gap round, expansion
├── screen.json [agent] scores, 0–3
├── expanded.json [CLI] citation-graph additions
├── coverage.json [CLI] per sub-criterion counts
├── shortlist.json [CLI] ordered and cut for the reranker
├── ranked.json [agent] the rerank
├── evidence.json [CLI] the deliverable
├── evidence.md [CLI] the same, for a human
├── evidence.bib [CLI] BibTeX
├── manifest.json [CLI] the audit trail
└── *.log.jsonl per-stage structured logs

The batch-file prefixes are a small thing that matters when you are debugging: 01 is a retrieved batch, x01 an expansion batch, and r/xr are the same two families in the gap round. You can tell where a paper entered from its filename.

The schema is the source of truth

One module — schema.py in the package — generates three things:

  1. the JSON Schema every file is validated against,
  2. the contract documentation the agent reads,
  3. the error messages you get when validation fails.

They cannot drift apart, because they have one origin. A test compares the generated contract docs against the committed copy.

Print any contract at any time:

Terminal window
research-scan schema --name EvidencePacket # one model, as JSON Schema
research-scan schema --md # every model, as Markdown

Validation is strict on purpose

Models write three of these files. Strictness is what keeps a plausible-looking mistake from travelling downstream.

Unknown keys are rejected everywhere. A typo surfaces as an error instead of being silently ignored — a misspelled field is not a field that defaulted, it is a field that never arrived.

Bad input exits 2 with the full list of offending paths, not the first one. You fix the file and re-run that stage.

Constraints are not loosened to make a test pass. If a limitations array must have at least one entry, a packet without one does not validate, and the fix is the packet.

Reading a run without the tool

Nothing here requires Research Scan to interpret. evidence.json is a JSON array of objects with documented fields; manifest.json records counts; the logs are JSONL. jq is a perfectly good client:

Terminal window
jq '.[] | {rank, title, relation, verified: .verification.verified}' evidence.json

That is the practical test of whether files are really the interface. If you need the tool to read its own output, they are not.

Last updated Aug 21, 2026