Reference

Last updated: 14 September 2026

This is the catalogue's own card index. The five pages before it tell the story; this one lists the verbs, their flags, the fields a program reads, the codes the linter prints and the files on disk, each copied from the source rather than remembered. The verbs and the check codes were last read on 8 September 2026 and the rest on 6 September. Where a line had to be assembled instead of copied, the entry says so. Nobody is expected to read it through.

The twenty-eight verbs

The usage block

What kb --help prints, minus its prose, copied on 14 September 2026. Thirty-nine lines for twenty-eight verbs. init, handoff and panel each carry more than one usage, because each of their modes has its own argument set and folding them into one line would hide which flags may travel together; write, source add, handoff save and one of the panel usages wrap onto a continuation line because they are too long for the width.

kb --version
kb check [path]... [--strict] [--all]
kb index [path]... [--json] [--all]
kb list [path]... [--base B] [--folder F] [--kind K] [--stage S] [--provenance P] [--json] [--all]
kb route <question> [path]... [--top N] [--hybrid] [--json] [--all]
kb answer <question> [path]... [--top N] [--all] [--expanded | --complete]
kb remember <claim> [path]... [--all] [--json]
kb init <name> [fleet-root]
kb init --person [fleet-root]
kb write <agent> <slug> [fleet-root] --keys <a, b> --summary <one line> [--folder F]
            [--captured-from PATH]
kb source add [path] --type T --title X --retrieved-on DATE --retrieval-status S
              [--author A]... [--year Y] [--container C] [--volume V] [--pages P]
              [--publisher P] [--url U] [--doi D] [--isbn I] [--lang L]
              [--replaces KEY] [--note N]
kb sources [path]... [--json] [--all]
kb fleet [path]... [--all]
kb blocks [path] [--emit]
kb eval <gold.tsv> [path]... [--top N] [--all] [--classify] [--triad]
kb commit <path>... -m <message>
kb boot [path]... [--top N] [--all] [--session ID] [--cwd DIR] [--text]
kb ingest <file> [path]... [--agent NAME] [--keep] [--dry-run] [--top N] [--all]
kb promote [path]... [--top N] [--all] [--dry-run] [--max N] [--lock]
kb consolidate <agent> [path] [--sessions N] [--top N] [--dry-run] [--json]
kb handoff save <agent> --task <task> [--decision <d>]... [--blocker <b>]... [--next <n>]... [--ref <r>]... [--session ID] [path]
kb handoff claim <agent> [--session ID] [path]
kb handoff complete [--session ID] [path]
kb handoff [path] [--session ID] [--show] [--json]
kb ui [path]... [--port N] [--all]
kb capture [path] [--session ID] [--conclusion <text>]
kb serve [path]... [--top N] [--all]
kb misses [path]... [--all] [--top N] [--json] [--apply --gold <tsv>]
kb misroute <message> --chose <agent> --owner <agent|none> [--why <text>] [path]
kb misroutes [path] [--top N]
kb abstentions [path] [--top N] [--all]
kb panel <artifact> [path] --owner <agent> [--reviewer <agent>]... [--out D] [--json]
kb panel <artifact> [path] --from <agent> (--objection <text> [--blocking]
                                          | --nothing | --not-returned) [--why <text>]
kb panel <artifact> [path] --resolve <n> (--taken | --refused | --escalated) [--why <text>]
kb panel [<artifact>] [path] --ledger [--json]

answer carried no usage line when this page was first written and its entry below had to be assembled from the source. The line exists now and is copied like the rest.

Verb by verb

Each entry is the usage line, what the verb does, and the one thing it never does. The last sentence is the one to hold it to.

