Three weeks ago A boundary is a price arrived at a definition. A boundary is a place where crossing costs something. Where crossing is free, what you have is distance, and distance drifts. When I wrote that, Rheo Stream was an idea document one day old, and nothing was built.
Phase one is built now, and my own memory lives in it. So the definition can be tested against a build instead of a plan. The boundaries paid for themselves where the price sat on the crossing. Every leak and near-miss in phase one was a price sitting one step away from it: a count taken before the permission check ran, a rule written in a doc with no check behind it, a deploy that reported on its workflow instead of on the running image. The definition holds, but it needs a second clause the first essay did not have. The price has to be charged at the point of use, by the thing doing the crossing. A boundary that is correct at the owner and borrowed around by a consumer is distance again.
Twenty-two days passed between the first commit of the idea document and the day my memory moved in, and main now has 500 commits and 131 merged pull requests. Rheo Stream is the thing under test.
Where the price was real
The first essay said a designed boundary is "not necessarily a separate deployment." That held. Rheo Stream is a modular monolith: one application serves the shell, the identity host and every module's subdomain through host-based routing. The module hosts are a routing table; there is no deployment per module. A module is a Python package that publishes one entry point, which resolves to a typed manifest, and the core imports no module at all. Each workspace gets its own Postgres database, so one tenant cannot read another's rows, and there is no policy check to get wrong. Someone who has split a monolith will ask what a deployment per module would have blocked here. For the failures in this piece, nothing. The privacy leaks below went out in responses the caller was entitled to get, and the deploy and pool trouble sat outside the modules.
Where those boundaries get crossed, running code refuses. There are three kinds of token, and the API surface takes the CLI kind only. An MCP token presented there comes back token_wrong_kind, because an MCP token is a model's credential and the API is not for models. Redaction changed with PR #147. Until then the redaction contract was a vocabulary of sensitivity tiers and nothing else; no code read a module's sensitivity map. After it, manifest validation refuses a restricted field in any event, and rendering for a model fails closed. Contact details are masked by default, and lifting that takes both an operator setting and an explicit row for the workspace.
Those are types and tests, the kind of price the first essay said works, and they worked. I have no count of the crossings they refused. The evidence is narrower: every failure I found sat somewhere else.
Issue #134 is the opposite case. One of the founding guardrails says raw SQL lives only in repository modules. Nothing checks it, and text( already appears outside the storage folders. The requirements say a second database backend is possible, and the behavioural suite that claim rests on does not exist. The module-contract doc drifted too: for a while it described a Module object with a register function, and the build found no such object or function in the tree. Three weeks ago I said an intention to behave is free. The project still carries two rules that are only that. The clause says what the fix for the first one looks like: a check that fails the build when text( turns up outside storage. Nobody has written it, and #134 is still open.
That much confirms the first essay. The leak is where the second clause came from.
The leak was at the consumer
Recall in Rheo Stream runs a permission walk: of the memories that match a query, which ones may this caller read? The walk was right. The leak happened anyway, three times, the same way each time.
Issue #121: recall reported how many matches each retrieval arm found, and those counts were taken before the walk. Any caller could learn how many memories matched that it was not allowed to see. Issue #125 turned up in the cold review of the #121 fix. Under hybrid retrieval the fused score encodes ranks from before the walk, so the scores counted hidden memories too. PRs #124 and #126 closed both.
The third case was in the UI. The memory UI's search screen rendered those same per-arm counts. In the empty state, as the run log put it, the count "is exactly a hidden memory." The per-prompt reviewers and the integration reviewer passed 17 checkpoints with it in place. A fresh-context review of the whole PR caught it at checkpoint 18 of 19. The screen met its acceptance criterion, which asked for the search strategy and whether dense retrieval was available. The counts rode in through the provenance field. The docstring on the count type says they are taken before the permission walk, and nobody connected that sentence to the empty state.
All three went the same way. The owner of the boundary wrote the price down in one place. A consumer computed or displayed something on the wrong side of it and never read that place.
The fair objection is that a design which leaked three times did not pay for itself. I think it did, because of where the trouble was. The leak was real, and I am not counting it as a win. But none of the three was a wrong answer about who may read what. All three were a number computed before the walk ran. The counts were a free path around a price that was charged correctly, and the last of them was found only when someone read across the whole change.
Not all of it is closed. Several one-bit residuals are accepted and stay: read's bit 2, a hybrid recall's reference_scan_limit bit, each arm's own pre-walk bound, and redaction still letting window totals and recall provenance count excluded items. Issue #272 is open. In a targeted read, a shared scan budget lets a caller learn that at least one more mention exists that it cannot see. That is one bit, about an entity the caller can already see, and the two options for it are undecided. None of these is exposed today, for a reason outside the code: the flagship has one owner.
The cutover got cheaper and the safeguards did not
That one owner is me. The last thing phase one did was move my memory in, which was the part with real stakes, and it tests the definition from the other direction. The leaks were prices set in the wrong place by accident. In the migration I moved prices on purpose: I took them off every step I could redo and left them on every step I could not.
The original plan was written to prove parity before switching: a row-by-row loss ledger, a retention forecast, a ground-truth harness, double dry runs, review at every step. On 2026-09-27, four of fifteen prompts into the groundwork, I said the migration could be done a lot more cheaply. The objective became moving my data safely, and the general migration framework was dropped. The bar I set was roughly this: keep the source read-only, keep anything derived from the data out of the public repo, make the import idempotent and wipeable, keep a private count per table, take a fresh backup, then spot-check by using it.
The reasoning was reversibility. The import left the old system untouched, so a bad import could be wiped and rerun, and a parity gate is a price on a measurement, which is something you can take again any day. The next day I relaxed it further: no preselected old memories, no planned queries, no overlap@10 or A-B gate against the old system's results. A docs-only PR amended the acceptance criterion to count, identity and sample checks. That cut has a cost I will come back to. I have no number for how much of the old recall survived.
What stayed was everything on a crossing that could not be undone. The decision record lists what was not waived: source accounting, privacy, backup, rollback and separate authorization for each live action. In practice that meant a full backup of the flagship database, restored offline in a disposable container with no network, before anything was touched. A brief complete pause of the old system for a snapshot hashed into a manifest. Checkpoints before and after the import, each restored offline, every artifact bound by checksum, and one approval from me on the exact packet.
And the importer refuses a second run. That one I like best. It is the second clause in a single feature: the price is charged at the point of use, by whoever calls it next, and nobody has to remember anything.
The writer inventory was the other place the clause turned up. It found more writers to the old memory than I expected, twelve producer classes in all: the bot posting on every turn, REST and MCP routes, nightly maintenance that writes even with the per-worker flags off, a historical hourly email-intake routine. There is one line from that inventory I keep coming back to: "A flag toggle is not cancellation." A flag per worker is an intention each consumer is trusted to read. So a master memory-disable switch went into the old system first, merged on 2026-09-28 and left off until the cutover, which put one price on the store itself instead of one on each caller.
A reader who disagrees will say this only worked because the data was one person's, and that person could spot-check it. The spot-check covers quality, which I gave up on purpose. It does not cover a double import or a backup that will not restore, and neither of those depended on me noticing anything. The objection is right about one thing. With rotating operators, or with data other people depend on, I would keep the parity harness, because a spot-check only works when the person doing it knows the data.
The counts are what "proved" meant. The history source reconciled to 4,008 rows: 4,000 history candidates and 8 telemetry rows. The curated memories were counted on their own. At 08:04 UTC on 2026-09-30 the import committed 4,000 history entries and 523 curated memories atomically. 75 credential-shaped strings were masked and 30 curated candidates held back. A second invocation was refused before it wrote anything. By 08:49 UTC the workspace held 529 memories, 523 migrated and 6 derived, each with an embedding.
The cutover came at about 17:15 UTC the same day, and after it there was one writer. M.O.T., the old system, runs with MOT_MEMORY_DISABLE=1, confirmed in the container, and keeps tickets only. Rollback was three lines: remove the MCP server, revoke the token, unset the flag. At the cutover a fresh session's top recall hit was a memory captured automatically from a conversation that same morning.
One cost I did not price was on the client side. Rheo bot saved memory on every turn through the old system. When the old writer was disabled, the bot's per-turn memory had nowhere to go. I accepted that at cutover rather than quietly port it, and it came back about 13 hours later, with long-term memory going through the new recall tools, the raw chat kept in a local turn log, and no automatic extraction until a later phase. I have not yet confirmed with a real Telegram turn that it saves and then recalls. Moving a memory boundary moves the wiring of every client that used to call across it, and the client pays for it.
The machinery had no price at all
The near-misses were in boundaries nobody had drawn as boundaries: what releases when, what counts as deployed, how many connections the system may hold. None caused damage, and each splits along the clause: what was charged at the crossing held, and what was reported from a step upstream was wrong.
During the first write-quiet window for the import, an unrelated automatic release of PR #260 replaced the containers. Its normal schema step ran into the temporary read-only setting on the target, and the workspace needed a repair with the supported command before it was active again. No import had run. The read-only setting was a price on the database itself, and it is the reason this cost a repair. Nothing put a price on the schedule. A release could land inside a migration window because nothing said it could not. A second automatic replacement came at 08:13 UTC, after the import, and left the counts unchanged.
The deploy failed the way the pre-walk counts did. The deploy workflow skips a commit that is no longer main's tip, and the run still ends in success. On 2026-09-30 two merges landed back to back, and a commit was reported live on a green run plus a healthy /healthz while production still ran the earlier image. A health check that answers 200 identifies nothing (it says something is up, not what). Only the running image tag proves a deploy. The workflow and the count both reported on the step before the crossing, and both looked correct from where they sat.
The connection pool is the third. Issue #62 had already found that the documented connection budget understated the worst case, because evicted engines keep their checked-out connections. That was a budget in a doc. The price got charged only when rheo doctor ran against the live flagship and warned that core plus worker could demand 172 connections against a stock Postgres limit of 100. Cutting the cached workspace engines from 16 to 6 brought the budget to 72 of 100.
Four cases, one clause
Run the cases through the definition with its new clause. The pre-walk counts: the owner charged the price and the consumer went around it. The raw-SQL rule: the price sat on nothing. The deploy: the price sat on the workflow, one step from the running image. The cutover: I took the price off every step I could redo and left it on the one I could not, and the cheaper version of "safely" held because of where it stayed.
The clause tells you where a price belongs. It does not tell you where the consumers are. The cases where I know how the problem surfaced were found by something that looked across the whole system: a review of the whole PR, an inventory of every writer, a check against the live box. The review of the part that produced the problem had passed it.
This is one build, by one owner, over twenty-two days. The definition passed one test, which is not a proof. The clause needs a second system, built by someone else, before it is more than a finding.
What this did not solve
Only Claude Code can recall. Desktop chat, the web app and the phone have no path to the memory. Recall is on demand only; nothing injects relevant memory at session start. A constant-size MCP tool surface exists, and my daily client still lists 11 tools, so one surface is one surface for one client today. The episodic ledger, entity lifecycle and typed relations are approved to plan and not built. Phase two has not started. I want to run phase one for a while first.
There is no parity number. Whether the old recall survived is a question I chose not to measure, and I still cannot answer it.
And the multi-member fixes have never met a second member. Every residual bit in this piece is unexposed because the flagship has one owner, which says something about today's membership and nothing about the code. #121 and #125 were gated before any multi-member deployment, and both are closed. The bits that remain were accepted for a workspace of one, and when a second member joins, each of them has to be accepted again or closed. The fixes have been tested against a design and never against a person.