What Is Next
Last updated: 2 September 2026
How to read this page
Four statuses and no dates. An item is waiting for a measurement, proposed, not planned, or decided and kept possible, and the status leads its line below. Waiting means a decision record names the measurement or the event, and the work starts when it arrives. Proposed means a record names it as the next move and what it needs is a decision, not a number. Not planned means it was decided against, on a measurement or an argument, and the record says what would reopen it. Kept possible means it was deliberately left buildable and deliberately not built, with the event that would build it.
The work in front of us commits to priority, never to a date. A date says when somebody hoped to be finished; a trigger says what has to be true first, which is the only kind of promise a page like this can keep. Every item names where its trigger comes from: a decision record, by number, because the house rule is two options and their consequences, or it is a preference and not a decision; or, for the few items no record covers, the report or README that carries them. Where a record names no trigger, the line says so. The records are listed one line each on What Was Done.
A number here carries its platform, its corpus and its date in the sentence that quotes it. Several of the triggers are measurements only the authors' own fleet can produce, and that fleet is private; where a figure came from it, the line says so, and a reader cannot reproduce it. What was read and not run is marked as read.
The work in front of us
Which of your books a question should open is a lookup, and code does that with no model. Which of your agents should answer is a judgement about a subject, and a model does that one, through a command you name. Most of what is in front of us is about the cost of that second step, about the hook that runs it on every message, and about the floor the catalogue measures every answer against.
- A local model for the agent choice
- Proposed. The one step that calls a model is the choice of which agent answers, and it takes seconds; ADR-0027 states the cost rather than hiding it, and names a local model as the obvious next move because it collapses the latency argument. The fallback already exists: with no model configured, or a failing one, the deterministic choice runs and the fleet never stops routing. Local model routing is not built. What it needs is a local model on the machine and the owner's decision to run one, not a measurement.
- A resident server for the boot hook
-
Waiting. The hook that tells a session which agent
it is opens every base on every message. Isolated by experiment on
the release binary on 2026-08-18, with five bases of the authors'
fleet open: about 355 ms per message, of which about 60 ms is process
start, about 280 ms is opening the five bases, and about 5 ms is the
routing itself. The trigger in ADR-0022 is 355 ms becoming annoying,
at which point
kb serveholds the open bases and the hook talks to it instead of opening them again. Part of the 280 ms was one git subprocess per base, and git left the open on 2026-09-01 under ADR-0034; the figure the trigger names is the one from before that. - Batch mode for
kb route -
Proposed, as finding F-08 of the first integration,
the one finding of ten still open. On the first integrator's machine
the process cost more than the search: about 6 ms to spawn against
2.4 ms of retrieval, on WSL2 on x86-64 with a 9 entry base on
2026-08-29, not reproduced here.
kb servespeaks MCP over stdio, which does not fit a stateless HTTP handler, so a serverless caller has spawn and nothing else. Many questions in one invocation and one JSON line per answer is the cheap version of a server. The report of 2026-08-30 calls it a decision rather than a task, and says it blocks nobody today. - A model reading the transcript at session end
-
Waiting. Today
kb capturewrites what the session already measured into the agent's short memory, with no model: the questions the library refused, and the agent that was booted. Option B in ADR-0035, a small model reading the transcript onSessionEndand writing candidate facts in a second file beside that one, waits for the first measured junk rate fromkb promoteover deposits that capture wrote. That number decides whether it is switched on and with what--max. - The self-limiting threshold for automatic promotion
-
Waiting. A promotion run that writes more junk than
some threshold should stop and wait for a person. ADR-0030 leaves
the number unchosen, because choosing it from zero runs would be
inventing it, so it waits for runs to count; ADR-0034 adds that the
measure has to stop assuming git: count the files promotion wrote at
stage: capturedand their later deletions, rather than a diff. Both records name it as unbuilt. - Whether a guess is a recall loss
-
Waiting. Today it is not: a guess was served, with
its warning, and a question that reached the caller is not one the
library failed to reach. Moving that line changes what
kb-misses.txtcounts, and two live revisit triggers are measured against that file, so the line moves on a measurement and not on an argument. A test pins the current answer so that changing it has to be deliberate; the report of 2026-08-30 lists this as one of two decisions left open on purpose. - The floor and the gate on a fleet near a thousand entries
-
Waiting. Since 2026-09-02 the confidence floor
scales with the corpus: it is 62 percent of what one key found in
exactly one book scores, which is 17.5 at 226 entries, where it was
measured, and 6.9 at eleven. The largest fleet it was calibrated on
is the authors' own at 226 entries; above that the table in ADR-0036
is arithmetic, because no fleet of a thousand entries exists there
yet, and the arithmetic says that at a thousand entries the old
fixed floor would have let a word in fifty files clear it alone. The
trigger is the first fleet
near a thousand entries.
kb evalon it is the instrument, andgate.floorin every JSON reply says which floor applied. MIN_ENTRIES_TO_ROUTEat 2- Waiting. A fleet of one entry never routes above a guess, because with one book every key weighs the same and rarity cannot tell anything apart. Two is the structural minimum, the first size at which a word in both books weighs less than a word in one, and the sweep after the change, over the demo corpus on 2026-09-02, routed every gold question to the right file from two entries up. A larger minimum was the owner's instinct and the data did not give one, so the constant carries a trigger instead of a guess: the first fleet that misroutes at two to four entries (ADR-0036).
- The Linux spawn figure, with our own toolchain
-
Waiting. Spawning
kb route --json, opening the fleet and answering costs p50 184.8 ms on a Windows laptop (release build,examples/demo, 40 samples after 3 warm-ups, 2026-08-30) and p50 9.6 ms on Linux (WSL2 on x86-64, a 9 entry base, 40 executions, 2026-08-29), about twentyfold apart. The Linux row is the first integrator's measurement and has not been reproduced here: WSL on the project machine has no Rust toolchain, and installing one is a change to somebody's machine that was not asked for. Both READMEs and the report of 2026-08-30 mark the row as somebody else's until it is; no decision record covers it. - A genuinely read-only mount
-
Waiting. The index is built before a deploy and only
read at runtime; the one file a query writes is the miss log, and a
failed write there prints on stderr, returns the reason in
miss.errorand does not fail the query. That was run on 2026-08-30 against a log path that could not be written, and not against a read-only mount. The tool's own README and the report of 2026-08-30 say so beside the claim, and the sentence stays marked until somebody runs it on one; no decision record covers it. MAP.mdleaving the resident set- Waiting. The map is a reading list for people, and the catalogue does not consult it; the keys it matches are the Search for line each book carries. It is still a resident block in the constitution, and ADR-0013 blocks taking it out until the expansion step exists and the gap against a model reading the whole map is under two questions in twenty. The orthographic half of that step shipped in ADR-0015, the trigram suggestions a miss returns; the semantic half is the model already in the loop. The record's other trigger is the map passing roughly 300 entries, where a resident map costs what was measured for a native memory that reads everything.
Decided against, and what would reopen it
Each of these was measured or argued and rejected, and each record says what would put it back on the list. A decision against is not a door nailed shut; it is a door with the key named on it.
- A dense scorer, or embeddings in the catalogue
- Not planned. BGE-M3 was measured against twenty real questions on 2026-08-17, on the authors' laptop with the CPU only, over 121 files and 1,039 chunks: 13 right against roughly 16 for the keyword scorer, and fusing the two moved one correct answer onto a wrong file and turned an honest abstention into a confident wrong answer. It cost 2.2 GB and 2,833 seconds to index the 1,039 chunks. The next day, on the same laptop over 122 files and 1,529 chunks, two cross-encoder rerankers degraded the ranking they were handed, 16 correct of 19 to 11 and to 14, at a median of 2,571 ms and 647 ms per pair (ADR-0018). The finding that outlives both: the keyword scorer's score separates a hit from a miss and no model's did, and that separation is what lets the librarian say she does not have the book. The measurement's own stated weakness is that the question set was tuned against the same day, which flatters keywords. Reopens when a base passes roughly a thousand files, when a question set nobody tuned against shows the keyword scorer below the dense one, or when the alias table passes a few hundred entries (ADR-0006). ADR-0018 adds one more: a model under 1 GB, under an Apache or MIT licence, that shows hit and miss separation on somebody else's benchmark.
- A local model expanding a missed question
-
Not planned. When the library has nothing, the miss
hands back the library's own vocabulary by character trigram
overlap, in microseconds with no dependency: it reaches a typo or a
cognate and never a translation, and the reply says which. A local
model doing the semantic half would cost 3.4 to 7.2 seconds for a
20 to 40 token expansion: arithmetic, not a run, from generation
measured earlier at 5.55 to 5.88 tokens per second on the authors'
laptop with no GPU, against 4.43 ms measured for the whole of
kb_retrieve. No local model was installed on that machine when ADR-0015 was written on 2026-08-17, and the cost would be paid on the path where the question already failed. Reopens when the miss log shows suggestions failing to help on most misses; ADR-0015 also names the vocabulary passing a few thousand terms (849 on that day), and the suggestion floor producing a false positive or a false negative on a real question. - A macOS binary
-
Reopened and shipped, in
kb-v0.2.2on 6 September 2026. It was not planned because nobody here runs one, so a published artifact would have been one nothing had ever executed, which is worth less than its absence. The reopening this entry named was somebody who runs one asking. That is not what happened: the release workflow was rebuilt for other reasons, and once it could execute an artifact on a runner of the artifact's own architecture, the objection had an answer and the cost of holding the line was higher than the cost of crossing it.kb-macos-x64is built on an Intel runner andkb-macos-arm64on an Apple silicon one, each printing its version and linting the demo fleet where it will actually run. Cross compiling both from one runner would have reinstated the original objection, because the hosted Apple silicon images carry no Rosetta and the x64 artifact could not have been executed at all. - A per-consumer serving scope
-
Not planned. A base declares its own private layer
in one line of
agent.txt, defaulting toprofile/, projects/, records/, and--allincludes that layer and means nothing else (ADR-0034). A third scope, a hosted agent that may readprojects/but notprofile/, is a scope per consumer rather than a declaration per base, and the record does not provide it. Reopens when the second real consumer asks, not before. - A lease layer for two sessions in one file
-
Not planned.
kb commitcloses the race in git's index by building a commit from only the paths named (ADR-0021). Two sessions editing the same file still clobber each other, and no git technique fixes that, because the race is at the filesystem before git sees anything. A claim or lease layer would; it costs every agent a protocol step, needs expiry handling for crashed sessions, and the failure it prevents had not happened once when the record was written. Reopens on the first real occurrence. - Query planning below the residual median on a large base
- Not planned. On a base of 57,486 chunks, the LongMemEval-V2 enterprise haystack of 221 MB measured on 2026-08-26, the text scorer's match walk is the cost: a word like list alone matches 19,530 chunks. Pruning terms found in more than a quarter of the corpus took the p95 from 1,772 ms to 1,264 ms on identical 19-question sets and cost 65 ms at the median, leaving a median around 300 ms (ADR-0033). The record names the corpus and the date and not the machine; the V2 evidence instrument run that day names an 11th Gen Intel i5-1135G7 with 16 GB on Windows 11. The fix below that median is query planning work, term-at-a-time scoring or an FTS content strategy that shrinks doclists, and the record says it is deliberately not attempted. It names no trigger. Below 10,000 chunks nothing changes at all, and every personal base measured so far sits far under that.
Kept possible, not built
These were left buildable on purpose. The mechanism in every case is the same: nothing was built for them, and nothing was built that would contradict them, which is cheaper than half building them now.
- A hosted, multi-tenant service
-
Kept possible. Ulpia is built for exactly one
self-hosted user: no accounts, no tenancy, no telemetry,
configuration as a file in the base (ADR-0008). The order is what
keeps a service possible: a directory now and a database later is an
afternoon, a database now and files later is an export and an
apology. Three things are done for the single user that a service
would also need, the base addressed by path and never assumed,
nothing derived ever the source of truth, and
kb initcreating a base from a spec. The record's position is that a paid product would be convenience, not capability. The event that changes it is the first paying user, which turns a plan into a system with decisions of its own. There is no hosted instance today and none to point at. - Voice
-
Kept possible. Not built. The seam ADR-0009 records
is two things. The contract's message content is a list of typed
parts from its first version, text, audio and image, while only text
is implemented; the MCP reply in
mcp.rscarries that shape today. Speech in and out behind one trait with a single empty implementation is the second, and the record describes it while the source in this repository holds no such trait yet. Adding a variant to a list is additive; turning a string into a list later breaks every client, which is why the list came first. The trigger the record names is voice moving from a second phase to now, which is when the seam gets tested rather than assumed. - The phone
- Kept possible. The desk in the reading room streams over SSE with typed events and sequence numbers, on loopback (ADR-0023). Reaching it from a phone is four decisions deferred together, and two of them are not engineering. A LAN bind is a new exposure class and needs a token presented on every request. An installable app needs a secure context, which plain LAN HTTP never grants: a page, never a service worker. What the phone may do is a choice, because a reachable desk can spend the owner's plan from any device holding the token. And the reliability tier, replay and idempotency keys, stops being scaffolding on phone Wi-Fi, where streams drop as a matter of course. The trigger is the owner asking for it again and sitting through the four in one session.
- A second human on one fleet
-
Kept possible. Everything assumes one operator. The
person is one base every agent reads and never an agent that
answers (ADR-0024); a second human makes that base a directory of
people and the
[user]block a per-session choice rather than a constant. A second human who does not read the languages the fleet is asked in today also reopens the language decision (ADR-0006). - Bulk import
- Kept possible. A book and the label that makes it findable are one write, and there is no flag that does one without the other (ADR-0016). That forecloses importing notes without deciding how each will be found, and the record calls the foreclosure deliberate. It reopens when a real import arrives, a hundred notes at once from another system, at which point the question is whether an import is the same operation as a write, and the record's answer is probably no.
kb guard-
Kept possible. The pre-commit hook that refuses a
raw
git commitis tracked in.githooks/and still needs onegit config core.hooksPath .githooksper repository, because git will not let a repository configure its own hook path from tracked content, and that is a security feature. Two copies of the hook exist, one per repository, accepted at two (ADR-0021). The trigger is a third repository, at which point akb guardcommand, which does not exist today, should write the hook and set the config in one step. - A checkable cross-base link
-
Kept possible. A wikilink resolves inside its own
base and nowhere else, in the linter and in the reading room alike,
because a base is a privacy boundary and a link that silently
crossed one is how private material lands in a public file
(ADR-0026). Crossing a base is written out as a path, and a path in
prose is not checked. A syntax the linter could validate, such as
[[base:note]], is worth building the first time a written path goes stale without anything noticing.
The launch
The source is public at github.com/richard-wollyce/ulpia, under Apache 2.0, with LICENSE and NOTICE at the root, and the latest release carries the Linux and Windows binaries, each with its checksum. No date for anything else on this page: it commits to priority, and a date would be the one item on it that is a date.
What is released, and in what order, was decided before most of this page existed. ADR-0008 builds for one self-hosted user and puts that release before any hosted service, keeping the service possible without building it, and the reason the paid product would be convenience and not capability is that the capability is the part being released.
Previous: What Was Done · Next: Benchmarks · All of the docs · Back to the library