kb route <question> [path]... [--top N] [--hybrid] [--json] [--all]
Asks the catalogue which books a question should open. The question is scored against every note's Search for line, the owner agent is named, and the verdict is one of hit, guess or nothing. --hybrid fuses the keyword ranking with full text search over the chunks; --json prints the whole answer as one line on stdout and always fuses, so --hybrid adds nothing on top of it. It never calls a model and never invents a file.
kb answer <question> [path]... [--top N] [--all] [--expanded | --complete]
Copied, where it used to be assembled: this verb had no line in --help when the page was written, and it has one now. It runs route, then assembles context using small-to-big section windowing: expanding matched chunk hits to their enclosing section and paragraph boundaries (up to 1,800 characters, ~450 tokens) and merging contiguous chunks cleanly. It then hands the served passages to the command named by answerer = in fleet.txt, which must ground every claim in them or say plainly that they do not hold the answer. The default reads the top five files in one model call; --expanded reads up to twelve; --complete reads the whole base in batches of ten and prints its estimate before the first call. It never reaches the model on a nothing verdict, and without an answerer line it prints the reading list route would have printed, except in --complete, which is nothing but model calls and stops instead.
kb remember <claim> [path]... [--all] [--json]
Measures a claim against what the library already holds and proposes ADD, UPDATE or NOOP, with the overlapping passage as evidence. The measure is containment, the fraction of the claim's words already present in one chunk: NOOP at 0.90 or above, UPDATE at 0.55 or above, ADD below. It writes nothing, and it never proposes DELETE, because absence and falsehood look the same to a word count.
kb write <agent> <slug> [fleet-root] --keys <a, b> --summary <one line> [--folder F]
Reads the note body on stdin and writes the keys twice: into the note's own Search for header, which is the only thing the catalogue indexes, and into an entry in the agent's MAP.md, which is a reading list for a person. --folder defaults to knowledge, --provenance to agent and --stage to derived. It refuses without keys, and there is no flag to skip them; it also refuses when the agent has no MAP.md. It never leaves half a write behind: a failed map entry deletes the note again.
kb source add [path] --type T --title X --retrieved-on DATE --retrieval-status S
Mints a source record. The key is ten characters from an alphabet with 0, 1 and O removed, drawn from the operating system's generator, and the record lands at sources/<KEY>.txt. It then prints the one bibliography line a citing note has to carry, which kb check later compares byte for byte, so the reference list is a projection of the pointers rather than a second telling of them. --type is one of book, chapter, article, report, webpage, recording; --retrieval-status is one of full, partial, metadata, unreachable, and it cannot be skipped, because a source nobody opened has to say so out loud. --replaces KEY supersedes an earlier record without deleting it. The key is never derived from the title, the author or the path, and it is never regenerated.
kb sources [path]... [--json] [--all]
Lists what a base has read, one rendered line per record. --json emits CSL JSON, the shape every reference processor already reads, with retrieved_on under accessed and retrieval_status and replaces under custom. Read off disk and never out of the index, so an export is never an export of a stale cache, and it skips a base that declares sources/ private unless --all is given. It is an export and never an import: nothing here reads CSL back.
kb promote [path]... [--top N] [--all] [--dry-run] [--max N] [--lock]
Reads each agent's inbox/ and offers what it finds to two promoters. The first proposes notes and never sees the base; the second decides, three times through three different questions, and never sees the first one's reasoning. Only a unanimous accept writes, at stage captured, and a refusal is counted in kb-rejections.txt. --dry-run decides everything and writes nothing; --max N stops after N proposals are admitted and leaves the rest of the deposit where it is; --lock refuses to start while another run holds .kb-promote.lock. It never writes on a split decision.
kb check [path]... [--strict] [--all]
Lints every base and reports what the conventions promise while nothing was checking. The exit code is 1 when it finds errors, or when --strict is given and it finds warnings. It fixes nothing and touches no file. The codes are listed below.
kb index [path]... [--json] [--all]
Builds one SQLite file per base at <base>/.kb/index.db from the markdown, content hashed so that an unchanged file costs nothing on the next run. --json prints the index entries as JSON instead of building the index. One walk classifies every file it touches into three buckets that sum back to the files on disk, so the totals line also carries unreachable, the files with no Search for line, which is E02's own population and is listed three deep with a pointer to the linter for the rest, and unsearchable by design, the files exempt from carrying one at all. A remainder with no name is how a deliberate exemption comes to be read as a broken base. It never runs in the background, and it holds nothing that cannot be rebuilt from the files.
kb list [path]... [--base B] [--folder F] [--kind K] [--stage S] [--provenance P] [--json] [--all]
Lists what the shelves hold, narrowed by facet, with nothing ranked. It takes no question, so the answer carries no score, no floor and no verdict: a filter has no ranking problem in it, and asking one of route scores it against the floor and comes back as a guess, which is the confusion this verb exists to end. --base is one agent by its directory name; --folder a directory and everything under it; --kind the species read from the folder, memory, skills or tools; --stage one of raw, captured, distilled or derived; and --provenance one of human, agent or external. The last two are read from each file's own front matter rather than from the index, so a note whose header changed an hour ago is listed as it stands and not as the last kb index saw it. Facets combine with AND, and a value outside its set is an error naming the legal set, exit code 2, never an empty list: zero rows reads as a fact about the base rather than as a typo. The walk is over the files and not over the index entries, so a file no question can reach is a row like any other, which is the whole deposit and every README.md. It never ranks anything, and it never lists the private layer without --all.
kb eval <gold.tsv> [path]... [--top N] [--all] [--classify] [--triad]
Grades the catalogue against a gold file of questions and expected answers, including the questions it is supposed to refuse. --classify asks the command that chooses the agent, named by classifier = in fleet.txt, about every question, one model call each. --triad computes offline RAGAS-style triad metrics in pure Rust with zero external API calls: faithfulness (grounding in served passages), answer relevance, and context relevancy, scoring abstentions faithfully. It refuses to grade a gold file that names files the fleet does not have, because grading against a stale gold file reports a wrong number confidently.
kb boot [path]... [--top N] [--all] [--session ID] [--cwd DIR] [--text]
The librarian at the door. On a UserPromptSubmit hook it reads the message from stdin, decomposes compound multi-domain queries into atomic sub-questions, routes them across the fleet, and prints the owner's constitution to stdout, where the runtime injects it; the constitution is emitted only when the routed agent changes, tracked per session under .kb/sessions/. When sub-queries trigger distinct high-confidence agents, Vesta seats the primary agent as owner and automatically convenes a multi-agent review panel (kb panel) with secondary specialists. Below the gate it picks nobody and emits the roster instead. It always exits 0, because exit 2 on that event erases the user's message and exit 1 shows them a hook error.
kb blocks [path] [--emit]
Reads blocks.txt and reports each block's mode, files, bytes and tokens, then what changing each one costs in tokens that have to be prefilled again. --emit prints the assembled resident constitution instead of the report. It never invents a block that is not on disk.
kb fleet [path]... [--all]
Prints the fleet's name and roster, read from fleet.txt and from each agent.txt; it is the same text kb_fleet returns over MCP, so what a model sees and what a person sees cannot drift apart. Identity is never derived from the index.
kb init <name> [fleet-root]
kb init --person [fleet-root]
Generates a base in the shape the catalogue needs, under <fleet-root>/fleet/<name>, served as it stands: no repository is needed and none is created. --person writes the person's base, which has no agent.txt, so the catalogue reads it and can never choose it as the one who answers. It never writes a word about the person; the skeleton is empty on purpose.
kb commit <path>... -m <message>
Commits exactly the paths named, through git commit -- <paths>, then reads the commit back and prints what it left dirty. There is deliberately no flag meaning everything. It refuses an empty path list, a blank message, and a list that spans two repositories.
kb serve [path]... [--top N] [--all]
Speaks the Model Context Protocol over stdio, with five tools, kb_route, kb_retrieve, kb_remember, kb_fleet and kb_list, all through the same Memory contract the command line uses. It writes nothing to stdout that is not protocol; every diagnostic goes to stderr. It never serves the private layer unless --all says so.
kb ui [path]... [--port N] [--all]
The reading room: a local page over the same contract. It binds 127.0.0.1 and nothing else, on port 4114 unless --port says otherwise, and there is no flag for any other address. It never serves a file discovery did not produce, however the path is spelled.
kb capture [path] [--session ID] [--conclusion <text>]
Turns a session's record into a deposit: one markdown file in the routed agent's inbox/, raw and without a Search for line, so the catalogue never names it and every passage from it is labelled short memory. The session id comes from --session or from the hook payload on stdin, the same JSON boot reads. With --conclusion <text>, records assistant decisions or conclusions into the session events; secret credentials (API keys, bearer tokens, passwords) are automatically scrubbed in pure Rust standard library without external regex dependencies, and rendered under a ## Decisions and conclusions section in the deposit. It runs no model and captures nothing from a session no agent was routed in.
kb misses [path]... [--all] [--top N] [--json]
Reads kb-misses.txt back: every question the free stage could not answer, most asked first, with its count, its dates and the vocabulary the base offered back on the day. Beside each one it names the files today's index nearly caught it with and the keys each of those files declares, which is the half a log cannot hold: which file came close depends on the base as it stands now, and that changes every time a note is written. A file the text scorer reached at a keyword score of 0.00 is the finding itself, because the words are in the note and not on its Search for line, so the repair is an alias line or another key rather than another note. --top N bounds the candidates carried per logged question, not how many questions are printed. It always prints the path it read, because KB_MISSES_PATH can move the log and two fleets pointed at one path share it, and an absent log exits 0: a fleet nothing has missed against is healthy rather than broken. It proposes an alias line and, since 3 September 2026, can also apply one. This page said until 6 September 2026 that it proposes and never applies, and that there was deliberately no flag that does. There is one now, and the condition on it is the whole point. --apply exists only together with --gold <tsv>, and the pairing is not ergonomic sugar: every argument for letting a machine write into kb-aliases.txt rests on the alias being measured against a gold set afterwards, so a writer with nothing to measure against is the one case the flag refuses to be. Without --gold it still only proposes.
kb ingest <file> [path]... [--agent NAME] [--keep] [--dry-run] [--top N] [--all]
Turns a document on somebody's disk into notes in one step, where it used to be seven prose steps that most agents did not have. It moves the document into the owning agent's deposit, extracts its text through tools/extract/<ext>.cmd, distils it through the same promote the nightly sweep runs, and then destroys the source. --agent names the owner instead of letting the router pick, --keep leaves the document alone, and --dry-run does none of it. It never deletes a document that produced no note, and never deletes a path the caller typed: it only ever removes what it moved itself.
kb misroute <message> --chose <agent> --owner <agent|none> [--why <text>] [path]
Records that the router handed a message to one agent when a different one owns it, in the owner's own words, so a routing fault becomes a countable fact instead of an anecdote that dies with the conversation. It is evidence and never an edit: it does not touch a base, and it does not decide who was right.
kb misroutes [path] [--top N]
Reads that log back, most reported first, so a pair of bases that keep being mistaken for each other is visible as a pattern rather than remembered as a grievance. It writes nothing, and it ranks by nothing except how often the same fault was filed.
kb abstentions [path] [--top N] [--all]
The router finding that nobody owns a subject is the fleet's most useful failure, and it was the only one that left no trace: the miss log fires on retrieval, and a misroute needs an agent to file it. This records each coverage abstention keyed on the classifier's subject rather than on the words of the message, with its reason and the bases that scored anyway, and reads them back with what each would score today. Keying on the message is what it refuses to do, because that would leave every real gap sitting at a count of one forever.
kb panel <artifact> [path] --owner <agent> [--reviewer <agent>]...
Opens an objection round on one artifact and keeps its ledger, so the objection that killed a piece is remembered by something other than whoever happened to be in the room. The first form boots a named panel from each reviewer's own blocks.txt and prices the round in tokens before it is spent. --from records what one reviewer returned, silence included, as an objection, a --nothing or a --not-returned. Objections are validated against constitutional self-critique rubrics: blocking objections must be mechanism-grounded (citing repository invariants, failure modes, memory limits, or rules). Subjective stylistic nitpicks are classified as advisory and cannot block note admission. --resolve accounts for each objection as --taken, --refused with a reason, or --escalated. --ledger reads the round back, and the round does not close until every objection is accounted for. It never calls a model and never chooses the panel for you. A blocking objection cannot be refused, and each reviewer gets at most one, because a reviewer who can block everything holds a veto rather than an objection.
kb consolidate <agent> [path] [--sessions N] [--top N] [--dry-run] [--json]
Compares records across multiple past sessions side-by-side to discover recurrent patterns, cross-session architectural decisions, and common blockers. Enforces a code-level invariant requiring every candidate pattern to cite at least two distinct sessions that genuinely exist in recorded history before passing to the three review lenses (contradiction, duplication, scope) against the base. --sessions N controls how many past sessions to gather (default 5); --dry-run evaluates everything without writing.
kb handoff save <agent> --task <task> [--decision <d>]... [--blocker <b>]... [--next <n>]... [--ref <r>]... [--session ID] [path]
kb handoff claim <agent> [--session ID] [path]
kb handoff complete [--session ID] [path]
kb handoff [path] [--session ID] [--show] [--json]
Persists and coordinates structured task continuity across sessions. handoff save records the active task, architectural decisions, blockers, references, and next steps into .kb/sessions/<session>.handoff.md. kb handoff claim <agent> and kb handoff complete enforce strict baton claim semantics (Pending → Claimed → Completed): an agent claims the active baton before starting work, preventing race conditions between concurrent sessions, and marks it complete once finished. Running kb handoff reads the latest handoff record back so the incoming agent or session resumes with full context and zero memory loss.
kb --version, also kb -V and kb version
One line: the crate version, the commit the binary was compiled from, and the platform, as in kb 0.2.2 (a1b2c3d, x86_64 linux). The commit is compiled in at build time from KB_BUILD_SHA, which the release workflow sets to the commit it is building, and a build with none says unknown rather than naming one it might not have come from. It never asks git at runtime, and that is the reason it exists: the copy being questioned is usually vendored inside somebody else's deployment bundle, on a host with no .git and no git binary to ask.

