backdraft

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
In practice the reading and writing steps are an agent's: you ask for a cited memo, the backdraft skill imposes the gate, and you receive the artifact.

CLI reference

CommandDoes
initCreate .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).
lsList 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|showLedger 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.
exportThe whole registry as JSON.

Bind exit codes

CodeMeans
0Every citation resolved.
1Usage or environment error.
2Bind 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 # title becomes 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

FileIs
memo.mdWhat the writer wrote, prose plus tokens. Yours; re-bindable.
memo.backdraft.htmlThe 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 generic OPENAI_API_KEY in 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.