‹ all posts

Erik Rekola

TLA+ on my own tools, in numbers

2026-10-22

This is the fourth and last part of the series that began with Twelve days of TLA+ on my own tools, continued in Thirteen review rounds and one rollback and went on with Three outside reviews of my TLA+ models. It closes the series with the full numbers. Part 3 set a condition for a fourth outside review, and I did not keep it, so that comes up below too.

How long did the work run?

The first pilot started on 27 September and this post is written on 22 October, 25 days later. The table lists the stretches that carried the work.

DateWhat happened
27 SeptemberPilot on the script that removes sync conflict copies.
28 SeptemberSix more phases in one day: send chain, shared logs, pipeline, rescan obligations, ship and cross-posting scripts.
1 OctoberInventory of 27 model files with 23 distinct models. I created the library itself on 14 October.
8 to 9 OctoberFirst outside review read and verified. Part 1 published on 9 October.
10 to 12 OctoberGate fix session with 13 review rounds. Part 2 published on 12 October.
12 to 15 OctoberA plan of 12 items before the second review.
15 and 16 OctoberSecond and third outside reviews.
17 OctoberPart 3 published. Packages for the fourth review built.
17 to 19 OctoberFourth review verified and its fix chain shipped.
19 OctoberInternal audit of every TLA+ decision.
21 OctoberRe-evaluation of the fixes.
22 OctoberOvernight chain of five fix sessions, a full mutation run in 48 parts, this post.

How big did the model library get?

The library is a versioned folder of models with a manifest of the code each model covers. A drifted model is one whose covered code has changed since the model last ran, so its stored PASS describes older code.

Point in timeModelsDrifted
27 September, pilot3not tracked
1 October, before the library23not tracked
14 October, library created3220 at the first run
15 October, before the second review4121
17 October, before the acknowledgement8856
17 October, after the acknowledgement8817
19 October, internal audit115, of which 111 valid79
21 October, re-evaluation112 valid102
22 October, after the first fix session of the night116, of which 105 valid95
22 October, after the second11669
22 October, after the third1167

The 116 models are 105 valid, 4 replaced by later models and 7 marked historical. Their registry holds 880 stored runs: 351 pass runs, 394 vacuity runs and 135 liveness runs. A vacuity check shows that a property can fail at all. Drift grew again as the fix chain of the fourth review changed code, which is why 79 models had drifted by the audit. The acknowledgement of 17 October and the one of 22 October use different definitions, and the audit section explains why.

What real faults did the work find?

This is a selection. It has 14 rows, one of them four separate causes, across 11 tools or tool groups. Some came from the outside readers and the audit. Each either happened in real use or was reproduced against real code or a test copy of it, and not only in a model. Every fix chain also named smaller limits that are not in the table.

ToolFaultHow it was reproduced
Sync conflict cleanerDeleted the newest version of a file when sync restored an old version under the original name.Run on the unchanged script.
Sync conflict cleanerQuarantine folder names collided within one millisecond: old code lost 1 of 300 copies, fixed code 0 of 300.Real processes.
Send script and batch publisherFour separate causes of sending the same message twice.Real send loop against a fake mail server on the local machine.
Send scriptA late 354 reply after the end dot of an SMTP message counted as a certain refusal, so a retry delivered twice.Fake server.
Send scriptA late 4xx or 5xx reply after the end dot removed the reservation and a retry delivered twice. The fourth outside review named it as a limit, and the internal audit reproduced the double delivery.Replay.
Pipeline writerTwo overlapping additions lost the first record while three existing checks stayed green.Replay.
Log size registryTwo runs that read the same registry version silently lost each other's entries.Replay.
Ship scriptPushed another session's undeployed work and created public tags ahead of the deploy.Happened in a real ship on 28 September, and the model reproduced the sequence.
Publish scripts for dev.to and HashnodeDuplicate posts. In the model the old scripts broke in 28 of 48 configurations on dev.to and 30 of 48 on Hashnode. Both scripts are retired now.Fake server replay: the old script broke 7 of 8 cases, the fixed one 0 of 8.
Plugin hook state fileOne gate silently skipped its check in 20 of 300 rounds, 0 of 300 after the fix.Replay against the real hook code.
Batch importAn interrupted run stayed at 4 of 5 records after three retries.Real code.
Tag release scriptExited 0 and sent a release for a tag one patch ahead of the version in the tagged commit.Old code against the fix in a test setup.
Archive toolRestore exited 0 with "0 records updated" when it had updated nothing.Replay.
Hand-log writerExited 0 and printed PASS when the row to add was missing.Replay.

I count a fault only when it happened in the unchanged tool or was reproduced against it. A model's counterexample alone does not count.

What did the four outside reviews find?

