# 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:

```bash
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:

```bash
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.
