What Was Done

Last updated: 2 September 2026

The Bibliotheca Ulpia was a reading room and a record office in one building, and this page is the record office. It holds two ledgers: what landed, by the day it landed, and what was decided, one line per record, with the reversals left in. It is written for the developer asking whether this is a weekend project or a system with a record, and whether the things it refused were measured or assumed.

How the record is kept

A change to the product lands as a decision first and is measured after. The rule is written where the numbers are: changing the product to chase a benchmark is the tuning the instruments exist to refuse, so a change is recorded as a product decision, with the mode it runs under declared in the run's own header, and only then run against the same questions as before. The records are numbered and dated, and the ledger's rule is that none is rewritten after acceptance: what changes later is added beneath the record as a dated amendment, which four of them carry, and a decision that is reversed gets a new record that supersedes the old one, which is why the reversals below are lines in the ledger and not deletions from it.

Each record carries the two options that were on the table and what each would have cost, because a recommendation with one option is a preference and not a decision. The ledger's header asks each to name the event that would reopen it, its revisit trigger, so that a decision is not held by habit; thirty-two of the thirty-six do, and 0012, 0030, 0032 and 0033 carry none. The floor's record, for one, waits for the first fleet that misroutes at two to four entries, for the first fleet near a thousand, and for its measured constant moving again.

A floor that was published stays published. The first LongMemEval-S run, every mode at its default, scored 49 percent under a local judge, and its hypotheses file remains beside the file of the run that declared the reading mode per question and scored 61 percent. The lower file is not withdrawn because a better one exists; it is the floor.

The records are deliberately not in the retrieval path. They bind every agent, so they live at the root of the repository rather than inside any one agent's folder, and they are read by people and by an agent that opens them on purpose. Attaching them to routing was tried and reversed, and the ledger's header says where the reason is kept: in the comment block of fleet.txt.

Four records that still read proposed

Four records carry the status proposed while what they describe has shipped, and the honest thing is to say so here rather than promote them quietly. ADR-0005, the agent wakes with its constitution and not with its library: the boot path landed on 2026-08-14 and kb blocks on 2026-08-15. ADR-0010, the memory as an MCP server: kb serve landed on 2026-08-16. ADR-0011, the fleet layout: kb init and one index per agent landed that same day. ADR-0020, the owner from intent and the reading from agreement: the split landed on 2026-08-18, and two later records changed parts of it without naming it, ADR-0027 moving the owner choice to a model and ADR-0036 replacing the fixed floor it gated on.

Three more read proposed for the ordinary reason. ADR-0004, local inference, and ADR-0023, the phone, describe work that is not built. ADR-0006 describes a cascade of which the alias table and the trigram step exist and the embedding step was measured and refused. ADR-0009 is accepted for the stack and proposed for the contract, and says so on its own status line.

Shipped, by day

Fifteen days carry a commit between 2026-08-14 and 2026-09-02, read from the repository's log on 2 September 2026 at commit 8285c1f; a day missing from the list is a day on which nothing landed in this repository. Beside each day are the hashes of the commits the line is drawn from, and each resolves in the log of the repository at github.com/richard-wollyce/ulpia, so a line can be checked against the commit it was drawn from.

