This is the human-friendly version of ../SPEC.md §2. If you want the field-by-field schema, read the spec; if you want to understand the model, read this.
A vouch KB is six kinds of objects, plus the audit log:
┌──── Source ───────────────┐
│ immutable input bytes, │
│ content-addressed sha256 │
└────────────┬──────────────┘
│ cited by
▼
┌──── Claim ────────────────┐
│ atomic assertion, │
│ must cite ≥ 1 source/ev │
└────────────┬──────────────┘
│ referenced by
▼
┌──── Page ─────────────────┐
│ narrative markdown that │
│ weaves claims together │
└───────────────────────────┘
┌──── Entity ───────────────┐
│ typed named thing │
│ (service, person, repo…) │
└────────────┬──────────────┘
│
▼
┌──── Relation ─────────────┐
│ typed edge between two │
│ ids (uses, supersedes…) │
└───────────────────────────┘
A Source is "the bytes that something was said in" — a meeting note, a PR description, a transcript, a URL snapshot, a commit message. vouch identifies sources by sha256 of the content. Re-adding the same content gives you the same id.
When a source is large or external (a YouTube transcript, a public URL), you can register just the metadata — vouch records the locator and the hash you saw at capture time, without copying bytes locally.
Evidence is more specific: a pointer into a source. "Lines 20-25 of meeting-notes.md" or "0:14:23 in the recording". Claims can cite either Sources or Evidence.
A pdf or a recording cannot be quoted directly — a receipt is a byte
range into the stored bytes, and the bytes of a pdf or an mp3 don't
spell the sentence you want to cite. vouch source add spec.pdf (or
call.mp3) extracts the text first and stores that as the source, so
citations work exactly as they do for a text file. Alongside it vouch
keeps a coordinate map: which byte range came from which page, or
from which point in the recording.
The upshot is a citation that verifies mechanically and points somewhere a human can check:
$ vouch source add postmortem.pdf
9f2c… # id of the extracted text
$ vouch source locate 9f2c… "rollback took eleven minutes"
p7@b1200-1340 # page 7, bytes 1200-1340Two deliberate limits. Extraction needs the optional [pdf] extra
(pip install 'vouch-kb[pdf]'), and a scanned pdf with no text
layer is refused rather than silently registered empty — vouch does
not OCR. Audio needs no extra: transcription is a command you configure,
so vouch never bundles a speech model.
# .vouch/config.yaml
sources:
transcribe_cmd: "whisper --output_format vtt --output_dir - {path}"The command must emit WebVTT or SubRip; {path} is replaced with the
audio file's path. vouch records the original file's sha256 too, so
vouch source verify still notices if the pdf or the recording changes
underneath a claim that cites it. Pass --raw to register the bytes
untouched instead.
A Claim is the smallest statement worth citing or contradicting. Examples:
- ✅ "Auth uses JWTs in the Authorization header." — one fact, one citation, easy to verify.
- ❌ "Auth uses JWTs, we use Postgres, and on-call is 24/7." — three claims jammed together. Split.
A claim has:
- text — one sentence.
- type — fact / decision / preference / workflow / observation / question / warning.
- status — working (draft) / actionable / stable (the default durable state) / contested (two stable claims disagree) / superseded (a newer claim replaced this one) / archived / redacted.
- confidence — 0.0 to 1.0. Default 0.7. Be honest; 1.0 is suspicious.
- evidence — one or more Source or Evidence ids. Required. A claim without evidence is rejected at the gate.
A Page is maintained markdown. Use pages when:
- You want a write-up, not a list of disconnected facts.
- You want one URL/file to land on when someone says "tell me about auth".
- You're producing a decision record that needs prose explaining the trade-offs.
Pages reference claims by id. They have YAML frontmatter for metadata (claims, entities, sources, tags) and plain GitHub-flavoured markdown for the body.
An Entity is a typed named thing: a person, a service, a repo, a
concept, an incident. Entities anchor claims — instead of saying
"about Postgres" via tags, you say entities: [postgres] and the
graph knows.
A Relation is a typed edge between two ids: service-api uses postgres, claim-x supersedes claim-y, decision-z implements rfc-12. Relations are themselves objects with their own ids, so you
can cite the connection.
You don't have to use entities and relations. A small KB does fine with just Claims and Pages. They start paying off around 50+ claims, when "find everything about X" stops being easy via search alone.
A Session is one work block an agent had. It opens with
kb.session_start, closes with kb.session_end, and bundles every
proposal the agent filed during that block.
kb.crystallize looks at a closed session and produces a
session-summary page. The agent's proposals are listed; the durable
parts get promoted into reviewable form. This is how an hour of agent
work compresses into a coherent record without the human having to
read every individual proposal.
A Proposal is what the agent files instead of writing directly.
It contains the would-be artifact in its payload, plus
proposed_by, rationale, and a status (pending → approved or
rejected).
Proposals live in .vouch/proposed/ (gitignored) until decided, then
move to .vouch/decided/ (committed). The durable artifact —
claims/, pages/, etc. — is only written on approve.
See review-gate.md for the state machine.
Every mutation in the KB emits one line in .vouch/audit.log.jsonl.
Source registration, proposal creation, approval, rejection,
supersession, archival — all of it. The log is committed and
append-only.
This is what makes vouch auditable: not the review gate alone, but the gate plus a complete history of what was decided when by whom.
vouch deliberately doesn't have:
- Tags as first-class. Tags are strings on objects, not their own records. If a tag matters enough to be cited, it should be an Entity.
- Folders or namespaces. The flat directory layout is the storage, not the user-facing organisation. Pages and entities are how you group things.
- Comments on claims. A comment that argues with a claim is
itself a claim — file it, cite it, mark
contradicts.
These omissions are intentional. If you find yourself wanting one of them, please open an issue; we're listening.