Satsuma owns what it builds, and fixes it without touching the money
Eleven runs. Every contract Satsuma deployed became a product it owns: a record on its chain, a repository, a list of things that must stay true, and a review of its own code by a model told to attack it. Those reviews found real defects, twenty of them, and Satsuma fixed nine by vote, three different ways: a redeploy for a contract holding nothing, a migration that moved ten thousand tokens to a successor, and in the last run an upgrade in place of the contract holding the position. In five of the last seven runs nobody touched anything.
The previous experiment ended with Satsuma proposing what it needed, building it, running it under a policy it had written, and reviewing that policy every five minutes. It also ended with something nobody was looking at. The contracts it had built were deployed and then forgotten. An exchange and a mandate account sat on two chains holding Satsuma’s money, and nothing in the loop would ever read their code again, ask whether they still did what they promised, or fix them if they did not.
Anyone who has shipped software knows that this is where the work begins. A contract that holds funds is attacked, drifts from its specification, or turns out to have been wrong from the start. Someone has to own it: know what it must keep true, notice when it stops, judge how bad that is, stop it if it is dangerous, and fix it, without losing what it holds, in a way others can check.
This experiment asks whether Satsuma can be that someone. The objective and the world are those of the first two experiments. What changed is that every contract Satsuma deploys now becomes a product it owns, and the run is scored on what it does with that ownership.
Eleven runs over five days. Every run but one built the same two products from scratch, and a review of their code by a model told to find what was wrong filed twenty issues against them. Satsuma fixed nine, each by a vote, and did it three different ways depending on what the broken contract held. The last run did the hardest of the three: it changed the code of the contract holding its position without moving the position, and nobody touched anything from start to finish.
The setting, briefly
Readers of the first two experiments can skip this section.
Satsuma is an attempt at a fully autonomous AI. Language models make its decisions, and a blockchain of its own holds its identity, its treasury and the rules it must act within. It is given an objective with measurable key results, written into a on its chain, and everything else has to come out of its own process: proposals, votes, builds, reviews, deployments, and only then action. The objective is unchanged:
Keep Satsuma’s holdings on tangerine balanced by swapping on its DEX, with no human in the transaction path, and never exceed the mandate.
tangerine is a second chain with its own token, TANG. The two chains exchange proven messages over , carried by a and checked by the receiving chain itself. Satsuma holds 10,000 tUSD and 10,000 TANG there, and to begin with has no exchange on tangerine and nothing that can act for it.
In every run it builds two things. An exchange, an on tangerine, which a scripted market then trades on. And a mandate account: a contract on Satsuma’s chain that one key may ask to swap, which checks each request against limits and refuses anything outside them. An accepted request crosses to tangerine as a proven message, a keyless there makes the swap, and a receipt crosses back. The remote account is the contract that actually holds the tokens. No private key for it exists; only the mandate account’s instructions can move what it holds.
Then an operation trades through the mandate under a policy Satsuma wrote and voted, reviewed every five minutes, as in the previous experiment. The four key results are the same: swaps executed and proven (10), requests refused by the mandate (1), failures after which the books still balance (2), and holdings between 45% and 55% tUSD by value at the close.
The brief gained one paragraph. Everything Satsuma builds must come with an operating manual, its administrative functions must be held by the chain’s or not exist, and it must say how each contract can be changed after deployment.
What owning means here
The design is recorded as two architecture decisions, written before the runs that tested them. The first says what a product is.
When a deployed contract is on chain, it becomes a product: a
record in a registry, with a state, Operating, Halted or Sunset, and a
private repository of its own under Satsuma’s GitHub organisation, carrying
the files any serious open-source project carries: a security policy, a
changelog, issue forms, a disclosure that the sole contributor is a machine and
that the ’s vote stands in for human review.
Each product has a manifest: a document naming the that must hold and the signals worth watching. A model drafts it from the deployed code and the builder’s manual; the council adopts it by vote, with a maintenance reserve set aside from the build’s budget to pay for fixes. From then on the invariants are read from the chain every cycle, and a reading outside its bounds halts the product without asking anyone.
Signals go to an assessor: a model role whose only job is to say whether something that looks wrong is a defect in the product, an exploit in progress, a market condition, or nothing. A market condition is the operation’s problem and goes to its reviews. A defect becomes an issue, filed on chain by with a readable copy in the repository, and given a severity on the scale bug bounty programs use: S1, theft or permanent loss of funds; S2, funds frozen for a time; S3, denial of service or ; S4, wrong behaviour that loses nothing. Some floors do not depend on the model: a breached invariant is S1, whatever the assessor thinks. An S1 halts the product: every operation using it pauses, and no new build proposal is accepted anywhere in the loop until it is resolved. Resuming is a vote.
Besides the signals, a product’s code is read. The assessor’s adversarial review takes the deployed sources and reads them as an attacker would, file by file, and files what it finds with the same severities.
A fix is building, shortened. It is a proposal citing the issue, paid from the reserve without a funding vote, built from the deployed sources on a branch of the product’s repository, through the same gates as a first build, and landed by the same attestation vote that records any deployment. That vote also resolves the issue and resumes the product. The fix’s is merged only when the chain says the issue is resolved: the repository’s main branch is whatever is deployed, never ahead of it.
The second decision was forced by run 2, and it is the subject of most of this article. It says how a fix reaches a contract that holds money.
Run 1: it measured, and refused to file
The first run showed the loop closing without anything going wrong. Both products were opened on chain with their repositories. The assessor drafted two manifests, and here the brief’s paragraph had already changed what Satsuma built: told that administrative functions must be held by the timelock or not exist, the mandate’s builder made its caps, floor and timeouts compile-time constants, gave it a timelock-only pause, and wrote a manual listing fifteen invariants of its own. The assessor turned that manual into seven invariants that pin exactly those constants, and the council adopted both manifests with their reserves. Every cycle the invariants were read back, and read back exactly at the end: 1,000 tUSD per instruction, a 90% floor, a 90-second message timeout.
Then the began: a relayer outage, and the liquidity pull from the previous experiment, in which the is emptied under every swap in flight so that each one fails. Seventeen swaps failed in a row. The assessor was asked three times whether this was a defect, and three times said no. Its reason the second and third time:
The counters do not say why the swaps failed, so they cannot tell a defect from a market condition.
That is the right answer for a liquidity pull, reached for a partly wrong reason: the signal it was shown carried no failure reasons. It was given them before run 2. But the shape of the behaviour was what the design wanted. The reviews of the operation said amend the policy, twice, and the policy was amended. Ownership asked is the product defective and answered no. The two channels stayed separate, and nothing halted that should not have.
Run 2: the first real finding, and the wrong shape of fix
Run 2 added a drill: three swaps into the operation, the harness asks Satsuma to review both products. It did, and the review of the exchange found a known weakness of the Uniswap V2 router it had built: a function that lets anyone a signed permit and make the caller’s withdrawal . The assessor filed it as S4, a bug that loses nothing. Within the same cycle a fix was proposed, funded with 500 from the exchange’s reserve, approved, built from the deployed sources with the issue as its scope, and deployed. Only the router was redeployed; the factory and the pool, which hold the liquidity, were untouched. One vote recorded the new router, kept the factory where it was, and resolved the issue. The whole loop in the figure above had run once, by votes, on a real defect.
The same run produced a false halt. One of the exchange’s invariants pinned the factory’s address, and the loop’s measuring step pushed that address through a on its way to the chain. The registry saw a value that did not match and halted the exchange, as it must. The assessor was asked whether this was an exploit, and declined to file: “the expected address after losing precision in a 64-bit float.” After the push was made exact, the loop proposed a resume, the council passed it, and a second vote set the operation active again. The halt was wrong; the recovery was three votes and no hand.
And the run found the problem the rest of the experiment is about. The review of the mandate account filed an S4 as well (the proof-age defect that run 11 would fix nine runs later), and the loop treated it exactly as it had treated the router’s: propose a fix, rebuild, redeploy. The builder stopped and returned a gap report instead of a contract:
The position cannot be moved from the live RemoteAccount to a redeployed one.
It was right. For newcomers: a deployed cannot be edited. A redeploy is a new contract at a new address, and whatever the old one held stays in the old one. For the router, which holds nothing, that is fine. For the remote account, which holds Satsuma’s 10,000 tUSD and 10,000 TANG and moves them only on the mandate account’s say, a redeploy would have produced a new, empty account and left the money in the old one, now owned by nothing. The previous experiment’s loop would have shipped it. This builder noticed, and said so.
Three ways to fix a contract
The second architecture decision came out of that gap report. A product’s manual must now declare, for each contract, how it can change: stateless, so a fix is a redeploy; upgradeable, behind a the timelock owns, so a fix is a new implementation the proxy is pointed at; or migratable, exposing a function the timelock may call that hands its state to a successor, so a fix is a successor and a . A contract that declares none is fixed for good: a fix is a new product. The manifest’s validator refuses a manual that leaves a fund-holding contract stateless.
- Build the fix from the deployed sources
- Deploy it at a new address
- One vote: record the address, move the claim, resolve the issue, resume
- Build the fix as a successor
- Deploy the successor and its keyless account
- One vote: tell the old contract to migrate, move the claim, resolve, resume
- Build the fix as a new implementation
- Deploy the implementation only
- One vote: point the proxy at it, resolve, resume. Address, holdings and state stay
The decision adds three rules that run 2 had also asked for. A fix builds from the product’s repository at the deployed commit, never from a build workspace, because the workspace predates the security fixes made before deploy. A Critical finding the assessor is unsure of is filed as an investigate issue rather than dropped. And every value pushed to the registry is an exact integer, read twice before a halt, so that a float can never halt a product again.
Runs 3 to 11 are the story of these paths being built and tested, one at a time.
Run 3: it stopped, and stayed stopped
Run 3 had the declarations and the rules, but not yet the migration or upgrade payloads: the loop could refuse an unsafe fix but could not yet make a safe one. The question was whether it would refuse.
The review of the mandate filed an S1: the price floor every swap was checked against came from a single of the pool’s reserves, which an attacker can skew in the same block, so an operator could be made to accept a swap at any price. The product halted, the operation paused, and intake froze. Then the loop looked for a fix and declined:
mandate-account, cross-chain-account hold state (… declare an upgrade path the loop cannot execute yet). It stays open until an upgrade or migration proposal answers it.
The reserve was not touched. The product stayed halted for the rest of the run, which is what the decision asked for, and what run 2 would not have done.
Two other things happened. A stray vote, a loop bug fixed during the run, set the operation active while its product was halted; the operator still did not run, because a halt outranks the operation’s state. And the council said no. The exchange’s manifest came to it twice with zero invariants, because the assessor had written the invariant calls in a shape the registry cannot encode, and the council refused both drafts 2 to 1. The third draft, with three invariants, was adopted 2 to 1. Nothing in the code decided that.
Runs 4 to 7: the money moves
Run 4 had the migration path. Its review filed another S1 on the mandate’s
floor, and the product halted. The manifest said both of the mandate’s
contracts were upgradeable, which is what the builder had claimed when it
wrote the manual. Before building an upgrade, the loop checked: it read the
storage slot where a proxy keeps its implementation address, and found no
proxy at either contract. The builder’s claim was wrong. The loop asked the
assessor what path the contract actually offered, and it found a
migrate(address) function that only the timelock may call, verified in the
deployed . A vote amended the manifest. Then a fix was proposed as a
migration, built from the deployed sources, and deployed as a successor: a new
mandate account and a new remote account.
The first deploy attempt failed, for a reason worth recording. Earlier in the same run, the exchange’s review had filed an S3 and the exchange’s router had been redeployed with a new contract. The successor’s deploy script compared the predecessor’s wrapped token with the exchange’s and refused to proceed. The failure went back to the builder as scope, and the second attempt deployed. A cross-product consequence surfaced as a deploy failure, not as silent corruption.
Then one vote did everything: it called migrate on the old mandate account
with the successor as argument (the loop chose the argument by simulating the
timelock’s call, because this build’s migrate takes the new mandate, not the
new account), recorded six new addresses, resolved the S1 and resumed the
product. On tangerine, the old remote account went from holding the position to
holding nothing, and the new one held 9,847.8 tUSD and 10,964.4 TANG. The
operator restarted on the successor, and the drills followed it there.
Run 4 took seven loop fixes and one edit of the daemon’s journal by hand to get there. Run 5, on the fixed loop, took none. The mandate’s builder declared its migration in the manifest itself this time, and said that the successor would carry the predecessor’s counters. The review filed the floor weakness as an investigate issue, S3, because the assessor’s confidence was under the Critical floor; the fix was proposed as a migration at once, the successor deployed on the first attempt, its pull request opened in the product’s repository, and one vote moved 12,783.4 tUSD and 9,235.6 TANG. The swap count read 28 before and 28 after.
Run 6 was the first run of the experiment in which the operation reached its
objective under the full regime: a false halt (every invariant of the mandate’s
manifest had been pinned to the wrong KPI by one, after two products were
adopted in the same cycle) recovered by a re-pin vote and a resume vote; an S4
on the exchange fixed by redeploy; an S2 on the mandate, which an operator
could use to block migrate by keeping an instruction pending forever, fixed
by migration; a successor deploy that failed on the same wrapped-TANG mismatch
as run 4 and recovered on its own this time; and ten swaps, holdings in band,
the operation closed by its own policy. One stop and restart for a loop fix.
Run 7, with no intervention, migrated the mandate while the operation was trading through it. The operation had made 25 swaps on the predecessor; the migration landed; the reviews amended the policy to bind the operation to the successor; the operator restarted there and made nine more, counters carried, closing in band twice. 14,847.9 tUSD and 9,527.1 TANG on the new account.
| Run | What the mandate’s issue was | Path | Holdings on the successor |
|---|---|---|---|
| 4 | S1, floor from a skewable spot reading | migration, after a vote corrected “upgradeable” | 9,847.8 tUSD, 10,964.4 TANG |
| 5 | S3 investigate, the same floor | migration, declared in the manifest | 12,783.4 tUSD, 9,235.6 TANG |
| 6 | S2, a pending instruction blocks migrate | migration | 11,322.1 tUSD, 10,390.0 TANG |
| 7 | S3 investigate, the same floor | migration, mid-operation | 14,847.9 tUSD, 9,527.1 TANG |
Four migrations, four positions moved, four issues resolved by the vote that moved them. The books balanced on every successor: Satsuma’s recorded holdings equalled what the new account held on tangerine.
Runs 8 to 10: the proxy
A migration is the heavy path. It deploys two contracts, moves funds across a bridge, and relocates every claim that depended on the old address, which is why run 7 found one more gap: the claim for the remote account was recorded at the wrong successor contract, and the loop watched that contract for three hours for a position that had landed, elsewhere, within minutes. An upgrade is lighter: the proxy keeps its address, its funds and its state, and a vote points it at new code. The brief gained a rule before run 8: a contract that others bind to by address is a proxy the timelock may upgrade.
Run 8 showed the rule working and nothing else. Given it, and given the proxy libraries, the builder put the mandate behind a proxy on the first try and the loop recorded the claim at the proxy, not the implementation. Then the operation met its objective, closed, and was retired by its own review; and for five hours every cycle found every key result met and proposed nothing. No issue was ever filed, because the drill had asked for the mandate’s review before the mandate’s state folder existed, so the request was lost. The upgrade path went untested for a harness reason.
Run 9 tested two things the earlier runs had not reached. The exchange’s S4
(“invariant I1, WTANG fully backed, is tautological”) was fixed by redeploy,
and this time the fix’s pull request was merged into the product’s main
when the chain resolved the issue, the first time that ran live. And because
the redeploy moved the router, the ownership pass filed an S3 on the mandate:
“Dependency amm-dex relocated: this product still binds to 0x4bfb…”. A
product learning, on chain, that a product it depends on has moved. The
mandate’s own issues stayed open, though, and the reason was a loop bug: the
loop looked for the migrate function in the proxy’s bytecode, where of course
it is not (it is in the implementation’s), and a declared migration was given
priority over an upgrade even when both were real. Both were fixed afterwards.
The fix made the assessor decide, per issue, which path a defect travels when
a product can travel both.
Run 10 lasted 96 minutes and built nothing. The proposer named the exchange’s
interface IUniswapV2, the builder wrote a file of that name declaring the
factory, pair and router interfaces, none called IUniswapV2, and the gate
that checks the first milestone accepted only an exact match. Five refusals
across two proposals, each retried blind, before the run was stopped by hand.
Run 9 had passed the same gate because its proposer happened to choose a name
the builder declares. A gate that passes by coincidence is not tested. The gate
now accepts the file a faithful builder writes, the brief tells the builder
what the name must be, and a refused attempt carries its reason into the next
one.
Run 11: the upgrade
Run 11 ran on 17 October from 03:33 to 08:24 UTC, on frozen code, with no intervention; it ended itself.
Both products built and deployed on the first attempt. The proposer split the exchange into the two interfaces a dependent actually calls, the factory’s and the router’s, and the mandate was built behind a proxy owned by the timelock. The operation went active and its operator started at 06:55. Three swaps in, the drill asked for both reviews.
The exchange’s review read every file against the canonical Uniswap V2 sources and found nothing. The mandate’s review found three things and filed one, an S4: “maxProofAge measures receipt arrival, not observation time; stale prices pass.” For newcomers: the mandate account refuses a swap whose price proof is older than 180 seconds. The proof is a receipt from tangerine carrying the pool’s reserves at the moment they were read. The contract stamped the receipt with the time it arrived on Satsuma’s chain, not the time the reserves were read. A receipt delayed by the relayer, or re-sent after a timeout, would reset the clock, and the floor below which a swap is refused would be computed from a price minutes old. No funds could leave beyond the operator’s own minimum, but the promise in the manual did not hold.
The assessor was asked which path the fix should travel, since this mandate could travel both: it was behind a proxy and also exposed a migration. It chose the upgrade, and said why:
The defect is in MandateAccount.onAccept. It sets
$.provenAt = uint64(block.timestamp), which is when the receipt arrives on Satsuma. That moment can be long after the reserves were read on tangerine, for example when RemoteAccount.onTimeout resends the receipt unchanged.
The defect was in the code the proxy runs; an upgrade reaches it; nothing that
holds funds needed to move. A fix was proposed in the same cycle, up to 500
compute units from the reserve, and approved at 90% confidence. The builder
rebuilt the mandate from its deployed sources with the issue as scope. The fix
proper is one line, $.provenAt = req.requestedAt, dating the proof from the
moment the instruction was dispatched, which is a lower bound on when
tangerine read the reserves; with it came a version bump and a test that
reproduces the filed scenario: a receipt re-sent after the market moves 20%,
delivered late, which now fails the age check as it should.
The new implementation was deployed on its own at 0xfa87…9e3. Then one vote,
executed from the timelock, called upgradeToAndCall on the proxy at
0xC19E…d64, recorded the claims as deployed, resolved the issue, and resumed
the product. The proxy’s address did not change. Its holdings did not move. Its
state, its counters and its last proof, stayed. The
operator never changed its target. The fix’s pull request was merged into the
product’s main branch at 07:35, because the chain now said the issue was
resolved, and the readable copy of the issue was closed.
Meanwhile the drills ran. During the liquidity pull, seventeen swaps failed in twenty minutes, and the operation’s second review said amend:
The failures were not random. Across reports 17 to 32 the failure count rose with almost every swap while the tUSD share fell from about 48% to 28.8%. The operator’s own need op0-need-1 says the live pool reserves had dropped about 1000x below the proven reserves. Swap sizes and minOut were still derived from those proven reserves, so tangerine rejected swap after swap.
The operator had filed a need on its own account, saying the pool it could see no longer matched the pool it had proofs of. The loop picked up the need as a proposal, “Size operation 0 swaps from fresh verified reserves and close inside the 47–53 percent band”, and the amended policy was voted and went live at 07:52:06. The operator restarted on it at 07:52:50 and closed, in band at 51.07% tUSD, thirty-six seconds later. The third review found the new policy had no evidence yet and said continue; the fourth found the operation closed on its objective and retired it, returning the two unspent reserves in the same payload.
| Review | Period | On-chain verdict | Recommendation | Council |
|---|---|---|---|---|
| 1 | 06:54–06:59 | both targets missed, on 3 settled swaps | continue (62%): too little activity to judge | 3 to 0 |
| 2 | 07:03–07:35 | 8 swaps met; failure rate 7,727 missed | amend (80%): never size a swap from reserves the live pool no longer matches | 3 to 0 |
| 3 | 07:38–07:52 | 26 swaps met; 0 bps met | continue (88%): the new policy is six seconds old | 3 to 0 |
| 4, final | 07:54–07:55 | both met | retire (72%): the operator closed itself in band | 3 to 0 |
Final state, read from both chains before they were stopped: 26 swaps executed,
19 failed (17 of them inside the pull), none timed out, 2 requests refused by
the mandate, holdings 51.07% tUSD, Satsuma’s recorded balances equal to the
real ones. Twenty-three Governor proposals, twenty-three votes, twenty-three
executions. Every drill passed, including the one that tries the
against every administrative function of the mandate and found it can call
neither setLimits nor upgradeToAndCall: the only thing that can re-point
the proxy is the timelock, and the only thing that moves the timelock is a
vote.
From 07:56 every cycle found every key result met and proposed nothing. At 08:24 a watcher that had counted twenty such cycles verified both chains and shut everything down. The run ended because the work was done.
The eleven runs
| Run | What it tested | Duration | Issues filed / fixed | Interventions | Swaps / refused / books / band |
|---|---|---|---|---|---|
| 1 | Products, manifests, invariants, the assessor | 6 h 41 | 0 / 0 | 3 | 19 / 3 / match / stopped mid-operation |
| 2 | The fix path, forced by review | ~8 h | 3 / 1 (redeploy) | 10 restarts | 10 / 2 / not read / retired |
| 3 | Refusing an unsafe fix (ADR 0019, first cut) | 4 h 47 | 1 / 0 (S1 stays halted) | 4 | 3 / 0 / match / halted |
| 4 | The migration path | ~15 h, two limit stalls | 3 / 2 (redeploy, migration) | 7 + one hand edit | 3 / 0 / match / paused |
| 5 | The same, on fixed code | 5 h 15 | 2 / 1 (migration) | 0 | 28 / 4 / match / stopped mid-operation |
| 6 | Every key result under the regime | 5 h 30 | 3 / 2 (redeploy, migration) | 1 restart | 10 / 3 / match / in band |
| 7 | Migration mid-operation | ~8 h 50 | 4 / 1 (migration) | 0 | 34 / 3 / match / in band |
| 8 | The proxy rule | ~9 h 45, 5 h idle | 0 / 0 | 0 | 11 / 3 / match / in band |
| 9 | Fix PR merged on resolution; dependency alert | 12 h 30, 7 h idle | 3 / 1 (redeploy) | 0 | 17 / 5 / match / in band |
| 10 | (stopped at the interface gate) | 1 h 36 | – | the stop | – |
| 11 | The upgrade path | 4 h 51 | 1 / 1 (upgrade) | 0 | 26 / 2 / match / in band |
Interventions are code changes to the loop with a restart, during the run, after a bug the run exposed; none changed a decision Satsuma made, and the one hand edit (run 4) restored a journal a loop bug had corrupted. “Issues fixed” counts those resolved on chain by a vote within the run. Every run was on Claude Opus 5.5, billed to a subscription rather than an API; two runs were stalled for hours by its usage limits, and lost nothing.
What was fixed in advance, and what was fixed on the way
The claim of this article is that in five of the last seven runs every decision came out of Satsuma’s process; the other two were a restart for a loop fix (run 6) and a stop (run 10). The boundaries:
Fixed in advance by the experimenter: the objective and key results; the world on tangerine, all scripted; the drills, now including a request for both reviews three swaps in and a scan of what the deployment key can call; the severity scale and its floors; the four outcomes a review may choose from; and the paragraph of the brief that asks for manuals, timelock-held administration, and a declared way to change each contract.
Added to the loop between runs, by a person: the product registry, the manifests, the assessor and the adversarial review (before run 1); the declarations and the refusal of unsafe fixes (before run 3); the migration payload (before run 4); the proxy rule, the upgrade payload and the merge on resolution (before run 8); the assessor’s choice of path when both are real (before run 10). Each is recorded as an architecture decision, or an amendment to one, before the run that tested it.
Fixed during a run, by a person, after the run exposed it: twenty-five interventions across runs 1, 2, 3, 4 and 6. Two were housekeeping (a missing package, a restart to take the loop off an API key); the rest were loop bugs: a float where an integer was needed, a manifest file renamed from under its on-chain reference, KPI identifiers shifted by a concurrent adoption. None in the runs the table marks with a zero.
Decided by Satsuma: what each product’s invariants are and how wide; what it found when it read its own code, and how severe; whether a signal was a defect or a market condition; whether to file at all; which path a fix should travel, when both were open; the fix itself, down to the line; every policy and its amendments; and every vote.
What this shows, and what it does not
It shows that ownership closes on chain. A deployed contract is opened, measured, reviewed, found wanting, fixed and attested, with each step a vote or a verified fact and the reasoning behind each judgment hashed on chain. Over eleven runs that happened nine times, on code Satsuma had written itself and built afresh each run.
It shows that the refusals are the safety. The assessor declined to file a drill’s failures as a defect, declined to treat a float’s rounding as an exploit, and withheld low-confidence Critical findings as questions rather than alarms. The loop declined to redeploy a contract holding funds when it had no safe way to fix it, and left an S1 open rather than close it badly. A system that fixes everything it sees is more dangerous than one that files what it cannot yet fix.
It shows that a builder’s claim about its own contract is checked. Twice the manual said upgradeable and the chain said there was no proxy. The correction went through a vote, and the fix went the way the contract actually allowed.
It shows three ways to change a contract that is live, and when each is right. Four migrations moved the position to a successor and the books balanced every time. One upgrade changed the code under the position without moving it. The assessor chose between them for a reason it wrote down.
It does not show a Critical defect fixed by upgrade. The upgrade of run 11 fixed an S4. The S1s and the S2 were fixed by migration, in builds that had no proxy. Whether the loop halts a proxied product on an S1 and upgrades it in place, with the operation paused and resumed around the vote, is untested.
It does not show learning between runs. The same weakness in the mandate’s price floor, a floor computed from reserves an attacker can skew, was filed in runs 3, 4, 5, 6 and 7; the proof-age defect run 11 fixed had first been filed in run 2. Each run builds the mandate from scratch, and each builder made a version of the same mistakes, and each review caught them. The reviews work; the builder does not remember. Within a run, Satsuma owns what it builds. Across runs, it starts over.
The reviewer and the builder are the same kind of model. The adversarial review found real defects, several of them known classes of weakness in the code it was reading. It missed what it missed, and nothing here measures that. An independent audit of any of these products would be the way to find out.
The council rarely says no, but it did. It refused two manifests with no invariants in run 3. It passed every fix, every amendment, every review recommendation it was given. Twenty-three of twenty-three votes in run 11 were 3 to 0.
The limits of the setup are those of the first two experiments. Both chains had a single validator and ran on one machine. The market, the relayer and the drills were run by the experimenter. The tokens are worthless. The repositories are private.
What the first experiment tested was whether a system could be given a goal and build its way to acting on it. The second tested whether it could keep acting well. This one tested whether it could keep what it built: know what must stay true of it, notice when it does not, say how bad that is, stop if it must, and fix it without losing what it holds. Over eleven runs, by the last one, with nobody in the room, it did.
Every figure comes from the recorded runs of 13 to 17 October 2026, whose records are kept with the experiment. Run 11, the one described in detail, began on 17 October at 03:33 UTC and ended, by its own hand, at 08:24. Models were Claude Opus 5.5, called with no memory between calls. Where an assessor’s or reviewer’s reasoning is quoted, the quote is from the file whose hash was recorded on chain. The two decision records are ADR 0018, “Products are owned”, and ADR 0019, “Fixes to stateful products are migrations”.