2026-08-14
The linter and the router with no model in it, the same day. kb, the linter that makes the conventions real; the agent wakes with its constitution and not with its library; kb route, the router, with the place it breaks found; routing across languages with an alias table and IDF, no model needed. Commits 82ce813, 9884cdb, 5401661, 0f54c2a.
2026-08-15
The memory pipeline and the single user posture decided, and provenance landed; the SQLite index and hybrid retrieval, taking the first dependency; kb remember, which measures a claim and refuses to decide; kb blocks, which measures what the agent wakes up knowing. Commits c6b9c67, f543333, 3ade07c, 6c1701f.
2026-08-16
kb serve, the base as an MCP server; the private layer leak closed, and scope made a thing a query must state; kb made a library with the whole pipeline behind one type; the tray, linking the memory in as a library; kb init; one index per agent, with no way to point at the wrong one. Commits fda0b23, c62409d, 429562c, edf2ac0, b43a844, 441ae15.
2026-08-17
The restart: the public repository started with the agents structurally outside it; a miss answers with the vocabulary the base actually has; one language in the software and any language for the person; the agent shape published and made unable to drift. Commits d5fda73, ab54bdc, d02507d, 10e6392.
2026-08-18
kb write, a note and the entry that makes it reachable, or neither; tools/bench, the measuring instrument in Rust; the keyword score carried past fusion so each scorer does the job it is better at; the decision records moved to the root; the system named Ulpia, the librarian still Vesta, the agents in fleet/; commit by pathspec, because more than one session writes the tree; the front door, ulpia.io; the fleet boots the agent on a hook instead of asking the model to boot itself; the reading room, kb ui. Commits 56438ce, 9bb34b9, 622b2fa, ef5fdf9, 0890bae, eb5a186, 0f0564f, f51fe19, d22a9a7.
2026-08-19
The desk opens, and the reading room learns the library's own language; the person is one base every agent reads and never an agent that answers; a fleet declares its human, and the shape of that declaration is what ships; a wikilink stops at the edge of its base, in both surfaces; the site deployed to Cloudflare Pages; a model decides who answers, because choosing an agent was never arithmetic. Commits ff2672a, 38db595, b2abdb3, d824094, 779cab0, 6bbbe8d.
2026-08-20
A note carries its own keys, and the map stops deciding what exists; the person's base is person/; the README stops publishing numbers it cannot defend, and promotion gets its trigger, on SessionEnd; a deposit needs two readers. Commits 59b70f4, 68d83cc, c623069, 1218ac1.
2026-08-21
The page's privacy claim made to match the code, and the runbook made to match the account. Commit 3cfd1de.
2026-08-23
An agent's evidence has three species, and mass in one cannot outvote purpose in another; the tree made ready for release, with nothing personal left in it; the two benchmarks nobody else runs, the refusal and the speed. Commits d705197, c93e382, c0d2662.
2026-08-24
kb answer: the library learns to speak, and the refusal survives prose; the session-to-fleet converter, so LongMemEval runs against the product itself; LongMemEval-S, all 500 questions, with the refusal as the headline. Commits c3881d5, c6ba44e, e93063d.
2026-08-25
Three answer modes, because one default lied on aggregation; the declared-modes run, with the earlier floor kept published; Ulpia as a LongMemEval-V2 backend, the harness theirs and the memory ours; the benchmarks page. Commits 8e1019c, 80b8e01, 6d7206a, 4198189.
2026-08-26
The V2 adapter learns where to open the book, four instrumented rounds with every fix named; the text scorer prunes what cannot rank. Commits 757f141, 603ceda.
2026-08-28
route speaks JSON, the index says it has one home, and the Linux binary links no libc; the musl claim stops being a claim, because the binary ran on amazonlinux and on alpine. Commits cbd590d, f320903.
2026-09-01
Nine of the ten field-report findings closed, worked on 2026-08-30 and landed here: the gate speaks in the payload, one definition of a recall loss, and git leaves the runtime; the session feeds the deposit, and every surface counts its losses. Commits c071dd4, c8c3890.
2026-09-02
The floor scales with the corpus, and a corpus of one has no floor; the continuous integration run asserts the eval lines the front page quotes; the tool's README counts four MCP tools and drops the check that no longer exists. Commits ea43d1d, 4aba4de, 8285c1f.

The decision record, one line each

Thirty-six records, in number order. Each line is the ledger's own summary cut to a sentence or two, and the status and date are the record's own. Names of the people and agents who appear in the records are left off this page. Three records carry a suffix because a later record replaced part of them: 0012's system name by 0019, 0024's directory name by 0029, and 0025's runtime half by 0034. A number in a line was measured on the private fleet unless the line says otherwise, and a reader cannot re-run those; the demo corpus and the benchmark files are the reproducible half.

