Documentation
Backdraft is three things that ship together: a Python CLI
(pip install backdraft) that ingests documents and checks
citations, a one-page agent skill that makes your agent write through it, and
a self-contained file format for the output. There is no server and no SDK to
integrate, the CLI is the whole system. This page covers install, the
concepts, every command, and what ends up on disk. The normative format specs
live in the
repo, and agents should start from llms.txt.
Install
uv tool install "backdraft[vlm]"
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.
Bring a model provider
The vision extractor needs a key you provide, and it runs only on explicit,
backdraft-scoped consent: set BACKDRAFT_VLM_API_KEY in
.backdraft/env (written by backdraft init) or the
environment. Ambient OPENAI_API_KEY-style variables are
deliberately never read.
Under the hood the client is the OpenAI SDK with an injectable
base_url, so any OpenAI-compatible provider works. The default is
OpenRouter (https://openrouter.ai/api/v1) running
google/gemini-3.1-flash-lite-preview, so the simplest setup is an
OpenRouter key. To point elsewhere, set BACKDRAFT_VLM_BASE_URL
and BACKDRAFT_VLM_MODEL, for example directly at OpenAI or at a
local server. [entail] adds the optional model-judge
verifier, keyed separately by BACKDRAFT_ENTAIL_API_KEY.
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
backdraft skill install # the writing skill, into ~/.claude/skills/ backdraft skill install --all # plus backfill and artifact-reading backdraft skill install --project # into this repo's .claude/skills/
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.