Documentation
Everything to run the loop: install, the concepts that make it trustworthy, every command, and what ends up on disk. The normative format specs live in the repo; agents should start from llms.txt.
Install
uv tool install "backdraft[vlm] @ git+https://github.com/spencerbraun/backdraft"
Python 3.13+. The [vlm] extra adds the vision-model PDF
extractor — the recommended path for real PDFs (glossy layouts, info boxes,
scans) — and needs poppler on your PATH
(brew install poppler). Without it, PDFs fall back to the keyless
embedded-text layer, and ingest says so.
The VLM extractor runs only on explicit, backdraft-scoped consent: set
BACKDRAFT_VLM_API_KEY in .backdraft/env (written by
backdraft init) or the environment. The default provider is
OpenRouter running Gemini Flash; ambient OPENAI_API_KEY-style
variables are deliberately never read. [entail] adds the optional
model-judge verifier.
Concepts
The gate
Source documents reach the writer only through read,
search and cell, which stamp a token on every
span they show and record the showing in a session ledger. The set of
citable things is exactly the set of things shown — so a citation to something
the writer never saw is a distinguishable failure
(not_shown), not an invisible one.
Tokens and receipts
bd:<slug>:<locator>:<hash> — document,
place, and a content-hash of the exact text cited. Locators are media-native:
p8 (a page), p8.c3 (a paragraph-scale chunk),
rent-roll!B10 (a cell). An anchor is not a pointer: it carries the
verbatim snippet and its sha256, so the finished artifact is defensible with
the registry deleted and the sources gone.
Drift
Re-ingesting identical bytes yields identical tokens. When a source
changes, citations into it resolve against the superseded snapshot and report
drifted — the artifact shows what was cited and what stands now,
as a word-diff.
The workflow
# once per project backdraft init backdraft ingest report.pdf model.xlsx notes.md backdraft session start --id s-deal export BACKDRAFT_SESSION=s-deal # read through the gate; every chunk arrives with a token backdraft read # list documents backdraft read report # table of contents backdraft read report p4-6 # pages, ranges, sheet names backdraft search "24850000" # hits are citable directly backdraft cell model "rent-roll!B10" # write claims as links, then bind and render backdraft bind memo.md --check value-trace,overlap backdraft render memo.md --to html # -> memo.backdraft.html
CLI reference
| Command | Does |
|---|---|
init | Create .backdraft/ (registry, credentials template) in the current directory. |
ingest <files> | Snapshot sources into the registry, minting every anchor. --extractor auto|vlm|pdf-text|xlsx|text, --slug, --config k=v. |
snapshot-pages <slug> | Backfill page images for an already-ingested PDF, locally, no model calls ([vlm] extra for rendering). |
ls | List ingested documents. |
read [slug] [selector] | The gate: document list, table of contents, or a token-marked page/range/sheet read. Mints what it shows. |
search <query> | Full-text search over every anchor; hits are citable. --in slug, --limit. |
cell <slug> <sheet!REF>… | Mint specific cells' tokens directly: token plus verbatim value. |
session start|show | Ledger sessions; export BACKDRAFT_SESSION to enable not_shown detection. |
bind <doc.md> | Resolve every citation, run --check verifiers, assemble evidence, write the record. --session, --mode frontwalk|backfill, --lean (skip page images), --bound (also write the markdown projection). |
render <doc.md> | The artifact. --to html|footnotes|json, -o. |
clean [dir] | Tidy a working directory: relocate stray records, remove leftover projections. Never touches authored files or artifacts. |
export | The whole registry as JSON. |
Bind exit codes
| Code | Means |
|---|---|
0 | Every citation resolved. |
1 | Usage or environment error. |
2 | Bind completed and something did not resolve — the code a CI job or Stop-hook gates on. Verification verdicts never produce it. |
Writing rules
Claims are markdown links whose href is the token, copied exactly from gate
output; multiple citations are ;-separated in one href:
[net operating income of $1,429,600](bd:t12-summary:p1.c3:f10b) [EGI of $2,684,400](bd:t12:p1.c2:7f11;bd:model:rent-roll!B11:4b79)
- Bind the span, not the sentence. The link text is the words the evidence supports.
- Cite only what you were shown. Never construct or edit a token by hand.
- Uncited prose is fine for recommendations and framing — cite facts, not opinions.
- Never fix a failure by deleting its token. A kept failure is the honest outcome; the artifact will show it plainly.
- An italic line directly under the
# titlebecomes the artifact's subtitle.
Verification
Independent switches, off by default, recorded as graded evidence — never
gates. value-trace finds every figure in a claim in its cited
source, reading through thousands separators, currency, scale suffixes
($1.4M ≡ 1,400,000), percent-vs-decimal (7.7% ≡ 0.077), accounting negatives
and dates; a match that only works after rounding is partial,
and a miss names the figure. overlap measures how much of a
claim's wording appears in its source (and skips single-cell sources, where
the question is meaningless). entail (extra) asks a model
whether the source supports the claim. Verdicts appear in the artifact's
Record layer in plain language.
Files & the artifact
| File | Is |
|---|---|
memo.md | What the writer wrote — prose plus tokens. Yours; re-bindable. |
memo.backdraft.html | The deliverable: document, receipts and evidence in one self-contained file. No network (CSP-enforced), nothing to install, degrades to readable footnotes if scripts are stripped. |
.backdraft/ | The machinery: registry, credentials, and the bind record (records/memo.backdraft.json — the machine-readable run, also embedded in the artifact as a JSON island with a self-describing legend). |
The artifact embeds only cited evidence — a memo citing ten pages
of a gigabyte corpus is a ~2 MB file. Recipients need the one
.html file and nothing else.
Agents
cp -r skills/backdraft ~/.claude/skills/ # write cited documents cp -r skills/backdraft-backfill ~/.claude/skills/ # attribute an existing doc cp -r skills/backdraft-artifact ~/.claude/skills/ # read an artifact cold
The skill's core instruction is a substitution: for source documents, use
backdraft read/search, never raw file reads. The
token-efficient reference for any agent is llms.txt
(~800 tokens).
Security & privacy
- Ambient keys are never read. Credentials reach backdraft only via
BACKDRAFT_*variables,.backdraft/env, or--config. A genericOPENAI_API_KEYin your shell is not consent to send documents anywhere. - Artifacts make no network requests, enforced by a Content-Security-Policy the browser applies — no fonts, no analytics, no phone-home.
- Everything is local. The only network calls in the system are the optional VLM/entail model calls you explicitly key.
- Gitignore
.backdraft/for confidential corpora — the registry contains the full text of everything ingested.
FAQ
Ingest says it fell back to the text layer — why?
The note names the condition: either the [vlm] extra isn't
installed, or BACKDRAFT_VLM_API_KEY isn't set. Fix the named one.
Text-layer receipts are fine for clean digital PDFs; glossy or scanned ones
deserve the vision model.
My artifact has no page images.
Images are stored at ingest by the VLM path. For text-layer or older
registries, run backdraft snapshot-pages <slug> (local,
free), then re-bind.
What does not_shown actually catch?
A real token, valid in the registry, that the writing session was never shown — i.e. the writer cited something it didn't read. Only a session ledger makes this class of failure visible; that is why the skill starts one.
Can a reader trust an artifact without installing backdraft?
That's the design goal: the receipts, the evidence, and a machine-readable record with its own decoding legend are inside the file. The artifact spec lists the checks a skeptic can run with nothing but the file.
Is this fact-checking?
No — provenance. Backdraft proves where a claim came from and shows you the source; the optional verifiers add deterministic evidence like "this figure appears in that cell." Judgment stays with the reader.