ADR-0001: an engineering agent is built on an earlier agent's three file split, extended for engineering
Accepted 2026-08-13. The split is extended with decisions/, records/sessions/, fleet/ and a staleness aware evidence ruler. Rejects a combined boot file, which grows without limit and is paid on every question, and rejects building a code first retrieval system before the manual procedure has proven its shape.
ADR-0002: the evidence ruler for claims about software
Accepted 2026-08-13. Why software needs its own ruler rather than adopting an earlier agent's unchanged, and the rule that matters most in it: a model's output, including the architect's own, is tier D until confirmed. The tier sits on the note with a per claim tag only when a claim diverges, and a recheck happens on a major version or on use, never on a calendar.
ADR-0003: files stay the source of truth, the index is derived
Accepted 2026-08-13. Answers whether the agents should move from markdown to a database, Neo4j specifically. Files stay the source of truth and any index is derived, rebuildable and disposable: the runtime reads files natively, a database turns every read into a tool call that can fail or be skipped, a graph is bad at storing long prose, the real pain is validation rather than retrieval, and the two options are not equally reversible.
ADR-0004: local first inference, and the role split it forces
Proposed 2026-08-13. Generation is memory bandwidth bound and prefill is compute bound, so on four cores with no GPU reading a 21,000 token boot path costs minutes of silence per question and no smaller model fixes it. The answer is to retrieve instead of read, and to split the jobs by what each model is good at. Its numbers are calculated, not measured, and the record says so on its status line.
ADR-0005: the agent wakes with its constitution, not with its library
Proposed 2026-08-13; the boot path shipped on 2026-08-14 and kb blocks on 2026-08-15. Prefix caching is real and costs three things: KV cache RAM at about 144 KB per token, an attention tax paid on every generated token, and silent recall failure. The constitution stays resident within a budget of 4,000 tokens; the library is indexed by code and retrieved per query; routing starts as a keyword lookup against the Search for lines with no model at all.
ADR-0006: one canonical language in the core, every language at the edge
Proposed 2026-08-13. Separates the core prose, the keys the router matches, and the edge conversation. Normalise at the boundary and keep one canonical representation in the core: English, per base, because the jargon is already English. The keys become multilingual by a cascade: free alias lookup, then local query expansion only on a miss, then embeddings only when keywords stop covering it.
ADR-0007: the memory pipeline, and provenance as a first class field
Accepted 2026-08-13. Four named outcomes on every write, ADD, UPDATE, DELETE and NOOP, where NOOP is the branch whose absence produced most of the junk in a published audit of a neighbouring product. The agent may delete, because a delete gate produces hoarding and git makes deletion visible and recoverable, so the constraint is disclosure. Provenance and stage are front matter, and an agent claim is never promoted to human without a human act.
ADR-0008: build for one self hosted user, keep the hosted service possible
Accepted 2026-08-13. Build for exactly one self hosted user, with no accounts, no tenancy, no telemetry and config as a file, and keep a paid hosted service possible without building any of it now: a directory now and a database later is an afternoon, while the reverse is an export and an apology. The paid product, if ever, is convenience and not capability.
ADR-0009: the GUI is a client of a contract, and the runtime is a choice inside it
Accepted 2026-08-16 for the stack, proposed for the contract. Tauri confirmed, but the boundary is the API contract and not the framework, which is what makes a hosted service a second implementation instead of a rewrite. Records that no third party application can hold a consumer subscription credential, and the three runtimes that follow from it. Voice deferred, with the seam built.
ADR-0010: the memory system ships as an MCP server, so the GUI stops being the only door
Proposed 2026-08-16; kb serve shipped the same day. Any MCP capable client can call the routing and the memory while the user's own subscription pays for the model, so the credential never comes near us. The server is confined to the base path it was launched with, because tool arguments arrive from a model. The write tool it named is not among the five tools the server publishes today.
ADR-0011: the fleet has one shape, and the library still accepts any path
Proposed 2026-08-16; kb init and one index per agent shipped the same day. The least reversible decision so far. Separates what the library accepts, any path, from where the product creates and looks, one defined place, because an orchestrator able to create an agent has to know where to put it. One index per agent, because a missing predicate cannot leak a file that is not in the database you opened, and no absolute path anywhere inside the fleet, so moving, backup and sync are one operation.
ADR-0012: the system is Vesta, and everything lives under a personal name
Accepted 2026-08-17; the system name and the no-new-domain position superseded by 0019. The system and its orchestrator were both called Vesta, shipping under the author's own name with one subdomain per system, because a civil name has no third party who can hold prior rights to it. A mark must be abstract and never literal, which kills Router, Hub, Conductor and Nexus on sight. Vesta is the Roman deity who had no statue, represented by the fire itself: the index is the fire, the markdown is the wills.
ADR-0013: the base is read before the model is allowed to decide it was not needed
Accepted 2026-08-17. Routing is a reflex rather than a decision, because it costs microseconds and reads no text, and the model classifies only after seeing what came back, because whether it needs the base is the one decision a model is worst at. Twenty real questions went 10 hits, 3 weak, 7 misses before repair and 17, 1, 2 after, with the fused scorer rescuing none of the seven; the record admits the second number is tuned against its own test set.
ADR-0014: the system is written in English, the conversation is not
Accepted 2026-08-17. One language for identifiers, comments, markdown, commits and interface copy; whatever the user types stays in the language they typed it. Beat the split rule because the cost of a split is not the translation but the boundary, and a rule that needs a judgement per string drifts, which it already had between two decisions four days apart.
ADR-0015: expansion is split by kind of distance, and the code half ships first
Accepted 2026-08-17. A typo or a cognate is orthographic distance: character trigram overlap against the keys the base already holds, in microseconds with no dependency, so a miss returns candidate vocabulary instead of a dead end. A translation is semantic distance and needs a model. Rejected building the local model version first on a measurement: at 5.55 to 5.88 tokens per second on the author's machine, a 20 to 40 token expansion costs 3.4 to 7.2 seconds against 4.43 ms for the whole of kb_retrieve, paid on the path where the question already failed.
ADR-0016: a note and the entry that makes it reachable are one write
Accepted 2026-08-17. There is no flag that does one without the other. Closes the bootstrap: the proposing half existed and nothing turned an approved proposal into a file, so the base could only grow by hand and did not. Rejects generating keys from the note text, because keys exist to bridge how somebody asks to how the file was written, and deriving them from the file guarantees the half already held.
ADR-0017: BGE-M3 measured and not adopted, because fusing it made the system worse
Accepted 2026-08-17. Head to head on twenty real questions it got 13 right against roughly 16 for the keyword scorer. Fused, nineteen answers were unchanged, one moved off the correct file, and an honest abstention became a confident wrong answer. The keyword scorer's one error scored 3.82 while every correct answer scored 9.55 or more, and BGE-M3's correct and wrong answers overlapped completely, so a dense scorer that never abstains removes the property the refusal depends on. Costs measured on the same machine: 2.2 GB, and 2,833 seconds to index 1,039 chunks.
ADR-0018: no model enters the retrieval path, and the score floor becomes the mechanism
Accepted 2026-08-18. The full table on a 19 question set: the keyword scorer 16 of 19 and the only scorer whose score separates hit from miss; both cross encoder rerankers degraded the ranking they were handed, BGE reranker v2-m3 to 11 at 2,571 ms per pair and Jina v2 to 14 at 647 ms per pair with a non-commercial licence. A skeptic commissioned against the reranker plan predicted zero marginal gain before the numbers existed, and the reality was negative.
ADR-0019: the system is Ulpia, and the fleet is the folder the agents live in
Accepted 2026-08-18. Supersedes 0012's system name and its no-new-domain position; Vesta stays the librarian. A rule that kills the literal words everyone converges on, a collision search that killed six of eight candidates, and the survivor is the Bibliotheca Ulpia, Trajan's library, which was Rome's public reading room and its record office in one building. Sealed by ulpia.io; agents/ becomes fleet/.
ADR-0020: Vesta chooses which agent answers, and each scorer does the job it was measured to be better at
Proposed 2026-08-18; the split shipped the same day. Fusion rewards agreement, so a file both scorers rank fourth beats a file one scorer ranks first, which is right for assembling passages a person reads and wrong for choosing one owner: the owner and the verdict come from intent, the reading from agreement. On the 19 question set the keyword scorer alone picked the right file 18 times and the right agent 16, the fusion 11 and 13, and a hardcoded agent 10. Carries a refuted prediction of its own author, that the runner-up margin should be the primary gate, killed by the instrument on its first run. The owner choice later went to a model, in 0027, and the fixed floor it gated on to a scaled one, in 0036.
ADR-0021: a commit names the paths it commits, and proves afterwards that it took nothing else
Accepted 2026-08-18. Written because one commit already carried two sessions' unrelated work under one message, staged by git add -A while the other session was mid write. git commit -- <paths> builds the commit from only those paths and ignores the rest of the index, so a pathspec closes the race instead of guarding it. kb commit refuses an empty list and a list spanning two repositories, and reads the commit back. Does not solve two sessions editing one file, and says so.
ADR-0022: the fleet boots the agent, the agent does not boot itself
Accepted 2026-08-18. A static conditional naming one agent literally meant a nutrition question still woke the architect. Replaced by kb boot on a UserPromptSubmit hook, the one surface in that runtime where the software speaks before the model does; it emits the constitution only when the routed agent changes, refuses to pick below the gate, and always exits 0 because exit 2 on that event erases the user's message. Cost 355 ms per message on five bases when isolated by experiment: 60 ms process start, 280 ms opening the bases, 5 ms routing.
ADR-0023: the desk streams over SSE with sequence numbers, and the phone is a decision, not a feature flag
Proposed 2026-08-19. Typed events and sequence numbers shipped; dedup and sync replay wait for a client that can actually lose messages, which a loopback browser cannot. The phone is four decisions: a LAN bind is a new exposure class, an installable app needs a secure context plain LAN HTTP never grants, a reachable desk can spend the plan from any device holding the token, and phone Wi-Fi is where replay stops being scaffolding. Deferred together.
ADR-0024: the person is one base every agent reads, and never an agent that answers
Accepted 2026-08-19; the directory name superseded by 0029. Written after the desk answered who am I with the base does not cover it, while the same human was recorded twice, in two languages, in two private folders. N copies of a person drift, and the gaps are invisible from inside any one agent, so the person is one base with a small resident core and retrieved domain files. It has no agent.txt, so the router reads it and can never elect it.
ADR-0025: a fleet must declare its human, and the shape of that declaration is the part that can be published
Accepted 2026-08-19; the runtime half superseded by 0034. Public: kb init --person, the committed person skeleton, and the structural rule. Private: the person's base, every word. kb init writes a [user] block into every agent it creates, so an agent cannot be born not knowing who it works for. Templates ship empty with questions in them, because an agent that does not know its human gives generic answers confidently. The general form: publish the shape, keep the content.
ADR-0026: a wikilink resolves inside its own base and nowhere else
Accepted 2026-08-19. The linter scoped links to one base while the reading room resolved fleet wide, so the same file was fine and broken at once. Privacy decided it, not tidiness: a base is where privacy is decided, and fleet wide resolution would make the audit that converted eight files' worth of private wikilinks permanent work. Crossing a base is written out as a path, and the linter names the other base in the error.
ADR-0027: a model decides who answers, because choosing an agent is classification
Accepted 2026-08-19. Retrieval is lexical and a keyword index answers it; who understands this subject is semantic, and no count of shared words answers it, which a subject nobody has written about proved: the word zero in zero downtime gave the marketing agent the whole field. The model gets a dossier of roster plus evidence, never the corpus, so it cannot invent a file, and coverage is a first class answer: covered, adjacent, uncovered. Carries a built and rejected cascade: gating the model on the deterministic score cut latency from 14 s to 1 s and routed a DevOps question to marketing in 971 ms, because a gate built on a blind signal inherits the blindness. The cost is stated and not hidden: 9 to 15 seconds per message through a model run as a command, and the record keeps its own correction, that the line read 13 to 16 until 2026-08-20 while the shipped path took 43 to 48, because that command inherited the fleet root as its working directory.
ADR-0028: a note carries its own keys, and the map stops deciding what exists
Accepted 2026-08-20. Written the day one file's formatting made seven of another agent's notes invisible to the router while kb check printed clean. Two labelled lines under the title. Not front matter: a delimited block has a way to not exist, and a file with none yielded zero findings. Not a Keywords heading: a section runs to the next heading and silently deleted the note's opening prose from the full text index, run with the header shape as the only variable. MAP.md is demoted to a reading list and a resident block, not deleted.
ADR-0029: the person's base is person/, because profile named three things
Accepted 2026-08-20. Supersedes only the directory name in 0024. profile named the shared base, a tombstone inside one agent and a private folder inside another, and the reading room already carried a special case written around the collision. The fleet refused to open when the emptied directory became a seventh base under 0028's predicate, which is the privacy gate working rather than failing.
ADR-0030: two promoters, and the second one is not a second opinion
Accepted 2026-08-20. The proposer reads the deposit and never the base; the reviewer reads the proposal plus what the deterministic router says the base already holds, and never the proposer's reasoning. Three lenses, unanimity to write, stage captured, and refusals counted, because a repeated refusal is a gap in the base. Exists against the measured failure of automatic extraction elsewhere: 97.8 percent junk.
ADR-0031: an agent's evidence has three species, and mass in one cannot outvote purpose in another
Accepted 2026-08-21. Memory is what an agent knows, skills are how it works and where it stops, tools are text declarations of what it may use. The fold takes the best file of each species and sums the species, which is the second answer to the volume problem, the first having been measured dead. Ingestion may only ever write memory, as a rule in code.
ADR-0032: the answerer sits after the verdict, and the refusal survives it
Accepted 2026-08-24, amended twice on 2026-08-25. kb answer runs retrieval first, deterministic and unchanged, and the model named by answerer = receives the question, the served passages and the gate's own evidence line under grounding rules; a nothing verdict never reaches the model. On arrival, the two medical baits the deterministic layer had answered confidently were both refused, 2 of 2, with Sonnet as the pen. Amended for three reading modes chosen by the caller and never guessed, and for a map that answers per file, which took extraction recall of gold evidence from 60 to 91 percent on the identical 30 questions.
ADR-0033: the text scorer prunes what cannot rank
Accepted 2026-08-26. Over a base of 57,486 chunks the FTS5 match walk was the cost: the word list alone matched 19,530 of them. A term in more than a quarter of the corpus is pruned from the text expression on bases above 10,000 chunks, and the keyword scorer still sees every word. Measured on identical 19 question sets against the unpruned binary: median 371 ms against 306 ms, p95 1264 ms against 1772 ms, half a second off the tail for 65 ms at the median. Below the threshold nothing changes and the demo eval is bit identical. The classic two phase fix was built and refuted, 524 ms against 246 ms, and the refutation lives as a comment on the query.
ADR-0034: git leaves the runtime, and a base declares its own private layer
Accepted 2026-09-01, implemented the same day; supersedes the runtime half of 0025. kb stops asking git what it may serve. The oracle refused folders without git init, hid every note until it was tracked, served nothing from a deployment bundle, shelled out once per base per question, and was bypassed by --all on every surface the owner actually used. A base declares its private layer in agent.txt, defaulting to profile/, projects/, records/; the deposit is served and every passage from it is labelled short memory. Git stays as an optional tool for whoever versions a fleet.
ADR-0035: the session feeds the deposit, and every surface counts its losses
Accepted 2026-09-01, implemented the same day. kb boot records what each message refused and where it was routed; kb capture turns that into a raw deposit in the last routed agent's inbox at session end, before promote reads it, with no model. The miss log holds a marker while it merges, which is what let the boot hook write to it under two sessions.
ADR-0036: the floor scales with the corpus, and a corpus of one has no floor
Accepted 2026-09-02, implemented and measured the same day. 17.5 was measured on 226 entries, and a matched key is worth 6 times its idf, which grows with the corpus, so the fixed floor meant three keys on four entries and half a key on a thousand. It is now 62 percent of one unique key at every size, 17.5 where it was measured, and a fleet of one entry never routes. Three instruments before and after: the calibration fleet unchanged to the decimal, the demo from 8 of 10 to 10 of 10 routed, and the abstention benchmark's refusal unchanged at 28 of 30 while its confident in-scope answers doubled from 6 to 12 of 20.
ADR-0041: the keys live in one place, and every door to a model filters
Accepted 2026-09-07. A note's keys live in the note and nowhere else, and every path by which base text reaches a model filters those lines at the door. W02 retired, and orientation files like MAP.md are exempt from indexing.
ADR-0042: a source is an object and a citation is a pointer
Accepted 2026-09-08. A source becomes a first-class object with an opaque ten-character key, a citation is a [src:KEY] pointer at it, and the bibliography is a byte-for-byte verified projection of the pointers.
ADR-0043: local reader offline optimization and 4-bit AWQ distillation profile
Accepted 2026-09-14. A local student model profile (Gemma-2 2B, Qwen 2.5 3B) and 4-bit Activation-aware Weight Quantization (AWQ) under a strict 2 GB RAM budget for standalone offline UI mode (Ulpia Tray), with an invariant of zero external API calls and zero background reader daemons when running inside an external agent harness.

