Rooms, halls, layers

Four axes, all optional, all one field each. Between them they answer: what is this about, what kind of thing is it, how long should it stick around — and, since this week, who put it there.

Room — which slice a recall searches

A room is a subject label on a fact, and a filter on retrieval. A write puts a fact in one room; a recall names the rooms it wants and gets nothing from the rooms it did not name. In SQL that is one predicate — AND room = ANY($n::text[]) — applied before ranking rather than after it.

What that predicate is worth, measured. Against a shared store of 5,882 turns, scoping a query to the right room recovers about half of what sharing the store cost in the first place — and on the lexical channel it beats a private store outright, because term statistics are a property of the corpus and a big corpus has better ones. It does not reach a private store's quality on our default configuration. The other half of the sentence is the one that matters more: a recall pointed at the wrong room scores exactly zero, in every configuration, on every question type. This filter does not degrade gracefully. The full ablation →

What a room is not, corrected. An earlier version of this page called a room a visibility scope and said an agent reads "its own room plus shared". That is not what the code does, and now that the source is public it is checkable, so: the engine's own type declaration calls room a topic classification; retrieval filters on whatever room list the caller passes; shared is not appended for you; and room: "agent" is assigned by matching words like agent, llm and ai in the fact's own text — so it holds facts about agents, and every agent writes into the same one. There is no per-caller identity in the room — identity lives on its own axis, described below.

So rooms give you selectivity, and the fleet property is real — one store, everybody writes into it, and 61.5% of the links in ours cross a room boundary. What they do not give you is isolation. This is deliberately not a permission system: no tiers, no policy engine, no trust levels, and — worth being blunt about — no enforcement either. If you need tenant isolation with audit guarantees, you want a governed platform and not this — we say which ones.

Author — the axis that is not the room

The correction above leaves an obvious hole: if a room is a subject label, where does identity live? As of this week, on its own axis. A retained fact records who wrote it, and a recall can filter on that independently of the room — so "what I know about deploys" is one call rather than a choice between two half-answers.

memory_recall({ query: "how do we deploy", room: ["deployment"], mine: true })

Keeping the two apart is the whole point. One string cannot be both a topic and an owner: file a deploy finding under the person who found it and nobody looking for deploys will see it; file identity into the topic and you lose the topic. Two fields, two filters, one query.

Coverage in our own store on 26 August 2026: 243 of 8,810 facts carry an author, first attributed write 24 August. It is not retroactive, and the backfill is only partly possible — the writer is not in the store and has to be reconstructed from the client's logs. Attribution also lives in our client layer today rather than in the packaged MCP server. Both are on the roadmap.

One side effect worth knowing, because it came free: attribution rides the engine's tag field, and the API already lists tags with usage counts and wildcard search. So GET /tags?q=author:* returns the roster of everyone who has ever written to a bank, with counts, today. There is no equivalent call for rooms — which is the next section.

A fact cannot change rooms — and what we decided to do about it

The room is assigned once, inside the write, and nothing in the engine revisits it. We went looking for a reassignment path — an endpoint, a background job, a query that updates the column — and there is none. The classifier runs exactly once per fact, during extraction.

Creating a room, by contrast, has always been free. room is a plain text column with no enum, no foreign key and no check behind it, and the classifier only runs when the caller left the field empty — pass a string and it is stored as written. Five of our rooms (shared, marketing, orchestrator, gateway, chat) match no pattern in the engine and exist for exactly that reason. Rooms are already fluid. What is missing is everything around that.

Three consequences, all visible in our own store rather than theoretical:

The decision: a room registry and a janitor on the write path

Decided 26 August 2026. The janitor and the listing call shipped the same day, in our client layer; the registry and the backfill did not. Details below the list.

Since creating a room already works, we are building the part that does not: knowing what exists, and keeping two spellings of one subject from becoming two rooms.

Three limits are part of the decision rather than caveats on it:

What actually shipped, and where the plan above was not followed

The janitor and the listing call were built and merged on 26 August. A caller who writes deploy, deploys or Deployment now lands in deployment; the string they typed is kept on the fact as room-alias:, a room nobody has used before is kept as typed and flagged room-new:, and an author:-style forged room tag in the call is discarded. The listing call reads the tag census and merges it with the known-room list so it is not empty on day one.