Each review read packages of models together with the code they describe. Sonnet subagents checked every claim against TLC and the real code. A claim was rated high when, in the reader's words, a verdict or a fix might not hold for the real code. After the check I kept the high rating only for a silent loss that contradicts a general statement in one of my reports.

RoundDatePackagesClaimsConfirmedRefinedRefutedRated high before and after
18 October4, 19 models683929033 before, 2 after
215 October6, 35 models493514020 before, 2 after
316 October6, 55 models372413010 before, 0 after
417 to 18 October8, 82 models35201508 before, 0 after
All four18911871071 before, 4 after

Rounds 1 and 2 were read by Sol 6.1 and round 3 by Astra, all through ChatGPT at its Ultra effort setting. Round 4 was read by Opus 5.5, and I chose the reader and handed over the packages by hand. Opus 5.5 also led the fix work, so round 4 was not read by a model from outside the family that wrote the fixes.

The falling number of high-rated claims cannot be read as a quality trend, because the reader changed between rounds and the prompt changed after round 2. All four rounds also share one limit that the internal audit named: no claim was refuted in 189, and I never planted a false claim to see whether the method would refute it. The verifiers are the same model family as the implementers. Zero refuted therefore says little about the method.

What did the internal audit of 19 October find?

I asked for an internal audit, because I needed to be able to trust the decisions. Eight Sonnet reviewers read and did not run anything. They covered 85 distinct decisions and the questions of the four outside reviews, in eight tool families. For each decision they checked the code on disk, the test and mutation protection, the evidence, the model and its drift, and the status line.

The audit gave 116 verdict rows. The decision held in 36 rows, held while its model had drifted in 60 rows and needed refining in 20. In none did it fail to hold, and in none was it impossible to check. The refinements were nearly always an outdated status line or a moved line number. A few were a sentence that a later decision had overruled.

Three structural findings came out of it.

The acknowledgement by re-run could not fail. A TLC run checks the model, and the model does not read the code. On 17 October I had acknowledged 39 of 56 drifted models by reading the diff and re-running 508 stored runs with no deviation. The re-run added nothing, because it could only pass. Of the 111 valid models, 79 had drifted. A classification of the diffs gave 16 comment-only drifts, 60 with real code changes after the last run and 3 without a recorded hash. The gotchas log had noted this problem on 16 October, but the code, the skill and one decision still said "by re-run".

The mutation suite had not run whole for six days. On 13 October the last full run caught 786 of 788, and 110 mutations had been added since without a full run.

Third came the finding about zero refuted claims, described above.

The audit also found eight code issues, and I confirmed six of them on disk. One of them was the late 4xx or 5xx reply in the send script.

The full mutation run of the audit caught 896 of 898, and the two survivors were a classification error and not a hole in the protection. Each of two mutations of the model library tool was expected to turn one named test red, and each also turned red a control test that was defined to stay green. The fix added that control to the expected-red lists of both mutations.

Did the fixes have to be redone?

On 21 October I wrote that so much had changed in the workspace that the whole fix plan probably had to be redone. Three Sonnet subagents measured it. Of the 41 covered files, 8 had changed since the last fix. For three of them I measured the diff and none touched a fixed spot. For the other five I inferred it from the changelog and the file times, because no comparison copy existed. The checks covering them were green. Five limited mutation runs passed with 7 of 7, 25 of 25, 87 of 87, 79 of 79 and 71 of 71, and a full run passed 936 of 936. The suspicion did not hold when I measured it. One caveat stays: that full run used an older version of the mutation file, and the 24 mutations added after it ran only in limited mode.

What did the full mutation runs show?

A mutation changes one line of a tool. It counts as caught when the test named for it fails and the control tests stay green. A full run applies every mutation of the tools suite.

DateCaught of totalDurationParts
9 October577 of 5772 hours 37 minutes1
12 to 13 October786 of 788about 3 hours 45 minutes1
19 October896 of 898about 6 hours 40 minutes1
21 October936 of 9366 hours 29 minutes1
22 October, filtered load test of five tool families291 of 29128 minutes48
22 October1 002 of 1 00264 minutes48

Serial, the 1 002 mutations would take about 7 hours at the rate of the 936. The 48 parts took 64 minutes, so the run is about 6 and a half times faster. The machine has 32 logical processors and 48 GB of memory. During the run the processor averaged about 86 percent, memory about 56 percent and peaked at about 61 percent, with at most 199 node processes. The baseline of 249 named cases ran 1 086 times in the parts and the parent, and one run was red, a baseline case under load. Four mutations were run again alone and all four were caught alone.

The plugin's hook gates have a mutation suite of their own, which I do not add to this table. It caught 643 of 643 on 12 October and 828 of 828 on 22 October. The second run took 927 seconds as three parts, alone, because its timing tests fail under load.