Shared flags

path
The base to work on. Defaults to the current directory. A path may also be a fleet root, a directory that is not itself a base but whose immediate children are; both are accepted and neither is required.
--top N
How many candidates to carry. Default 5. route, boot, eval, promote, serve and misses all read it. On misses it bounds the candidates carried per logged question and not the number of questions printed.
--all
Include the private layer, and nothing else: the folders a base declares with a private = line in agent.txt, or profile/, projects/ and records/ when it declares nothing. . declares the whole base, and the person's base is whole by name. Nothing outside the base is consulted, and no repository is needed.

kb route --json, field by field

One line of JSON on stdout, computed in one pass: the owner and the verdict from the keyword ranking, the reading list from the fusion of both scorers. Every score in it is rounded to six decimals, because the keyword score is a 32-bit float and widening it would print seventeen digits of precision it never had. When the base cannot be opened, stdout carries {"question": ..., "error": ...} and the exit code is 1.

The shape

Types rather than values, so nothing here is an example that could go stale. A vertical bar separates the values a field can take.

{
  "question": string,
  "verdict": "hit" | "guess" | "nothing",
  "gate": {
    "served": bool,
    "ranked_by_text_only": bool,
    "floor": number
  },
  "confidence": {
    "agreement": 0 | 1 | 2,
    "keyword_score": number,
    "margin": number
  },
  "agent": null | {
    "name": string,
    "score": number,
    "files": integer,
    "margin": number | null,
    "contenders": integer,
    "totals": [ { "agent": string, "score": number } ]
  },
  "keyword_top": string | null,
  "indexed": {
    "entries": integer,
    "agents": integer,
    "aliases": integer
  },
  "unreachable": {
    "files": integer,
    "paths": [ string ],
    "unindexed": integer,
    "unindexed_paths": [ string ]
  },
  "skipped": [ string ],
  "index_was_rebuilt": bool,
  "suggestions": [ string ],
  "miss": null | {
    "question": string,
    "looked_like": [ string ],
    "date": string,
    "log": string,
    "recorded": bool,
    "error": string | null
  },
  "results": [ {
    "base": string,
    "path": string,
    "memory": "short" | "long",
    "title": string,
    "purpose": string,
    "score": number,
    "keyword_score": number,
    "why": [ string ],
    "matched": [ string ],
    "passages": [ {
      "heading_path": string,
      "text": string,
      "excerpt": string,
      "provenance": string | null,
      "stage": string | null
    } ]
  } ]
}

