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,guessornothing.--hybridfuses the keyword ranking with full text search over the chunks;--jsonprints the whole answer as one line on stdout and always fuses, so--hybridadds 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
--helpwhen the page was written, and it has one now. It runsroute, 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 byanswerer =infleet.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;--expandedreads up to twelve;--completereads the whole base in batches of ten and prints its estimate before the first call. It never reaches the model on anothingverdict, and without ananswererline it prints the reading listroutewould 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,UPDATEorNOOP, with the overlapping passage as evidence. The measure is containment, the fraction of the claim's words already present in one chunk:NOOPat 0.90 or above,UPDATEat 0.55 or above,ADDbelow. It writes nothing, and it never proposesDELETE, 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.--folderdefaults toknowledge,--provenancetoagentand--stagetoderived. It refuses without keys, and there is no flag to skip them; it also refuses when the agent has noMAP.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,1andOremoved, drawn from the operating system's generator, and the record lands atsources/<KEY>.txt. It then prints the one bibliography line a citing note has to carry, whichkb checklater compares byte for byte, so the reference list is a projection of the pointers rather than a second telling of them.--typeis one ofbook,chapter,article,report,webpage,recording;--retrieval-statusis one offull,partial,metadata,unreachable, and it cannot be skipped, because a source nobody opened has to say so out loud.--replaces KEYsupersedes 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.
--jsonemits CSL JSON, the shape every reference processor already reads, withretrieved_onunderaccessedandretrieval_statusandreplacesundercustom. 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 declaressources/private unless--allis 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 stagecaptured, and a refusal is counted inkb-rejections.txt.--dry-rundecides everything and writes nothing;--max Nstops after N proposals are admitted and leaves the rest of the deposit where it is;--lockrefuses 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
--strictis 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.dbfrom the markdown, content hashed so that an unchanged file costs nothing on the next run.--jsonprints 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 carriesunreachable, 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, andunsearchable 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
routescores it against the floor and comes back as a guess, which is the confusion this verb exists to end.--baseis one agent by its directory name;--foldera directory and everything under it;--kindthe species read from the folder,memory,skillsortools;--stageone ofraw,captured,distilledorderived; and--provenanceone ofhuman,agentorexternal. 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 lastkb indexsaw 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 everyREADME.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.
--classifyasks the command that chooses the agent, named byclassifier =infleet.txt, about every question, one model call each.--triadcomputes 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
UserPromptSubmithook 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.txtand reports each block's mode, files, bytes and tokens, then what changing each one costs in tokens that have to be prefilled again.--emitprints 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.txtand from eachagent.txt; it is the same textkb_fleetreturns 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.--personwrites the person's base, which has noagent.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_fleetandkb_list, all through the sameMemorycontract 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--allsays so. kb ui [path]... [--port N] [--all]-
The reading room: a local page over the same contract. It binds
127.0.0.1and nothing else, on port4114unless--portsays 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--sessionor from the hook payload on stdin, the same JSONbootreads. 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 conclusionssection 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.txtback: 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 of0.00is 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 Nbounds the candidates carried per logged question, not how many questions are printed. It always prints the path it read, becauseKB_MISSES_PATHcan 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.--applyexists only together with--gold <tsv>, and the pairing is not ergonomic sugar: every argument for letting a machine write intokb-aliases.txtrests 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--goldit 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 samepromotethe nightly sweep runs, and then destroys the source.--agentnames the owner instead of letting the router pick,--keepleaves the document alone, and--dry-rundoes 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.txtand prices the round in tokens before it is spent.--fromrecords what one reviewer returned, silence included, as an objection, a--nothingor 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.--resolveaccounts for each objection as--taken,--refusedwith a reason, or--escalated.--ledgerreads 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 Ncontrols how many past sessions to gather (default 5);--dry-runevaluates 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 saverecords the active task, architectural decisions, blockers, references, and next steps into.kb/sessions/<session>.handoff.md.kb handoff claim <agent>andkb handoff completeenforce 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. Runningkb handoffreads the latest handoff record back so the incoming agent or session resumes with full context and zero memory loss. kb --version, alsokb -Vandkb 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 fromKB_BUILD_SHA, which the release workflow sets to the commit it is building, and a build with none saysunknownrather 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.gitand 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,serveandmissesall read it. Onmissesit 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 inagent.txt, orprofile/,projects/andrecords/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,guessornothing: the top file's keyword score against the floor for this fleet's size, and neverhitin a base with fewer than two entries. The margin over the runner-up is reported underconfidenceand 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 whatgateis for. keyword_top-
The file the keyword scorer ranked first, as
base/path, ornullwhen it ranked nothing. indexed.entriesindexed.agentsindexed.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.filesunreachable.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 populationkb checkreports as E02, and nobody is required to runkb check. unreachable.unindexedunreachable.unindexed_paths-
A different problem with a different repair: files on disk the
indexes hold no chunks for, which is a
kb indexthat has not run since they were written. Which scope produced the number matters and is not obvious: an index synced without--alland 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-
truewhen an index built before the private column existed was found and emptied on open. Runkb indexbefore 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_likecarries. 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:
falseexactly when the verdict isnothing. The rule stated as a field, so a caller does not have to re-derive it from a string. gate.ranked_by_text_only-
truewhen the answer was refused andresultsis 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.falsewith an emptyresultsmeans nothing ranked anywhere. gate.floor-
What
confidence.keyword_scorewas 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.
nullwhen 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.
falsemeans the caller holds the only copy. miss.error-
Why the write failed, when it did, and
nullotherwise. 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[].baseresults[].path- The agent and the path inside it.
results[].memory-
shortorlong. Short is the deposit,inbox/: recent, unjudged, served on purpose and labelled on purpose. A caller that wants only settled knowledge filters onlong. results[].titleresults[].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[].textresults[].passages[].excerpt- The chunk, and a shorter cut of it.
results[].passages[].provenanceresults[].passages[].stage-
From the note's front matter, or
nullwhen 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,UPDATEorNOOP. There is no fourth value. reason- One sentence saying what the measure found.
evidence[]-
The closest chunks, each with its
base,pathandheading_path, anexcerpt, itscontainment(the fraction of the claim's words present in that chunk, rounded to six decimals), the wordssharedwith the claim and the words stillmissingfrom 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
DELETEis 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 }.questionis required;topis 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 fromfleet.txtand eachagent.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:requiredis an empty array and there is noquestionproperty, 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.limitdefaults 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 whenkb servewas 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. |
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 = valuelines with#for comments.name =androle =are whatkb fleetprints for the fleet itself.classifier =,answerer =,promoter =andreviewer =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 forkb 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 bootkeeps the arithmetic's choice; without an answerer,kb answerprints the reading list; without a promoter or a reviewer,kb promotestops 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.txtrather 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 indentedlooked like:line recording what the base offered back. Gitignored, because a question can be as private as an answer.KB_MISSES_PATHnames a file, not a directory, and moves the log to a writable place; two fleets pointed at one path share one log.kb missesreads 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 promoterefused, counted, with the reason and the dates, besidekb-misses.txtand 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;
--lockrefuses 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 =androle =;private =, one line of folders separated by commas, a trailing slash optional,.for the whole base, and the default ofprofile/,projects/andrecords/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 noagent.txtis 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 inon-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 checkand to the index, and never read by the catalogue when it routes; required bykb write, which refuses when the agent has none. inbox/-
The deposit, the short memory. Files a person drops, and one
inbox/<date>-session-<id>.mdper captured session, withprovenance: agent,stage: rawand no Search for line. Served and labelled short at every surface; read bykb promote. sources/-
One
<KEY>.txtper source the base has read, written bykb source addand never by hand, askey = valuelines. Required on every record:type,title,retrieved_on,retrieval_status. Optional and typed:author, which repeats, thenyear,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;.txtand 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 itsprivate =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
sqlite3binary.
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 bootemits the constitution only when it changes. Losing it costs one extra injection. .kb/sessions/<id>.events-
What
kb bootappends on every message: arefusedline with the question and the vocabulary offered back, or aroutedline with the agent.kb capturereads 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