A full run is now weekly. A scheduled task created on 22 October runs it on Sunday nights, and its first run is on 25 October. The export gate for outside review packages refuses a package unless the stored result of a full run passed at most 7 days earlier. If the tools have changed since that run, it only prints a note.

What did it cost?

For this work I have only subagent tokens, and only for some steps. These are the figures for the main steps. A few more exist for single steps, and I left them out.

StepSubagentsTokens
Review 1 verification, three workflows337 111 799
Gate fix session of part 2, through round 923 runsabout 7 820 000
Review 2 verification163 860 337
Review 3 verification16 and 1 collation3 085 356 and 354 764
Review 4 verification16 and 1 collation3 260 693 and 385 767
Review 4 planning21 325 087
Review 4 fix chain, four of seven sessions8, 2, 8 and 111 108 424, at least 220 342, at least 1 272 894 and about 1 800 000

The review 1 figure leaves out its 22 QA agents, which have no figure, and 2 baseline check agents with 248 981 tokens. In the review 4 fix chain I summed the figures of two sessions from their item rows. The audit used 8 subagents and the re-evaluation 3. On the night of 22 October the three fix sessions used 6, 6 and 11 subagents, and the next one about 6.

I have not totalled the lead model's tokens or the money for this work, and the transcripts could give both. Subagent tokens are missing for the pilot and the six phases after it, the second and third fix chains, the audit, the re-evaluation and the fix sessions of 22 October. I have not counted the cost of the outside readers or the number of Claude Code sessions either, and neither is the cost of the aborted runs.

What went wrong?

I picked five problems that taught me something.

Did the condition of part 3 hold?

Part 3 said that a fourth outside review makes sense only after the full mutation run and the drift runs, and with a reader other than Astra. I chose acknowledgement first and left the full run for later. A session built the packages of the fourth review on 17 October with 39 of 56 drifted models acknowledged and 17 not, and the full run came on 19 October. The condition did not hold, because the review ran before the full run, and the acknowledgement behind those 39 was later found unable to fail as well.

The corrected rule is in the tools now. No fifth review is planned, and a package for one needs a passing full run at most 7 days old.

What is still open?

Seven models are still drifted, in three families. Five belong to the tag release family: the tag layer was rewritten and the fresh-tag readback is not in those models. One is a send model whose own correction says it describes code from before the late-reply fix, and the corrected mode lives in another model. One is the hand-log model, whose code gained two paths the model does not describe. The choice between a refinement check and the status historical is mine and I have not made it.

Several environments are unmeasured. They are a mail server that answers wrongly, which only the scripted replies of my fake server cover, how often my sync client restores files, a full disk and file symlinks, which this account cannot create. The send reservation also rests on two assumptions I have not measured: that creating a file exclusively is atomic in a synced folder, and how the Sent folder of my mail provider behaves. Five load-sensitive timing tests behave differently under load than alone.

The audit counted 13 other open limits. One of them is closed since, and I have not rechecked the rest. A list sorted by cost and consequence still does not exist. One limit cannot be closed on the client side: a late success reply after a reset marks a message as sent.

What does this not show?

A passing model holds for its small constants and for the code it was run against. Of the 105 valid models, 90 carry an acknowledgement under the new definition, which requires reading the diff, a replay test and a mutation pass. Seven are still drifted and 8 never drifted.

The mutation figures show that every mutation was caught by the test named for it while the controls stayed green. They do not show that the tests cover every behaviour. The reviews show that the readers described the code correctly in 118 of 189 claims and only in a narrower form in the other 71, and four rounds of outside reading are a narrow sample.

Many fix choices were made by a session on its own recommendation. In round 3 the chain made 31 such choices, in round 4 about 43, and the first four fix sessions after the audit made 17. The audit found none implemented wrongly. I confirmed the 31 and the 43 in one blanket answer on 19 October without deciding them one by one, and the 17 since then are the sessions' own recommendations.

I made the counts in this post from my decision log, the review and audit reports, the mutation run files and the subagents' usage readings.

Frequently asked

Did the models find real faults in the tools?

Yes, and the outside readers and the audit found some too. Across 11 tools or tool groups the work exposed faults that I then reproduced against the real code or a test copy of it, such as a cleaner that deleted the newest file and a send script that delivered the same message twice.

What does 1 002 of 1 002 mean?

Every one of the 1 002 mutations, each a one-line change to a tool, made the test meant to catch it fail while the control tests stayed green. It says nothing about behaviour that no test and no mutation touches.

Does a passing model prove the tool is correct?

No. TLC checks the model, and the model does not read the code. A pass applies to the code the model was run against, so a model whose code has changed needs a diff read, a replay test and a mutation pass before it says anything about today's code.