THEPROTOCOL

Somewhere to Break It

2026-09-20 · 24 min read · ruFFa
The Sandbox panel on the larch frame, finished. Two sandboxes are listed, larchsbx at sandbox.larch.theprotocol.cloud and larchsbxb at sandbox-b.larch.theprotocol.cloud, each with buttons to open its MCP tester, its console and its login. Below them the job reads Provisioning two federated sandboxes, done, 14:53, with eight steps all green: check names lanes and memory, stand up the sandbox, stand up the second sandbox, federate the two sandboxes, a cloud operator on each sandbox, federation suite fixtures, and check the tester can log in, each with a line of detail underneath. A green line reads: ready, open its MCP tester above and run the sweep there. A Tear down button sits at the bottom.
The thing this post is about: two test frames beside a real one, stood up from the frame's own console in under fifteen minutes, by the same commands that stood up the frame.

Next week the code that runs this network stops being something you have to ask us for. I have written about why nobody should own the middle of a network like this, and about what a handover has to contain to be worth receiving, so I will not do either again here. This post is about a smaller question that has been keeping me up: what happens in the first hour after somebody downloads it.

Not the first month. The first hour. A stranger unpacks an archive on a host that has never heard of us, and either something real happens or this project joins the very large pile of repositories that build exactly once, on the author's machine, in the author's presence.

Two things had to exist for the answer to be something real. The first is a kit: one commit, packed, that stands a working sovereign frame up on that host. The second is somewhere to break it, because the moment a stranger can run this, the next thing they will want is to change it, and nobody sane tests a change on the frame their users are on.

Both are live, and the way I convinced myself of that was to become the stranger. Yesterday evening I made an account on the flagship that had nothing to do with me, asked for a frame through the public form like anybody else would, approved it from the other side, downloaded the kit through the browser, checked it against its seal, and stood the frame up from nothing but what that page told me. Every screenshot below is from that run.

What is actually in the box

The kit is a release of one commit. Not a copy of my working tree, not a tidied subset with the interesting parts filed off: an archive of a commit, with a file beside the code listing the sha256 of every file in it. Twenty-one megabytes, and what it builds is the registry, the token ledger, the event store, the auditor, the federated index and the certificate writer that this network runs.

On your host, one command builds it. Six images, a few minutes, and no account of ours anywhere in it: no image registry to log in to, no token, nothing to sign up for. It pulls public base images and the packages the release pins, and that is the whole supply chain. The build reads the file list as it goes and stamps what it found into every image: the digest of the tree, whether that tree differs from the release, and which files differ if it does. Each frame then reports that at its own health endpoint.

That last part sounds like bookkeeping and is the reason the rest of this post works. A frame can always answer two questions about itself: which release am I, and has anybody changed me. Later on, those two answers are the whole difference between evidence and an anecdote.

The Your frame page on the Brussels console, seen by the person who asked for a frame. A progress rail reads Requested, Approved, Kit downloaded, Invitation sent, Federated. A checklist titled What your host needs lists Linux with Docker and the Compose plugin version 2.24 or newer, 8 GiB of free memory and 20 GB of free disk, ports 80 and 443 for the console and 8443 to 8445 for the machine lanes, the hostname and its wildcard pointing at this host, and a mail relay. Below, five numbered steps with the exact commands filled in: unpack and build, point your names at the host with two A records, check the host with frame-doctor, start the frame with frame-up, and join the network with frame-federate. Values only the operator knows are highlighted as placeholders. On the right, a card with the kit release 1.0.0-beta.6, 21.5 MB, a Download the kit button, 4 of 5 downloads left, open until Oct 19 2026, and the sha256 of the archive under the heading Check the download.
The whole handover on one page: what the host needs, the five commands with this frame's own values already in them, and the seal to check the download against. The highlighted values are the ones only the operator knows, and the shell refuses the command if they are left as they are.

Asking for a frame

There is a form on the Brussels frame for asking for one. Six steps: who you are, what the frame should be called, what its currency is, where it is, how it should join the network, and a review page that shows the registry card the answers will produce. It is a request, not a purchase, and nothing about it is automatic on our side: a person reads it and approves it.