ADR-0032 and ADR-0033 are absent from decisions/MAP.md, the ledger file the other lines are drawn from, checked by grep on 2 September 2026, so those two lines are drawn from the records themselves.

Measured and rejected

Six of the lines above are refusals. The reader asking whether they were measured or assumed gets the answer per line.

Neo4j, ADR-0003
Argued, not measured: the one refusal on this list that rests on reversibility and on what a graph is bad at storing, dated the day before this repository's first commit.
BGE-M3 as a dense scorer, ADR-0017
Measured on twenty real questions, head to head and then fused, and refused because the fusion turned an honest abstention into a confident wrong answer.
Two cross encoder rerankers, ADR-0018
Measured on a 19 question set, and both degraded the ranking they were handed, at 2,571 ms and 647 ms per pair.
A score gated cascade for the owner choice, ADR-0027
Built and run: it cut latency from 14 s to 1 s and routed a DevOps question to marketing in 971 ms, and no threshold rescues a gate built on a blind signal.
Front matter and a Keywords heading for the note header, ADR-0028
Both run with the header shape as the only variable: a broken front matter block yielded fewer findings than a whole one, and a heading section deleted the note's opening prose from the full text index.
Git in the runtime, ADR-0034
Five costs met in use rather than imagined, among them the first hosted integration passing --all for the wrong reason, to make anything work at all from a bundle with no .git.