The registry did not ship — there is no row per room, no provisional status and therefore no merge proposal for anything semantic. What exists is the spelling half, which is the half that was safe to automate. The backfill did not ship either, and that reverses the order this page argued for two paragraphs above: we said the unroomed half comes first, and then shipped the janitor first because it stops new drift while the backfill only cleans old drift. We think that order is defensible. We are flagging it because the page said otherwise and a plan quietly reordered is indistinguishable from a plan abandoned.

Two bounds on all of this. It runs in our client layer, not in the packaged server — a clone of the repository gets the free-text room column and none of the canonicalisation, exactly as with authorship. And a room is still assigned once: canonicalisation changes what gets written, never what was written.

Counts on this page were read on 26 August 2026 from our own store, which is live and moves by the hour. The shape is the claim, not the last digit. Tracked here, with what it blocks.

Hall — what kind of thing it is

The hall is the fact's type, assigned during extraction. It makes retrieval selective: "what decisions have we made about deploys" is a different query from "what has gone wrong with deploys", and the hall is what separates them.

The real distribution in our fleet's store, 8,810 facts, read on 26 August 2026:

HallFactsWhat lands there
decision2,078a choice with its reasoning
fact801a stable piece of state
warning687a trap somebody already fell into
discovery520something found while doing other work
procedure507repeatable how-to
preference88a standing rule from the human
event61a dated occurrence
(not yet typed)4,068raw observation awaiting consolidation

Halls are assigned during extraction and consolidation, so the untyped row is not a failure — it is the queue. Of the facts that have been typed, decisions outnumber every other kind.

Decisions outnumber everything by 2.5×. That is what a fleet doing engineering work produces — not conversation, not preferences, but choices with reasons attached.

Layer — how durable it is

A detail worth noticing, because it says something about how the axes interact: in our store every L0 and L1 fact carries a room, and the facts with no room are all L2. Not approximately — 459 of 459 and 3,176 of 3,176, against 1,107 of 5,175 at L2. Rooms get assigned where durability is decided. Raw observation arrives unscoped and acquires a room when it survives consolidation.

Nothing in the design enforces that. It fell out of two months of use, and it is the kind of pattern that only shows up if you look — which is why the store is ordinary Postgres tables and the query that produced those five numbers is one GROUP BY.

How a fleet actually spreads out

17 rooms in use across 22 active agents. The largest are shared (1,350), agent (891), then rooms that grew around areas of work rather than around individuals — deployment (771), infrastructure (442), general (234), ui (224), schema (222), pipeline (181), auth (162), marketing (121), api (119). The tail is very thin: six rooms hold fewer than ten facts each, which is what "somebody typed a new string once" looks like from the outside.

We did not plan that split and would not have predicted it. The convention we wrote down was per-agent rooms plus one shared; what the fleet converged on is a mix of per-agent and per-domain rooms, because a fact about deploys is more useful filed under deploys than under whoever happened to find it. The mechanism was flexible enough to allow that without a migration, which is the argument for keeping it one string.

Rooms cost nothing to run

Worth saying because it is unusual for the part of a system that carries the product claim: the classifier that decides a fact's room and hall is 176 lines of regular expressions. No model call, no embedding, no latency. The one thing in RCLL that is ours is also the one thing that never touches an LLM.

Rooms do not survive an export yet

The most important limitation on this page, and we would rather you read it here than discover it.

The engine's export endpoint exports a bank template — configuration, mental models, directives — and not a single fact. The listing API that does return facts returns 11 of the 26 stored columns, and room, hall and layer are three of the fifteen it drops, along with every link between facts. So the scoping axes described above currently exist only inside a running store. Move your memory anywhere — including into another copy of RCLL — and they flatten.

We consider that a defect rather than a missing feature, because a command called export that hands you a file which is not your memory is worse than having no command: it is a backup that fails on the day you need it. A full dump with every column, every link and provenance intact is the first item on our list, targeting the published portable-agent-memory interchange specification rather than a format of our own. The direction we intend to demonstrate first is getting memory out.