The other side of the request, on the Brussels frame's Frame Management page. A card headed Larch Cooperative, marked approved, lists the frame name larch, the display name, the host larch.theprotocol.cloud, the trust domain, the currency LRC Larch Unit, federation set to federate later, hosting on their own infrastructure, the place Tartu with its coordinates, the theme, the tags, and a frame kit line reading 0 downloads since 2026-09-19. Underneath are the applicant's own answers to why they want a frame and what else they want us to know, and three buttons: use for provisioning, renew kit window, reject.
What the person approving it sees: the request as it will become a frame, the download window and how much of it is used, and three buttons, one of which is no. Approving does not build anything on our side; it opens a window on theirs.

When it is approved, the applicant's own page fills in: the host checklist, the five commands with that frame's values already in them, and a card holding the kit, its release, its size and its seal. Five downloads, thirty days. The count and the expiry are not a licence check, and there is nothing to enforce: the code is open. They are there so a link that leaks does not quietly become a permanent anonymous mirror of a build nobody is watching.

The seal is the part I would actually use. The page prints the sha256 of the archive, and the command to print it yourself where the file landed. If those two strings differ, you are not holding what we published, and no amount of reading the code afterwards will tell you that as quickly as one line will.

Somewhere to break it

Now the part this post is named after. Filling that form in as a stranger, I wrote this in its free-text box, which was half an answer and half a note to myself:

We want them on our own server with our own currency, and we want to test every change in a sandbox before we suggest it.

The frame that came out of last night's run is called larch: Larch Cooperative, in Tartu, with a currency called the Larch Unit, its own trust domain, its own ledger, and a federation with Brussels that it asked for and we approved. It is not a demo tier. It runs what the flagship runs.

The larch frame's own console on its first morning. Its fleet is empty, with an invitation to create the first wallet agent. A network pulse panel reads 1.0M supply, 999.0K treasury, 0 staked, 0 agents, 0 events and 1 federated peer, and the currency chip in the header reads LRC.
The same frame an hour later, on its own console. A million Larch Units in a treasury nobody has spent, one federated peer, and no agents yet. Everything after this is somebody's decision, not ours.

Which is precisely the problem. Once somebody has a frame like that, the next thing they want is to change something in it, and a frame with a ledger and users is the worst possible place to find out what a change does. Our own answer to this for the last few months was a test tier that exists on this host and nowhere else. That is a perfectly reasonable arrangement for the people who own the host, and completely useless to somebody standing a frame up in Tartu.

So a frame from the kit can now stand up its own sandbox, from its own console, in about fifteen minutes.

That sandbox is where the work happens from now on. It is where you try a change, where the tests run, where you break something deliberately to see what the ledger does about it, and where you find out that the thing you were sure about is not true. Nothing in it is for users and nothing in it is for show. It exists to be treated badly.

The Sandbox panel before provisioning. Its lead reads: a test frame beside this one, the MCP tester runs inside it, so every write a test makes lands there, never on this frame. Under the heading What should the sandbox test are two choices: One frame, the lifecycle sweep, about 3.5 GiB of memory, and Two federated frames, the lifecycle sweep and the federation suite, about 7.0 GiB of memory, which is selected. Below is a ticked box reading A cloud operator on each frame, a line saying this host has 39.9 GiB of memory free, and a Provision button.
Two choices and one tick box, with what each costs in memory and what this host has free, before anything is pressed. The estimates move when the operators are ticked, because the operators are real frames too.