The top level

question
The question, as it was given.
verdict
hit, guess or nothing: the top file's keyword score against the floor for this fleet's size, and never hit in a base with fewer than two entries. The margin over the runner-up is reported under confidence and does not gate. It answers whether the keyword scorer ranked anything worth serving; it does not answer whether either scorer ranked anything at all, which is what gate is for.
keyword_top
The file the keyword scorer ranked first, as base/path, or null when it ranked nothing.
indexed.entries
indexed.agents
indexed.aliases
How many keyed entries, agents and alias lines were opened for this answer. An entry is a note with a Search for line.
unreachable.files
unreachable.paths
How many files across the open bases the catalogue can build no entry for, and the first eight of them as base/path. An authoring problem: the file is on disk, a person can open and read it, and it scores zero on every question ever asked. It is the same population kb check reports as E02, and nobody is required to run kb check.
unreachable.unindexed
unreachable.unindexed_paths
A different problem with a different repair: files on disk the indexes hold no chunks for, which is a kb index that has not run since they were written. Which scope produced the number matters and is not obvious: an index synced without --all and opened with it correctly reports every private file as unindexed, and the reverse reports nothing at all, so two runs can disagree while both are telling the truth. The counts are exact and only the two arrays are capped at eight, because a short list beside a count taken from the same short list understates the problem by exactly as much as it was shortened.
skipped
Bases the open had to leave out, named here rather than only on stderr, because a caller that reads stdout alone must not read an empty result set as a base that does not cover the question. Empty since the day git left the runtime: the one reason a base used to be left out no longer exists, and the field stays because callers read it.
index_was_rebuilt
true when an index built before the private column existed was found and emptied on open. Run kb index before relying on the passages.
suggestions
The vocabulary the base does know that looks like the words used, by character trigram overlap; the same list miss.looked_like carries. Empty when the answer was served, and empty on a miss with no orthographic neighbour, which is an honest answer.