The first integration and its ten findings

On 2026-08-29 the first production consumer sent back a field report. The shape: a chat agent inside a serverless function, an ephemeral instance with a read only filesystem, one execFile of kb route --json per question, the base and the binary shipped inside the deployment bundle. The binary was kb-v0.1.0 for x86_64-unknown-linux-musl at commit f320903; the base held 2 agents, 9 index entries, 11 text files, 148 KB. Six real questions, zero retrieval failures, the verdict gate obeyed, and the error contract used to warn a person on screen. The report itself is not in the repository, because it names a real deployment and real questions asked by real people; what the repository holds is the mechanism half, with file and line for every finding.

Ten findings came out of it. Nine were closed on 2026-08-30, each as a failing test first, then the change, then a run; the commit that carries them is dated 2026-09-01 in the log.

F-01, the gate speaks in the payload
route --json carries gate: { served, ranked_by_text_only, floor }, three facts rather than one enum. A caller branches on gate.served and never on the length of results, because a refused answer still carries its candidates.
F-02, one definition of a recall loss
Six surfaces had decided the contents of kb-misses.txt four different ways. Memory::recall_loss now decides, records and hands back the vocabulary in one call, and no surface holds its own opinion.
F-03, the miss travels in the JSON
A refusal carries a miss object with the question, the vocabulary, the date, the path it wrote to, and error when it could not, so a caller with nowhere to write can persist it whole. KB_MISSES_PATH moves the log. Run on 2026-08-30 against a path that could not be written: exit 0 and the reason in the payload. Not run against a genuinely read only mount, and the tool's README says so.
F-04, remember --json
The flag was parsed once for the whole process and dropped on this one arm. kb remember --json now emits the claim, the proposal, the reason, the ranked evidence and a notice carrying the caveat the terminal prints. All three outcomes ran against examples/demo on 2026-08-30: NOOP at containment 1.000, UPDATE at 0.625, ADD at 0.250.
F-05, refused and hedged counted apart
kb eval had counted anything that was not a hit as an abstention, so a guess that served two files scored as silence. The summary now carries abstention_refused, abstention_hedged and abstention_answered, with a test that they sum back to the denominator.
F-06, suggestions on refusal
suggestions was computed only when the fused list was empty, which is not the case it was built for. The condition moved from the list length to the verdict. The honesty property kept its own test: a base with no orthographic neighbour of any question word still offers nothing rather than the nearest thing it holds.
F-07, the index error names the cause
A bundle that left .kb/ behind got a permission error, true and misleading. A missing index now names the cause, then kb index, then the deployment half of it, with the underlying error kept in brackets.
F-09, both binaries documented
The report said only a Linux binary is published; the release workflow at the kb-v0.1.0 tag builds a Windows binary beside it, with its sha256. What was missing was the sentence in the tool's own README, which was made to name both and to state that there was no macOS build, with the reason: nobody here runs one. Overtaken on 6 September 2026. Four binaries are published now, macOS included, each one built and executed on a runner of its own architecture, which is the answer to the reason this finding recorded rather than a reversal of it.
F-10, spawn numbers with platform and date
The README had told integrators that spawning per request pays an in-process cold open figure every time. Measured on 2026-08-30, release build, examples/demo, 40 samples after 3 warm-ups, on the same Windows laptop as the latency benchmark: spawn, open and answer at p50 184.8 ms. On Linux the integrator measured 9.6 ms for the whole thing on WSL2, a figure the repository carries as theirs and has not reproduced.
F-08, open
Process start costs more than the search on a serverless caller, about 6 ms to spawn against 2.4 ms of retrieval on the integrator's machine, and there is no batch mode. kb serve speaks MCP over stdio, which does not fit a stateless HTTP handler. A batch mode is a decision and not a task, and it blocks nobody today.

Three defects turned up by running that reading had not found: kb answer recorded no recall loss at all, a fourth definition found while unifying the other three; the hybrid terminal path kept swallowing the record one level down after the first fix, with three tests passing over it; and the first timing of F-10 measured its own harness, PowerShell's Measure-Command at p50 298 ms, and was discarded.

Previous: Reference · Next: What Is Next · All of the docs · Back to the library