A sandbox here is not a mode, a flag or a mocked backend. It is a whole second frame beside yours: its own database, its own token ledger, its own event store, its own identity authority, its own console on a name of its own (sandbox. in front of your frame's name), and its own administrator login, which the panel will show you once. The test sweep runs inside it, against it. Every write a test makes lands in that frame's ledger and nowhere near yours.

You get two shapes. One frame runs the lifecycle sweep, which is the one that calls every tool a single frame serves. Two federated frames adds the federation suite, which is the one that tests what happens between frames, so it needs two of them and a cloud operator on each. The panel prints what each shape costs in memory next to what your host has free, before you choose, because the most common way this could waste somebody's evening is by starting something their machine cannot finish.

If a step stops, the panel says which one and why, and Try again picks up from there rather than starting over: the steps that already stand are skipped. Tear down removes the operators through their own frames' admin rails, then each sandbox through the kit's own teardown, and closes the ports it opened. The test reports stay behind, because those are the point.

Through the same doors as a real frame

This is the part I care most about, and it is invisible in every screenshot.

The sandbox is stood up by the kit's own frame-up, the same script the operator ran for the frame itself. The pair is federated by the invitation rail, the same one every frame on this network used to join it. Each cloud operator goes through the application, the approval, the activation and the provisioning, in that order, exactly as an operator does on the flagship.

A test frame built by a shortcut tests the shortcut.

There is no test mode in any of it. Nothing is mocked, nothing is seeded around the front door, and the sandbox does not know it is a sandbox beyond its own posture and the fact that another frame stood it up. So when a test passes in there, it passed through the doors a real frame has, on the software a real frame runs. What the sandbox shares with your frame is the host, the web server in front of it and the small service that does the host work. Nothing else: not a database, not a ledger, not an identity, not a user.

There is a way to see all of that at a glance, which is to look at what the evening actually made on this host. Sorted by the moment each container was created, newest first, the list runs from this morning's rebuilds down to the identity authority that came up first, and it is one hundred and sixteen rows long. The frame is thirty-two of them. Each sandbox is thirty-one. Each cloud operator is eleven. They are all still running.

The host's container table, sorted by creation time, newest first, 116 rows long. Every row reads running, on host theprotocol-srv1, with its average processor and memory use beside it. The newest rows are larchsbxb-registry, larchsbxb-directory and larchsbxb-auditor from this morning, then the whole of the second sandbox, then the first sandbox, then the two cloud operators, then the frame itself: its registry, token ledger, event store, auditor, federated index, policy engine, Prometheus, Alertmanager, Grafana, its message broker, its certificate writers, its databases and its Redis. The list ends at larch-spire-agent and larch-spire-server.
One evening, one host, ordered by the moment each container was created. It ends one hundred and sixteen rows down at larch-spire-server, the identity authority the standup brings up before anything else can have a name. Read it from the bottom and you are watching the evening happen: the frame, then its first sandbox, then the second, then a cloud operator on each.

That is the honest answer to how heavy this is. A frame of this kind is not a service you start, it is a small fleet, and standing one up by hand is a weekend for somebody who already knows where everything goes. What the kit does is make that one command on a machine we have never seen, and what the sandbox does is let it happen twice more, beside itself, to be broken.

The MCP tester page on the larch frame itself. The badge reads MCP REGRESSION SWEEP, PRODUCTION, RUNS IN ITS SANDBOX, and the lead ends with the sentence: on this frame it runs only in its sandbox, never against production. Below the description, a note reads that this frame is production, so the MCP tester does not run here, and that its sandbox should be stood up above and the sweep run in the sandbox's own MCP tester.
The same page on the real frame. It has no run button. A production frame provisions its sandbox here and runs nothing itself, and the page says which frame it is looking at rather than guessing from the address bar.

What the sweep asks of 738 tools

The thing that runs in there is our own regression sweep, the one we use on this network, and it is the reason a sandbox has to be a real frame.

It calls every tool both machine interfaces serve, 421 public and 317 administrative, and holds each one to twelve checks: the status, the shape of the answer, the types of the values, the read-after-write side effect, whether the event reached the ledger, whether the audit row was written, whether the tool is in the tier it claims, whether a caller in the wrong tier is refused, whether a peer frame sees the same thing, whether the wrapper and the route agree on the schema, the latency against that tool's own baseline, and how long the system takes to become consistent. On top of that, 86 multi-step lifecycles chain real mutations, transfers, stakes, disputes, governance, cross-frame work, and prove the side effects survive the round trip. A skipped tool counts against the result: a skip is a coverage hole, not a victory.

Four to five minutes for the lifecycle sweep, on a host that is also running everything else we run. The federation suite, 572 checks across both sandboxes and their two operators, takes about twelve.

Baseline, change, compare

A sandbox on its own is a place to look at things. What makes it a way of working is the order you do things in, and the kit ships that order as five commands.

flowchart TB K["the kit, as published
one commit, a digest per file"]:::g --> S{"frame-sandbox.py
a whole frame beside yours"}:::q S --> B["frame-test.py --baseline
the suite, on the release"]:::a B --> C["your change
build.sh --version 1.0.0-mine.1"]:::f C --> U["frame-update.py
only the images that changed"]:::a U --> R{"frame-test.py --compare
the same suite, again"}:::q R -->|"a red that was not red before"| X["exit 1, each one named
with the reason the tester gave"]:::x R -->|"another release, another suite,
a report older than your last edit"| X2["refused, in a sentence,
before anything is compared"]:::x R -->|"nothing new is red"| P["frame-contribute.py
the patch, both reports,
their comparison, your sign-off"]:::g classDef x fill:#3a1a1a,stroke:#a85454,color:#ffd6d6 classDef f fill:#2a1f47,stroke:#7a5cc4,color:#eadcff classDef q fill:#1a2740,stroke:#3f6ea8,color:#dce9ff classDef a fill:#0d3a4a,stroke:#2f8fb0,color:#d6f4ff classDef g fill:#0b3d2e,stroke:#1f8a5f,color:#d6ffe9

The comparison is the whole thing, and it is worth being exact about what makes it worth anything at all. The baseline has to have run on the release, unmodified. The second run has to have measured the tree you are actually proposing, not one you have edited since. Both runs have to have used the same test suite. And then the only question that matters is whether anything is red now that was green before.

The interesting number is not how many tests pass. It is whether your change made anything fail that did not fail before.

Those conditions are checked rather than trusted. Each of the five commands refuses, in a sentence, when its evidence would not be about your change: a baseline taken on a build that differs from the release, a comparison against a report from a different release, two runs whose test suites are not the same file set, a report older than your last edit. I wrote each refusal the way I would want to read it at two in the morning, which is to say it names the thing that is wrong and what to do about it, and it does not tell me it is sorry.

The exit code says which of those happened: nothing new is red, something is, the run refused before starting, it ran out of time, or the two runs cannot be compared. That is enough for a person, and enough for a script.

Here is that loop on the sandbox in the figures above, run twice on purpose: once with a change that breaks nothing, and once with a change that breaks something, because a detector nobody has watched fail is not a detector.

First the baseline, on the release, before anything of mine was in it:

$ ./frame-test.py --name larchsbx --baseline
larchsbx: run bbd8d9ece9fc40c2 (flow_mcp_lifecycle_complete) started
larchsbx: run bbd8d9ece9fc40c2 completed after 240 s
report: 2026-09-19T182734Z-flow_mcp_lifecycle_complete.json (758 tools: 690 pass, 0 fail, 68 skip)
baseline set: baseline-flow_mcp_lifecycle_complete.json

Then a change of mine, built under a version of my own, put on the sandbox, and measured against that baseline:

$ ./frame-test.py --name larchsbx --compare
report: 2026-09-19T183641Z-flow_mcp_lifecycle_complete.json (758 tools: 690 pass, 0 fail, 68 skip)
baseline: 758 tools: 690 pass, 0 fail, 68 skip (53a30aedf0d7, modified=False)
this run: 758 tools: 690 pass, 0 fail, 68 skip (ad1b38695eff, modified=True)
RESULT: no new red

The two digests in those lines are the trees the two runs actually measured, and the flag beside each says whether that tree was the release. Then the same loop with a fault I put there on purpose, to watch the thing fail:

$ ./frame-test.py --name larchsbx --compare
report: 2026-09-19T184527Z-flow_mcp_lifecycle_complete.json (758 tools: 689 pass, 1 fail, 68 skip)
baseline: 758 tools: 690 pass, 0 fail, 68 skip (53a30aedf0d7, modified=False)
this run: 758 tools: 689 pass, 1 fail, 68 skip (03f3fdd36997, modified=True)
NEW RED (1): theprotocol_getApyRates
theprotocol_getApyRates: expected success but got HTTP 400: Error: staking/apy-tiers returned 404
RESULT: 1 new red(s): the change breaks what the release passed

One tool, named, with the sentence the tester gave, out of seven hundred and fifty eight. That is the whole product: not a score, a difference.

And once more, from this morning, because it is the rule biting me rather than helping me. I moved that same sandbox onto the release this post ships and asked for a comparison against the baseline it still had from the night before. It refused:

the suite itself differs between the two runs (suite hash 9b96dc4ef5eb -> 24a557c1f42a): a changed test is not evidence about the code; not comparable

Which is correct. Between those two runs the test suite had moved as well as the code, so the two sets of numbers are not answers to the same questions, and a tool that pretended otherwise would be worse than no tool. Take a new baseline on the release you are actually on, and go again.

To try another build, update again. To go back, frame-update.py --rollback puts the release images back and the sandbox is a baseline machine again. Volumes, data and secrets stay where they are. Both directions check the ledger's supply invariant before and after, and refuse to call it done if it moved.

The archive that travels with the patch

The last command packs the argument. frame-contribute.py writes one archive holding the patch (a plain diff of every file your tree changed against the release, worked out from the file list, with the originals taken from the archive you downloaded), both test reports, their comparison, a manifest with a digest of each file in it, and your sign-off.

It refuses to pack evidence that is not about the change it is packing. If you edited a file after the run, it says so. If the baseline was taken on a build that was already modified, it says so. If the two reports came from different releases or different suites, it says so. If the comparison has a new red in it, it will not package it at all, which is a deliberately rude way of saying that the loop is finished when the evidence is clean, not when you are tired.

What lands in a pull request, then, is not a claim. It is a patch, and a run, on a frame the author stood up themselves, that says the suite this release passed still passes with the change in it. That does not tell anybody the change is a good idea. Nothing can do that except a person reading it. It does mean the argument starts somewhere better than trust.

The foot of the MCP tester page inside a sandbox, showing three blocks: architecture, describing the lifecycles and contracts; target, saying this frame is the sandbox its frame stood up and that the sweep runs against this frame itself so every write a test makes lands here and never on the frame that made it; and docs, pointing at PUBLIC_DOCS/TEST_VERIFIED_DEVELOPMENT.md for the loop this sweep belongs to: a sandbox stood up from your own frame, a baseline on the release, your build on top of it, and the comparison that has to show no new red.
The foot of the same page, which now points at a document the kit actually ships. Small thing. It is the difference between a page written for us and a page written for whoever is reading it.

What this is for

I have argued on this blog that nobody should own the middle of a network like this one, and that a stranger should be able to decide whether to deal with a frame for the price of one request. Both arguments have the same awkward footnote: as long as I am the only person who can change the software every frame runs, I am the middle. A polite middle, with reasonable documentation and a genuine intention to be fair, which is exactly what every middle has been at the beginning.

Opening the code removes half of that. This post is the other half. A code base you can read is not the same as a code base you can change, and the gap between them is filled with the question every maintainer asks and no stranger can answer: how do I know this does not break anything?

Now they can answer it, on their own machine, in an evening, with a run they did themselves. And so can I: the same loop is how I will judge what arrives, which means I am accepting the same discipline I am asking for, on the same instruments.

The frame in these screenshots was stood up by the five commands on that page, its sandbox pair came from its own console, the sweep ran inside the sandbox, and the supply invariant on every ledger involved read zero throughout. Nothing in that chain needed anything from our side that a stranger cannot have: a form, an approval, and a page with five commands on it.

Next week, and possibly from somewhere else

We are aiming to open the code base next week. Not a subset, not a demo: the frame, the ledger, the identity fabric, the console, the runner, the kit, the gates and the tests that hold what every run so far has taught them.

Where the code will live is not decided yet. When it is, it will be a link on this page and not a press release.

There is also a real chance that this project has to move to a different country to keep doing what it does. I am not going to speculate in public about something whose ending I do not know yet, and I would rather say the awkward thing plainly than have it turn up later as a surprise. If it happens, it happens, and I will write about it here when there is something to write.

What I can say is what it changes for the network, which is the only part that should matter to anyone reading this: nothing. That is the entire purpose of the last four weeks of work. A project that lives in one place can be made to disappear from one place. A project that has been handed over, with a kit that builds on a stranger's host and a way for that stranger to prove a change, cannot.

Where I stopped

Written down honestly, because a handover with a polished front and no list of limits is a sales page:

The auditor that checks every ledger in this post still does not work for us, and the frame in the screenshots will outlive my interest in it, which is the point of building it this way. Skepticism remains the local currency. This week it is accepted in one new place: a sandbox, which is exactly where it belongs.

Patchnotes

The last post left the fleet at V0372 and the export on a private branch. What follows is V0373 through V0410, the version this post is published from and the one every registry on this network now runs, together with the first two published kits: five days, supply delta zero throughout on every frame the auditor watches. A line that does not name where it runs is live on the whole fleet.

The kit

The sandbox and the loop

Frames and operators

Identity, the ledger bus and workers

Console and network

Build and release