gate and confidence

Branch on gate.served, never on the length of results. A refused answer still carries its candidates, because the verdict comes from the keyword scorer and the results come from both, so "verdict": "nothing" with a full array is an ordinary outcome rather than a contradiction.

gate.served
Whether the answer is served: false exactly when the verdict is nothing. The rule stated as a field, so a caller does not have to re-derive it from a string.
gate.ranked_by_text_only
true when the answer was refused and results is not empty: the text scorer ranked files the keyword lines missed, which is a base whose labels need a word rather than a base without the subject. false with an empty results means nothing ranked anywhere.
gate.floor
What confidence.keyword_score was measured against, for this fleet's size, never the calibration constant. A caller who disagrees with the gate needs the threshold that actually applied.
confidence.agreement
How many of the two independent scorers ranked the top file: 0, 1 or 2. Reported and never used to gate.
confidence.keyword_score
The top file's raw keyword score. Zero when only the text scorer found it.
confidence.margin
The top keyword score divided by the best runner-up's, and 1.0 when nothing else scored, because standing alone is not the same as winning.

agent

null when no agent scored. Otherwise the owner the fold chose, with the numbers a caller holding an incumbent would need to argue with it.

agent.name
The owner agent.
agent.score
The agent's total in the fold.
agent.files
Since ADR-0031 this counts species with evidence, memory, skills or tools, at most three per agent, and no longer files: fifty-nine matching files and one matching file are the same one species.
agent.margin
Over the runner-up agent. null when only one agent scored at all, which is JSON's encoding of infinity and means maximum confidence rather than a missing field: no other base in the fleet had anything to say.
agent.contenders
How many agents scored anything.
agent.totals
Every agent that scored, best first, each as {"agent": ..., "score": ...}.

miss

null when the answer was served. On a refusal it is the recall loss itself, handed back rather than only logged, and it repeats question and the suggestions on purpose: the object exists to be copied whole into somebody else's store by a caller that has nowhere of its own to write.

miss.question
The question, again.
miss.looked_like
The vocabulary offered back, the same list as suggestions.
miss.date
The day it was seen, in the log's own format.
miss.log
Where the log was written, or where the attempt was made.
miss.recorded
Whether the log holds this too. false means the caller holds the only copy.
miss.error
Why the write failed, when it did, and null otherwise. A failed write does not fail the query.

results and passages

One entry per ranked file, best first. Two scores travel because they answer different questions: fusion says which file to read first, and the keyword score is the number the verdict was measured against.

results[].base
results[].path
The agent and the path inside it.
results[].memory
short or long. Short is the deposit, inbox/: recent, unjudged, served on purpose and labelled on purpose. A caller that wants only settled knowledge filters on long.
results[].title
results[].purpose
The note's title and its Exists to line.
results[].score
The fused score.
results[].keyword_score
The raw keyword sum for this file.
results[].why
Which scorer ranked it and where, one string per scorer.
results[].matched
The query words that matched, so a bad ranking can be diagnosed instead of guessed at.
results[].passages[].heading_path
Where in the note the chunk sits, as Title > Section > Subsection.
results[].passages[].text
results[].passages[].excerpt
The chunk, and a shorter cut of it.
results[].passages[].provenance
results[].passages[].stage
From the note's front matter, or null when the note declares neither.

kb remember --json

