Repository navigation
session: the turn seals when the answer is done, and the route judge is spent when it lands (review needed) - #1011
Closed
santoshkumarradha wants to merge 35 commits into
Closed
santoshkumarradha wants to merge 35 commits into
santoshkumarradha wants to merge 35 commits into
Conversation
…ed a model since the summariser went RoleCompaction was a priced, described, settings-visible dial wired to nothing. [Agent.compact] takes `_ context.Context` and makes no model call at all — a pass is two mechanical rearrangements of text the session already has — so the row on the Providers tab offered a person a model for a call that never happens, and TierHigh's patience was derived from a summary nobody writes. The role, its tier assignment, its description and the settings and manual text that offered it all go. The high tier keeps every tenant it actually has — the auditor, the shaper, vision, the repair, the careful part of a division — each registered from the package that makes the call. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…thing that spells it NINE OF THE ELEVEN CALLERS OF THIS PACKAGE'S MODEL DOOR REACHED THE WIRE ANONYMOUSLY. The guardian, vision, the shaper, the spell-out, the subharness intake, the run planner, the harness designer, the ceiling's handoff draft and a saved program's own AI step all called completeWithModel with no provider.WithCallTag anywhere above them, so the model-call log could not say what any of them was: 2,309 of the 2,839 untagged finishes in the ten days to 2026-09-10 are this package's, and with them the answer to "what was this build spending that model on all night". The tag was a context value a caller could set or forget, and forgetting it is silent. It is an argument now — clientdoor.go's `callPurpose` — so a call with no purpose does not compile, and the door is the one thing in the package that calls provider.WithCallTag. The three callers that did stamp their own (the turn, the ask tool, the errand ladder) pass a purpose instead, which is one spelling of one fact where there were four. AND auxiliary.go's OWN CLAIM WAS THE FALSE ONE. It said callRole was "the one door all auxiliary provider calls pass through" while ten calls went round it. It is the one door every ERRAND passes through, which is a narrower and true claim, and the sentence says so and names what is legitimately not an errand — vision streams into the room in the person's own turn, the handoff draft is billed to the turn on the turn's own model, a program's step runs on no role's model at all. nohiddenwork_test.go is the law, on the laws gate: a blank purpose fails the build, a WithCallTag outside the door fails the build, and the walk fails if it ever stops finding the door. No behaviour changes. Every call goes to the same model with the same options; what is new is that its row in the log has a name on it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…carries THE GATE WAS POINTED AT A SHAPE NOBODY RUNS. fixedPrefixBudget's own doc comment has always said it bounds "the belt the shipping conversation door assembles". The fixture under it was v3ShapedAgent: a conversation with no memory store, no accounts hub, no standing items and no saved programs — eighteen tools, 39,073 bytes, eight kilobytes of headroom, green every day. A machine somebody has finished setting up carries twenty-three tools and weighs 53,025 bytes, which is 5,025 OVER the 48,000 cap the gate was enforcing. It has been over for as long as the shape existed and the gate could not see it, which is #576's shape exactly. The gate now weighs beltShapes[0] — "the shipping conversation, fully wired", the same definition prompt_belt_test.go uses, through one new helper so the two cannot drift apart again. AND THE FIGURE BECOMES A RATCHET. "The measurement plus a tenth" was right when the number came out of a diet that had just been paid for. This measurement is not the end of a diet; it is the first honest weighing of a prefix that had never been weighed, already past what the last diet aimed at, so a tenth on top would be five more kilobytes nobody chose. It is the measurement and nothing else, and anything that grows the shipped prefix now fails the build. WHAT IS OWED IS FILED, NOT FIXED. 48,000 stays as the stated target and the test prints the 5,025 shortfall on every green run. The bill is one tool: `stand` is 9,607 bytes, more than a quarter of the whole tool block and nearly twice the next heaviest. Trimming a schema is a change to what the model is told, not to a byte count, so it belongs to whoever owns the standing belt rather than here. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…s stated now TestEveryTagTheBuildWritesResolvesToARoleSomebodyDeclared walks the tree for provider.WithCallTag. internal/session has exactly one of those now — the relay line inside the door — so the law was reading a variable it could not resolve and finding fourteen tags for a table of thirty-seven. It reads the door's own call sites instead, which is the same thing the build does: the purpose argument is what reaches calllog.Record.Tag. The `callPurpose(...)` conversion comes off before the argument is read, a role worn as a purpose resolves to internal/roles' vocabulary (over-approximated, because this law is safe answering with more tags than one site writes and holed answering with fewer), and the door's own constants are read from their declaration the way roleWords already reads internal/roles. AND IT IMMEDIATELY FOUND THE TWO CALLS THAT HAD JUST BEEN GIVEN NAMES. The ceiling's handoff draft and a saved program's AI step now write tags, and nothing priced them: both are lane.RoleAuxiliary and both have a row. The draft rung is the dearest side-call on the ceiling road and it has been invisible to every reading of calls.jsonl until now. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…isters The models-and-cost page's roles table is described on the page itself as 'built from what is registered in the running binary, so it is the truth about this build'. It listed `compaction`, which nothing registers any more, and the crew's careful line and its account of what aforge calls on your behalf both named the summary a compaction keeps — a call that has not existed since the summariser was deleted. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…ritten on The first spelling of the ratchet was 53,100 — the laptop that weighed it said 53,025 — and it failed on the runner that proved it, at 53,132. The prefix is not the same size everywhere: `grep` says a longer sentence about itself where ripgrep is absent, which is 134 bytes on every request of every turn. So the gate weighs what the WIDEST machine pays. bare.WidestGrepDescription is the one place that knows which of the two sentences that is, and widestBelt swaps it in wherever the gate runs, with a law that fails if the tool it names ever leaves the belt — otherwise the budget would quietly get easier. The residual is the shelf: load_capability lists the groups THIS BUILD has, 27 bytes more on a machine that can edit video. That is filed in the file's head rather than fixed here, because naming what a build COULD have is a change to what the model is told. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…in it Deleting RoleCompaction left the word in every config.json that had pinned it, and the panel re-serialises the whole `models.roles` string on any change — so the next time that person moved ANY OTHER pin the write came back `"compaction" is not a role`, and every role pin on the machine was unchangeable until somebody hand-edited config.json. The deletion had no reading for what it deleted. roles.Retired is that reading: a one-way list of the words this build used to answer to, with the line that says what happened to each. It is the only memory the build has of them, and without it a dead word is indistinguishable from a typo. ParseModelRoles drops those keys and says so once per process; writeModelRoles takes them out of the TEXT, which is what is actually stored, so a person who changes one pin stops re-saving a sentence about nothing. The test is the exact string a pre-deletion profile holds. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A compaction is two mechanical passes over the transcript; it has never asked a model. Six comments and two manual lines still offered it as the example of a billed auxiliary errand — the pocket the title comes out of, the thing the careful tier is for, a non-streamed call like a harness node — which is a sentence about a call that does not exist, and was the figure the careful tier's own patience was derived from until this PR re-derived it. The settings hint for `careful work` no longer promises it either: what that tier actually buys is the check on finished task work, the brief a task is shaped into, and reading an image. The transcript NOTE a compaction writes is still called a summary in agent.go, because that one is real. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
`const spellOutRole roles.Role = "spellout"` lived in internal/session. It registered, resolved and tagged its call correctly — and reached the cost report as a word nobody had written down, because roleWords parses internal/roles and nothing else. roleOf prices an unrecognised tag as a background errand with nobody waiting, so the ONE auxiliary a person sits and watches was the one counted as if it did not matter. Nothing failed; a number was quietly wrong. The word moves to roles.RoleSpellOut beside its siblings, the Register call stays at the call site, and TestEveryRoleWordIsDeclaredWhereTheVocabularyIs fails the next one. And the replay walker stops enumerating receiver spellings. callName flattened one selector level, so three of the six rows in that switch — `p.agent.completeWithModel` and friends — rendered as "" and could never match: the ladder looked complete and was half dead. methodName asks the only question the law has, which is the method's own name. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
THE LEAN ARM HAD THE SAME HOLE THE FULL ARM WAS REPAIRED FOR. leanShapedAgent built its own thin config — no memory store, no accounts hub, no standing items, no saved programs — so the arm that bounds the prefix for the person with the LEAST room weighed a belt without `stand` (9.6 KB on its own), `remember` or `search_conversations`, none of which leanCapabilityGroups shelves. It is the shipped shape on a small window now, which is what its own comment always claimed. And three ways the number could go back to depending on who ran it: beltShapes[0] read the fullest shape only until somebody put a row in front of it. beltShapeNamed asks for it by name. WidestGrepDescription returned the fallback sentence because that one is longer today; it now measures both and returns the longer, and widestBelt ASSIGNS rather than comparing — a swap that only fires when it makes the number bigger is a swap that stops firing silently. load_capability names the groups this BUILD has, and `edit_video` exists only where ffmpeg is on PATH — 27 bytes of prefix that depend on a program nobody installed on purpose. loadCapabilityDescription takes the members as a question now, so the gate can ask for the widest answer instead of this machine's. Both budget figures are re-measured in the next commit, on the Spark. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
… say what they are THE CAPS. Full 53,141 and lean 44,989, both weighed on the Spark against the shipped shape at the widest machine, both with no headroom, both printing the shortfall against the target they owe on every green run. PERF.md moves in this commit, which is the rule for a cap. THE ROADS. memory_consolidate.go, standing_run.go and tools_doc.go each build a provider client the one door does not make — a longer timeout, static routing, a document config — and complete on it directly. That is a real difference, so the answer is not to force them through the door; it is to make them say the word the door says. clientdoor.go's withPurpose is that, and it is still the only file in internal/session that spells provider.WithCallTag. The two errands this build runs most often with nobody there reached the call log with NO TAG AT ALL, which made them indistinguishable from a turn that had lost its name. The standing check is `standing-check` and not `sentinel`, because cmd/aforge already writes that word for the resident's quorum errand and two calls under one tag is a reading of neither. The law now pairs the halves: a file that builds its own client owes a withPurpose, and it fails if it finds fewer than three such files — a structural law that matched nothing would pass for ever. And `compaction` comes out of the replay role table: no build can write it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A file-level pairing is satisfied by one road naming itself while the road beside it stays anonymous, which is the exact shape of the failure this law exists for: the forgettable one is always the second one. Every completion in a file that builds its own provider client must now carry a withPurpose on the context it is handed — inline, or through a local the purpose was folded into. standing_run.go writes the purpose four wrappers deep and completes on `callCtx` several lines later, so the reader follows assignments to a fixed point rather than looking only at the argument. And the second pass counts the CALLS it judged, not the files: three files that build a client and nought completions in them would pass in silence. cmd/aforge-replay's two doc comments stop citing auxiliary.go's deleted WithCallTag line and point at clientdoor.go's callPurpose, which is where the tag is spelled now. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
TestEveryRoleWordIsDeclaredWhereTheVocabularyIs caught `const standingSentinelRole roles.Role = "sentinel"` in internal/session the first time it ran, which is the second instance of exactly the defect it was written for and the reason it is a law rather than a note. The word moves to roles.RoleSentinel; the Register call stays at the call site. And with `spellout` finally visible to roleWords, the tag law asked cmd/aforge-replay to say what it prices as — so the table gains the row, which is spellout.go's own lane.RoleAuxiliary and not a second reading of it. That row carries the seam it exposes: a person SITS AND WATCHES a spell-out, and an auxiliary is by definition a call nobody's seconds are being spent on. Either the lane role or the product is wrong there. The table's job is to agree with the build, so it does, and says so. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…g on the machine THE RETIRED LEDGER IS DELETED. It was a map of the words this build used to answer to, so a dropped pin could be told apart from a typo — and it could not earn its keep. Both are a pin no call will ever consult, both should stop being written back, and the only reader who can act on either is the person adding a word right now. Keeping it meant every future deletion owed it an entry, and the day somebody forgot, the old failure would come back exactly as it was. So the reading is symmetric: a pin for any word that is not a role is dropped, the writer refuses only a name being ADDED, and nothing dead is ever written back. The row DRAWS what is acted on (config.LivePinsAt) rather than the raw stored text — it used to draw `compaction: some/model` as a live pin while the list under it had no such line — and the panel says "no longer a role — that pin is ignored" to the person about to lose it. It is a sentence and not the log.Printf this had, which was the only one in internal/config and landed on stderr in every headless and --json door. AND THREE MORE MACHINE-DEPENDENT NUMBERS. The lean page interpolates the working directory, so that arm measured 45,034 on macOS against 44,989 on Linux and failed — a fixture artefact worth tens of bytes in the gate whose entire purpose is to be one number for everyone. The lean arm was also being handed the FULL shape's load_capability sentence, making its figure a hybrid of two belts that neither pays. And the replay table priced `vision` as an errand nobody waits on while image.go, its only writer, sets lane.RoleTalk with the reasoning beside it: a look at an image streams into the room during the person's own turn. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
THE DEBT WAS INVISIBLE. It was a t.Logf, and nothing in the Makefile passes -v —
so the one line saying the shipped prefix is thousands of bytes over what it is
supposed to be was printed where no human and no CI log would show it. The cap
is the TARGET plus a dated waiver now, one per arm in one place, and a waiver
only ever shrinks: known-red's discipline applied to bytes. Growing the prefix
takes a diff with somebody's name on it.
ONE WEIGHING. The two arms were forty lines each, written twice, and the copies
had already drifted — only one printed its shortfall, and it was the arm with
the smaller debt. reportPrefix is called twice.
ONE SHIPPED SHAPE. v3ShapedAgent WAS the second opinion: its own thin config,
eighteen tools, weighed by four gates including both budgets. It is
shippedShapeAgent now. beltShapes' lean row built a third config again, so the
belt law and the prefix gate were measuring different lean conversations under
one name; both call buildShippedConversation.
ONE VOCABULARY READER. purposeWords and roleWords were the same function with a
different path; they are constWordsOfType, read once, with the empty-result
panic inside it. The role law catches the CONVERSION spelling too —
`roleRepair = roles.Role("repair")` was invisible to it — and now also reads
every roles.Register argument, which is the spelling that cannot be worked
around. roles.RoleRepair is declared with the others.
And the manual stops teaching a summarizer rung, its `session: summarizer
returned nothing` string, and a focus appended to its instructions. None of that
exists: a pass stubs and folds, asks no model, and never folds your own words.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The working directory was a fixture artefact worth tens of bytes; the Workstation line is runtime.GOOS/GOARCH and worth one, which a ratchet with no headroom fails on just as hard. Both are normalised at the one place that needs them to be, and the product still tells the model the truth about the machine it is on. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
… price
The lean figure went 44,989 → 44,920 because the page's working directory
stopped being a fixture artefact and the lean arm got its OWN shelf sentence
instead of the full belt's. That is sixty-nine bytes off a number that had been
measured on a hybrid, and the waiver comes down with it — which is the whole
behaviour the ledger exists to have.
And `repair` reaches the replay table. It only became visible there when the
role stopped being conjured at its call site as roles.Role("repair"): a word
spelled that way is invisible to every reader of the vocabulary, which was the
defect, and the law that now catches it found this the moment it could see it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…ent question A REGRESSION I INTRODUCED, CAUGHT BY THE SUITE. Making the read drop any word the REGISTRY does not know threw away `imagegen:paint/model` — a real pin doing real work. A role gets into the registry by registering, which is how it gets a tier, and a role with no tier never does: a painter is chosen by a pin or not at all, since a chat model in tiers.high is not a statement about painting. So the registry was never the answer to "what are the roles". roles.Vocabulary is that answer and roles.Known is the reader's door to it. The list is hand-written, which is a thing that falls behind, so a law parses the file it lives in and fails when a declared role is missing from it or a listed one is not declared. The refusal lists the vocabulary too, so a person is shown `imagegen` as a name they may pin. And the retired-pin test seeds the row the way a profile written before the deletion holds it — straight into the file, because the writer is exactly what would refuse it today and that person never typed it today either. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The `standing-check` rename and [widestBelt] are both measurements around a problem rather than fixes for it, and both are deferred to a cross-package wave rather than grown here. Each now says so where a reader meets it, and names issue #1010 (spine: #928) as the place it is deleted. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…n's file writeModelRoles forgives a retired word it FINDS and refuses one being TYPED, and it asked parseModelRoles which of the two it was looking at. That reader drops every retired word by design, so the stored row it handed back could never contain the one word the check existed to find: a name that had sat in the file since before the deletion read as a name typed for the first time, and a machine with an old `compaction:` pin could not save a change to any other pin. The set is read off the stored TEXT now — storedRoleWords — because every reader above that line drops what it does not recognise, and the dropped word is exactly the word this question is about. The asymmetry gets its own test: the same word, forgiven when found and refused when typed. The old test only ever typed a word that was never a role, so the half that broke was the half nothing watched. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…spent when it lands THE POST-TURN JUDGE HELD THE SEAL. `judge.takeAtTheEnd()` stood between the model's last word and [Agent.sealTurn] and blocked on a cheap screen plus, on a yes, a mastermind confirm — measured at up to half a minute with the answer already fully written on the person's screen, under a phase line reading "whether that should be work". Parked behind it were the seal, the usage row, the next Submit and the follow-up drain. The turn's job was never to know WHETHER the ruling had arrived. It was to say when one may be SPENT — past the reader that could re-open the turn — and it still says exactly that, through a fourth verb on the mechanism that already owns this shape: [sidecar.spendWhenItLands]. The answer is handed over the instant it lands, on the reading's own goroutine, with every one-answer rule [sidecar.take] keeps. AND THE READING MOVES OFF THE TURN'S CONTEXT, because otherwise not waiting for it would be cancelling it. It is the one reading in the package whose EFFECT lands outside the turn — a yes starts a task, which is as good a minute later — so it runs on the session's own lifetime ([afterTurn], which also names the two older hand-written spellings of that bargain and why they are not folded in yet). The told-after line goes to whichever stream is live when it arrives, which is [Agent.sayMemory]'s door. AND THE LAW GREW THE FIGURE IT WAS MISSING. journalPace measured the front of a turn and the middle of it; the END had no name, which is where this wait stood unnoticed. `sealMs` is the model's last word to the seal, and loop_speed_test.go now holds it to being bounded by the re-open reader — the guardian's named exception read at the other end of the turn — and by nothing else. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…h is not evidence THE MEASUREMENT FIRST, BECAUSE IT IS THE POINT. The pace ledger holds THREE rows on this machine and exactly ONE has a recall beside it (2026-09-12T03:06Z: sendMs 1, firstWordMs 7,389, aside recall / recall:reasked / mark). journalPace landed days ago, so there is no population here to derive a share from, and the 7.4 s the audit quotes is that single row. WHAT THE ROW DOES SHOW IS A HOLE IN THE INSTRUMENT. loop.go's law asserts `message → the main request on the wire ≤ 50 ms`, and this turn passed it at one millisecond. The request that left in that millisecond was then THROWN AWAY: the recall landed 5.6 s later, cut it, and the request that carried the person's answer did not leave until then. The row reported the law kept on a turn where a reading cost the person seven seconds — satisfied in the letter by sending a request nobody intended to read. So the row carries `reaskMs` now: the first send to the cut, which is exactly what cutting costs over having assembled the block before the first request. The two moves reach the person's first word at the SAME instant — the recall's own duration plus one time-to-first-token — and the re-ask pays for two requests to get there. Whether that second request is cheap for having warmed the prefix cache is the whole question, and it is a question about a population of turns. AND NOTHING IS CHANGED ABOUT THE MOVE ITSELF, deliberately. Choosing between cutting and pre-assembling on one row would be a threshold nobody could derive, which is the one thing the architecture bar refuses outright. This is the instrument that makes the choice decidable in a week instead of guessable today. Two laws hold it: a re-asked turn must price its throw-away, and a turn that threw nothing away must price nothing (the emptiness law on the same field). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The screen page said the two end-of-turn readers are asked at the same time and the stage lasts as long as the slower of them. Only one of them can keep a person now, and the page says which, why, and what happens when the other one answers late. The tasks page gains the same fact where somebody looks for it: the line can arrive after the turn ended, and nothing about it is different for being a moment late. The status word 'whether that should be work' is deleted from the corpus with the wait it named. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…lose bargain [afterTurn] is new machinery and its whole claim is a pair: nothing starts once the session is closing, and nothing in flight can hold a quit. Both are the kind of thing that is true when it is written and quietly stops being true, so they are tests: a settle cancels the work in flight and returns when it unwinds, a settle is bounded by its grace against work that ignores its cancellation, a second settle is a no-op rather than a panic, and a nil lifetime answers every door. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…eld for it [sidecar.spendWhenItLands] reports whether it TOOK the reading, which is the one-answer rule read from the caller's side. The judge holds a place on the session's lifetime for its reading and releases it from inside the spend; a sidecar that was already spent would hand that closure nothing, leave the reading counted for ever, and make a closing session wait out its whole grace for work that had already finished. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…ht it
THE TOLD-AFTER LINE COULD BE LOST, and that is the defect the judge change
exposed. `this looked like work, so task 4 started: …` is the one thing that
stops a task appearing on the rail from being a surprise, and the ruling that
starts it is spent whenever it lands — which is sometimes after the turn sealed
and closed its hub. Sent there, it was a line nobody read.
There is ONE mechanism for that shape now and it is [Agent.sayLate]: said on
whichever stream is live, held for the next turn when there is none. It was the
memory brain's own queue, which meant a session with memory OFF had no queue at
all and the second caller's line would have been dropped in silence — so the
queue moves onto the session, the drain moves to the top of every turn (the
memory refresh it used to live in does not run at all without a brain), and
`memoryBrain.said`, `queueNotice` and `takeNotices` are deleted.
── AND THE FIXTURES THAT LEANED ON THE OLD ORDERING ──
Four tests encoded "the turn waits for the judge", each in a different way, and
each is rebuilt on the fact rather than on the timing:
· the rate-limit test asserted the told-after line was on THAT turn's events.
It waits for the task and reads the line's NUMBER, because the first turn's
line legitimately rides the second turn's stream now.
· the multi-part-ask test did the same and now accepts the line on either
turn — the person being told is the claim worth holding.
· the raced-confirm test counted the post-turn read on the spot. It waits for
it, because the read is still in flight when the stream closes.
· M12's fall-through fixture PARKED ON THE JUDGE to hold a turn open past its
last boundary. That prop is gone. It parks on the remains reader instead —
the one end-of-turn reading the turn still waits for, because it decides
whether there is a turn to seal — and grinds to the ceiling's price gate to
reach it, which makes the window an honest property of the turn's shape.
And two new tests for the door itself: a line said with no turn live reaches the
next one, and a session with no brain holds one just the same.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
… beside-work is waited for The turn stopped waiting for the route judge, so twelve tests that read the judge's effects off the turn's OWN collected events were reading "not yet" as "never". Two readers replace that, and they are the only two shapes there are: routeNotice(agent, collected) — the line wherever it landed, asked once and now: this turn's stream when the ruling beat the seal, the session's held queue when it did not. It is the reader for a negative. routeSaid(t, agent, collected) — the same reading, waited for. A ruling that starts work is spent whenever it lands, and a positive has to wait for it. And routeSettled(t, agent) waits for exactly what Close waits for: the after-turn lifetime's own running work, registered while the turn is still going. "Was the judge asked", "did work start" are claims about the session now, not about the instant the turn sealed. The rate-limit test counts what STARTED rather than what was announced: the told-after line rides whichever stream is live when the ruling lands, so counting lines would charge turn 1's announcement to turn 2 and read the gap as broken. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…t ran TestTheRateLimitHolds counted the fixture's started RUNS, which begin on a goroutine of their own after admission — so on a machine that schedules the runner a moment late, turn 1's task is counted against turn 2 and the gap reads as broken. It failed that way on the Linux runner, deterministically, where the laptop it was written on never saw it. Admission is the judge's own act, inside the ruling the turn now waits for (routeSettled), and it is the thing routeJudgeGap is a rule about. The runs are still asserted, once, at the end. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…er lane's 997 is fix/pick-clock-under-load, merged. This lane is 1011. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
santoshkumarradha
force-pushed
the
simplify-D
branch
from
September 12, 2026 15:35
01645b8 to
9370ffd
Compare
…filename The rename landed and the line inside it did not. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
|
Member
Author
|
Closed per owner request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane D of the
simplifywave: two behaviour changes, both about work that aperson was made to wait through for no reason they could see.
Stacked on #996 (
fix/auxiliary-inventory), which is the bug-fix half ofthe same lane; this PR's base is that branch, so the diff here is lane D's
behaviour changes alone. Merge #996 first and this rebases to nothing.
1. The judge no longer holds the seal
The end of a turn waited for BOTH end-of-turn readings. One of them — the
work-or-words judge — stood between the model's last word and the seal, blocking
on a cheap screen plus, on a yes, a mastermind confirm. The answer was already
fully drawn on the person's screen. Parked behind that wait were the seal, the
usage row, the next Submit and the follow-up drain.
The turn's job was never to know whether the ruling had arrived. It was to say
when one may be spent, which it still says, and then walk away:
judgeRace.takeAtTheEnd(which blocked) isjudgeRace.spendWhenItLands(whichdoes not). A yes that lands after the turn ended starts its task then.
That needed a lifetime for a reading that outlives the turn that bought it —
afterTurninsidecar.go— because the judge's whole effect is to start atask, which lands outside the turn anyway. A reading that refuses to be taken
still releases what was held for it, and a reading beside a turn stops writing
into a conversation that has closed.
The status line's
whether that should be workis deleted, because the wait itnamed no longer exists. Only the reader that can RE-OPEN the turn is still
waited for.
2.
reaskMs— the recall's re-ask is pricedThis is an instrument, not a fix, and it is here because the ≤50 ms law can be
satisfied by a request that is then thrown away. On a turn where the person
waited 7,389 ms for their first word,
sendMsread 1. The recall hadcut its own request and asked again, and nothing measured the gap.
journalPacenow carriessealMs(the model's last word to the seal) andreaskMs(how long the request a recall threw away was on the wire), in placeof
sendMs/firstWordMs/stepGapMs. The share of turns that pay for acut-and-re-ask still cannot be derived from one ledger row — that is stated in
the change entry rather than claimed as closed.
The law
loop_speed_test.goasserted two figures; it asserts three, and the third isthe one that matters here: the seal must not be held by any reading whose
answer could be spent afterwards.
afterturn_law_test.gois new and on thelaws gate.
Proof
go build ./...,go vet ./...,gofmtclean.Spark against this exact head (tail in a comment below).
🤖 Generated with Claude Code