The proposal as one line, for a caller that is not a person. It measures overlap, proposes, writes nothing and needs no model, which is what lets an agent with no writable filesystem ask whether a fact is worth keeping, queue the answer, and apply it later on a machine that has the files.

{
  "claim": string,
  "proposal": "ADD" | "UPDATE" | "NOOP",
  "reason": string,
  "evidence": [ {
    "base": string,
    "path": string,
    "heading_path": string,
    "excerpt": string,
    "containment": number,
    "shared": [ string ],
    "missing": [ string ]
  } ],
  "notice": string
}
claim
The claim, as it was given.
proposal
ADD, UPDATE or NOOP. There is no fourth value.
reason
One sentence saying what the measure found.
evidence[]
The closest chunks, each with its base, path and heading_path, an excerpt, its containment (the fraction of the claim's words present in that chunk, rounded to six decimals), the words shared with the claim and the words still missing from the base.
notice
The caveat the terminal prints, verbatim, so a model reading this through another surface is told what a person is told: overlap is measured, judgement is not, and DELETE is never proposed.

When the base cannot be opened the error takes the same shape route uses, {"claim": ..., "error": ...} on stdout with exit code 1.

The MCP tools

What kb serve lists under tools/list. Five tools, none of which writes to the base, each returning one text part; the one file a refusal appends to is kb-misses.txt. Every tool answers through the same Memory contract the command line uses, so a model and a person cannot get different answers to one question.

The five tools

kb_route
{ "question": string, "top": integer }. question is required; top is optional and defaults to 5, or to the server's --top. Returns ranked file paths with the words that matched and the EVIDENCE line, and no file contents. On a miss it offers back the terms the base does know that look like the words used, and says that comparison is spelling rather than meaning.
kb_retrieve
The same two arguments. Returns the passages themselves, with the heading path and the provenance of each, under the EVIDENCE line. A passage from the deposit is marked [short memory: recent, not distilled, not yet in the library].
kb_remember
{ "claim": string }, required. Returns the proposal, the reason and the evidence, closest first, and ends with the same notice the command line prints. It writes nothing and decides nothing.
kb_fleet
{ }, no arguments. Returns the fleet's name and role and every agent with its role and directory, read from fleet.txt and each agent.txt. It is a lookup rather than a search, so it works on an empty library.
kb_list
{ "base": string, "folder": string, "kind": string, "stage": string, "provenance": string, "limit": integer }, every one of them optional: required is an empty array and there is no question property, because this is not a search and there is no ranking problem in a filter. An omitted facet means do not filter on this rather than any particular value, and the named ones combine with AND; a value outside its set comes back as a protocol error naming the legal set, never as zero rows, which a model would read as a fact about the base. Returns one line per file with its facets in brackets, nothing scored and nothing refused. limit defaults to 50 and the count of what was cut travels with the cut, so a caller can tell a narrow filter from a truncated one. It inherits the server's scope and states none of its own, so the private layer is listed only when kb serve was started with --all.

The EVIDENCE line

The line after the heading of every kb_route and kb_retrieve answer that found anything, in the words every other surface uses. Angle brackets stand for the numbers; the score and the floor are printed to one decimal and the margin to two.

EVIDENCE: keyword score <s> against a floor of <f>; both|only one|neither of the two independent scorers ranked the top file; it leads the runner-up by <m>x. Verdict: ...

The verdict clause is one of three sentences: something here matches for a hit; a guess, too weak or too close to the runner-up to tell from a coincidence of vocabulary for a guess; nothing matched for nothing. Agreement is reported and never gates the verdict.

The handshake

The server is dual-era: it answers initialize when asked and never requires it, and it echoes back whatever protocol version the client named. When a legacy client names no version, the answer names 2025-06-18, the oldest widely deployed revision, because naming a newer one at a client that does not know it is how a handshake fails. The methods it answers are initialize, ping, tools/list and tools/call; a notification, a message with no id, gets no reply, not even an error. Stdout belongs to the protocol and every diagnostic goes to stderr.

Check codes

What kb check prints, from the linter's own source. A code beginning with E is an error and counts toward the exit code; a code beginning with W is a warning and counts only under --strict.

The codes

Code What it catches
E01 broken-link A [[link]] with no file behind it. A link resolves inside its own base and nowhere else; when the note exists in another base, the message names that base and says to write the path.
E02 not-indexed A file with no Search for line, so the catalogue builds no entry for it and it scores zero on every question.
E04 bad-provenance provenance or stage carries a value outside the legal set. Provenance is human, agent or external; stage is raw, captured, distilled or derived.
E05 dead-citation A [src:KEY] with no record behind it in this base, or a key that is not ten characters of the alphabet. A citation is a pointer, so a key with nothing behind it is a claim with no source.
E06 uncited-source A source record no note points at. The bibliography is a projection of the pointers, so a record nothing cites appears in no bibliography. A superseded record is exempt, or correcting a source would start failing the moment the last note moved to the new key.
E08 bibliography A cited key with no bibliography line, or one that has drifted from the record. The comparison is byte for byte against the line kb source add printed, and the message quotes the line the record renders.
E09 source-record A file under sources/ that is not a source record. Without it a dropped file blames the notes for a defect that is in the folder.
W01 ambiguous-link A [[link]] matching more than one file.
W03 dash An em dash or an en dash, which house style forbids. One report per line.
W04 front-matter A note that cites a source and carries no evidence_tier, or no valid_for. It used to ask only whether the front matter declared a source or type key, which a writer adds or does not, so a base that declared neither was never graded; it now also asks a question about the body, namely whether the note cites anything, by [src:KEY] pointer or in the hand written prose paragraph that predates pointers.
W05 no-provenance Front matter with no provenance or no stage, so who wrote the note is unknown.
W06 thin-keywords A Search for line under twelve terms. The message asks for thirty, in both languages, including the words somebody types from inside the problem.
W07 dead-key A key that reduces to one word after stopwords, so it reaches neither the keyword index nor the phrase index and no question can find it.
W08 unignored A .gitignore is here and does not cover a folder the base declares private. A warning and never a refusal, because git is optional; skipped when the base is private as a whole.
W09 superseded A citation of a source another record replaces. The message names the replacement. A warning and not an error, because citing what you actually read is legitimate and the old key still resolves.
The fifteen codes the linter emits, read from its source at commit 645bd0a on 8 September 2026. E03, E07 and W02 are not among them.

Note. Three codes are missing from the sequence, and all three were deleted rather than left to print an error nobody could act on. E03 demanded a map file at the root, which was right while the index was built from map entries; since a note carries its own keys, a base with no map indexes perfectly well. E07 and W02 both graded a map entry, one for a shape the router's parser could not read and one for a missing Search for line, and both stopped meaning anything when the router began reading each note's own header instead of the map. W02 graded 391 lines across the fleet's fifteen bases on 7 September 2026 and reported nothing, which is what a check on a redundant copy looks like.

What the checks skip

E02, W06 and W07 are asked of every file the index walks, not only of the knowledge folder, and they skip the files nobody searches for: README.md, MAP.md, AGENTS.md, CLAUDE.md, what-goes-here.md, MOVED.md, and anything under inbox/, records/ or a calculation-log/ folder. The deposit is exempt from carrying the line, not hidden by it: since ADR-0034 a passage from inbox/ is served at every surface with the short memory label on, and the exemption only keeps E02 quiet about a line a dropped file is not supposed to have. A daily record keyed like a note would collapse the weight of every word it shares with the notes that answer questions. That exemption is one predicate and it lives beside the walk rather than in the linter, because two callers read it: kb check reports the files it does not cover as E02, and kb index counts the same set as unreachable. Measured on the fleet this was written against, the exemption is nearly the whole answer, 63 keyless files and every one of them exempt, so a count that tested only for a missing Search for line would have printed 63 beside a linter reporting 0 and left a reader no way to tell which of the two was lying.

Links inside fenced code blocks and inline code are ignored, because a base that documents its own link convention writes [[file-name]] in backticks and those are examples rather than references. The templates/ folder is exempt from link checks for the same reason: its links are placeholders. W04, W05 and E04 are asked only of a note in the knowledge folder that opens with front matter; a note with none gets no finding from them.

The linter does not judge whether a note is any good, and it has no notion of staleness yet: valid_for is required on a sourced note and nothing compares it to reality.

The files the tool reads and writes

Every file is plain text, and every file is where the folder is: nothing under a fleet carries an absolute path, so moving the folder moves everything.

Beside the fleet manifest

fleet.txt
The fleet's manifest, at the fleet root, as key = value lines with # for comments. name = and role = are what kb fleet prints for the fleet itself. classifier =, answerer =, promoter = and reviewer = each name a command that reads a prompt on stdin and answers on stdout, its path relative to the fleet root: the first chooses the agent at boot, the answerer writes prose for kb answer, the promoter proposes notes from the deposit and the reviewer decides on them. Each line is optional and none is guessed at: without the first, kb boot keeps the arithmetic's choice; without an answerer, kb answer prints the reading list; without a promoter or a reviewer, kb promote stops and names the missing line. attach = adds a base outside the fleet root; disable = names a child directory that is not a base.
kb-misses.txt
The recall loss log, beside fleet.txt rather than in .kb/, because the index is disposable and this is not. One line per distinct question the catalogue could not answer: count, first seen, last seen, then the question, with an indented looked like: line recording what the base offered back. Gitignored, because a question can be as private as an answer. KB_MISSES_PATH names a file, not a directory, and moves the log to a writable place; two fleets pointed at one path share one log. kb misses reads it back and adds the half the file cannot hold: which files today's index nearly caught each question with, and the keys those files declare.
kb-rejections.txt
What kb promote refused, counted, with the reason and the dates, beside kb-misses.txt and for the same reason. The same proposal refused three times is a gap in the base rather than a bad proposal. Gitignored, because a refusal quotes the note it refused.
.kb-promote.lock
The marker one promotion run holds so that another does not start over the same deposit. It exists for the minutes a run takes; --lock refuses to start while it is held. It survives a crash: a run within the hour refuses, and a later one takes the marker over and says which run died holding it.

Inside an agent

agent.txt
The card the catalogue reads to name and route. name = and role =; private =, one line of folders separated by commas, a trailing slash optional, . for the whole base, and the default of profile/, projects/ and records/ when the line is absent; ends =, where this agent stops, read by the command that chooses the agent, because a roster of roles says what each agent does and never what none of them does. A base with no agent.txt is read and can never be elected, which is what makes the person's base a base and not an agent.
blocks.txt
The constitution as labelled blocks, in stability order, most stable first, because prefix caching reuses a prompt only up to the first token that differs. A block is a [label] line followed by one file per line; a block is resident unless its label line ends in on-demand. The skeleton carries [identity], [user] pointing at ../person/core.md, [map], and [session] on-demand.
kb-aliases.txt
Optional, at the base root: alias = canonical, one per line, # for comments. Expansion is additive, so the original words always survive and a wrong line can add noise and never remove signal. A line is added after a real question missed, never before.
MAP.md
A reading list for a person, and a resident constitution block. Optional to kb check and to the index, and never read by the catalogue when it routes; required by kb write, which refuses when the agent has none.
inbox/
The deposit, the short memory. Files a person drops, and one inbox/<date>-session-<id>.md per captured session, with provenance: agent, stage: raw and no Search for line. Served and labelled short at every surface; read by kb promote.
sources/
One <KEY>.txt per source the base has read, written by kb source add and never by hand, as key = value lines. Required on every record: type, title, retrieved_on, retrieval_status. Optional and typed: author, which repeats, then year, container, volume, pages, publisher, url, doi, isbn, lang, replaces, note. One file per source and never one shared ledger, because two sessions appending to one ledger conflict on every busy day; .txt and not .md, because a record is structured data with no prose and as markdown it would join the chunk index and be graded for a keyword line it has no use for. The path is derived from the key and never the reverse. Public by default: a bibliography is the part of a base that is safest to publish, and a base that names it on its private = line is honoured. Explained on Citing What You Read.

The derived files

Everything under .kb/ is derived and disposable. Delete it and you have lost a rebuild, not a fact.

.kb/index.db
One SQLite file per base, and there is no flag to point it anywhere else. Three tables, readable with any sqlite3 binary.
CREATE TABLE IF NOT EXISTS files (
    base        TEXT NOT NULL,
    path        TEXT NOT NULL,
    hash        TEXT NOT NULL,
    provenance  TEXT,
    stage       TEXT,
    captured_from TEXT,
    private     INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (base, path)
);

CREATE TABLE IF NOT EXISTS sources (
    base             TEXT NOT NULL,
    key              TEXT NOT NULL,
    type             TEXT NOT NULL,
    title            TEXT NOT NULL,
    author           TEXT,
    year             TEXT,
    container        TEXT,
    url              TEXT,
    doi              TEXT,
    isbn             TEXT,
    retrieved_on     TEXT NOT NULL,
    retrieval_status TEXT NOT NULL,
    replaces         TEXT,
    PRIMARY KEY (base, key)
);

CREATE VIRTUAL TABLE IF NOT EXISTS chunks USING fts5(
    base         UNINDEXED,
    path         UNINDEXED,
    heading_path,
    text,
    tokenize = 'unicode61 remove_diacritics 2'
);

hash is a content hash, so an unchanged file is skipped on the next index. private is 1 for a file inside the base's declared private layer and 0 otherwise, two states and never a third. captured_from names the deposit a promoted note was distilled from and is NULL for anything a person wrote by hand, which is the ordinary case and not a defect; it travels as a column rather than as front matter because front matter is not indexed, so a provenance line written there is invisible to the scorer no matter how well it is worded. An index built before either column existed is emptied on open, because sync skips any file whose hash it already knows and a column added to a live index would stay NULL forever. That is the one thing to know when upgrading a deployed copy: the first command that opens the index empties it, and only kb index fills it again, so run that immediately after replacing the binary and before anything reads. The tokenizer folds accents, which is what lets a Portuguese question find an English note. sources is rewritten in full on every sync from the records on disk, so it needed no migration and has no stale row to migrate around; it answers "have we read this already", which nothing could be asked before.

.kb/sessions/<id>
The agent last routed in a session, so kb boot emits the constitution only when it changes. Losing it costs one extra injection.
.kb/sessions/<id>.events
What kb boot appends on every message: a refused line with the question and the vocabulary offered back, or a routed line with the agent. kb capture reads it, writes the deposit, and deletes it only after the deposit is on disk, so a crash between the two captures again rather than losing the session.

The header of a note

Two labelled lines directly under the title, before the body. The first is the label the catalogue matches; the second is the one line the model that chooses the agent is shown in place of the file. The front matter above them is what W04, W05 and E04 read.

---
provenance: agent
stage: derived
---

# The title

**Search for:** `first key`, `second key`, `a phrase a real question uses`

**Exists to:** say in one line what this file is for.

Both labels are read tolerantly: case does not matter, bold or plain, comma or semicolon, backticks optional, and the Portuguese Buscar por: and Existe para: are accepted. Only what sits above the first section heading is read, which is what keeps MAP.md, with one label per entry, from indexing itself as a file holding every key in the base. A line excludes itself from the text index; a heading would exclude everything after it, which is why the label is a line and not a section.

Previous: Somewhere That Is Not Your Machine · Next: What Was Done · All of the docs · Back to the library