Repository navigation
auxiliary inventory: every request says what it is for, a dead role goes, and the prefix gate weighs the real belt (review needed) - #996
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
EvidenceLaws gate (Spark,
|
| home | prompt | rows | untagged | tags |
|---|---|---|---|---|
/tmp/afh996 |
read go.mod; a wide "review every package" ask | 62 | 0 | turn 50, reflex 8, title 4 |
/tmp/afh997 |
propose a task + run bash | 30 | 0 | turn 21, markreader 5, title 2, caption 2 |
/tmp/afh998 |
describe an image at a path | 41 | 0 | turn 17, tool:view_image 10, title 6, tool:read 6, caption 2 |
133 rows, 0 untagged. tool:view_image and tool:read are the ask-tool path whose tag moved from its own WithCallTag to the door, and it still writes the same word; markreader and caption are errands through callRole, whose role still becomes the tag.
Honest limit on the live evidence: these runs exercise the turn, the ask tool, the errand ladder, the namer and the captioner. They do not fire the nine paths this PR renamed — guardian, vision, shaper, spell-out, intake, planner, designer, handoff draft, subharness step — each of which needs a configuration or a turn shape a headless --once does not reach (an approval gate with a guardian model, a model that cannot see images, a turn long enough to hit the ceiling, the TUI's ctrl+r). What covers those is the structural law, which walks all eleven call sites and fails the build on any of them going blank; that is a stronger guarantee than any one live run, but it is a different kind of evidence and worth saying so.
Prefix gate
the fixed prefix is 53025 bytes (~13256 tokens): prompt 19114 + tools 33911 over 23 tools
it is 5025 bytes over the 48000 target — see this file's head for the bill and who owes it
--- PASS: TestTheFixedPrefixStaysUnderItsBudget
--- PASS: TestTheLeanPrefixStaysUnderItsBudget (the lean prefix is 31341 bytes over 12 tools)
Every shape weighed, for the record:
| shape | tools | tool block | total |
|---|---|---|---|
| a conversation that remembers (shipped) | 23 | 33,911 | 53,025 |
| a conversation with memory off | 18 | 19,993 | 39,107 |
| a task node that may fan out | 19 | 26,232 | 45,346 |
| a worker with inherited reads | 16 | 17,822 | 36,936 |
| a lean conversation on a small window | 12 | 14,243 | 33,357 |
(the old gate's fixture, v3ShapedAgent) |
18 | 19,959 | 39,073 |
One red, and it is not this branch's
The full internal/session suite under load reported:
--- FAIL: TestC1AProposalCannotPutRepositoryWorkInTheCheckout (0.33s)
testing.go:1464: TempDir RemoveAll cleanup: unlinkat
/tmp/TestC1AProposal.../002: directory not empty
It is not in .github/known-red.txt. It passes -count=3 in isolation on this branch and on origin/dev (82624af79), and it is a t.TempDir cleanup race — something is still writing into the directory when the test ends — which is the load-sensitive shape CLAUDE.md says to treat as a bug report rather than rerun and move on. Nothing in this PR changes any goroutine's lifetime, so it is not from here; I am reporting it rather than absorbing it. A dev-under-load run to confirm it reproduces on the base is in flight.
The session red is
|
| branch | full-suite internal/session |
isolated -count=3 |
|---|---|---|
fix/auxiliary-inventory (be3e2c866) |
--- FAIL: TestC1AProposalCannotPutRepositoryWorkInTheCheckout — TempDir RemoveAll cleanup: unlinkat …/002: directory not empty |
pass |
origin/dev (82624af79) |
--- FAIL: TestHandingTheSameDecisionOverTwiceSaysItIsAlreadyHandedOver |
pass |
Two different tests, neither in .github/known-red.txt, both green in isolation, both red only when the whole package runs. That is the shape CLAUDE.md names as a bug report rather than a known red — but it is pre-existing on the base, it is not the same test twice, and nothing in this PR changes any goroutine's lifetime or any temp-directory handling.
So: not this branch's, reported rather than absorbed, and no line added to known-red.txt. Worth its own issue — the varying identity of the failing test is the interesting part, and it points at shared state or a cleanup race rather than at either named test.
Everything this PR is actually about is green: the laws gate, internal/roles, internal/manual, internal/e2e, internal/iconlaw, cmd/aforge-replay, and internal/session + internal/tui3 inside the laws run.
Live tag evidence, final tally
The two longer runs finished and one of them started a real task, so the node's own turns are in the log:
| home | rows | untagged | tags |
|---|---|---|---|
/tmp/afh996 |
62 | 0 | turn 50, reflex 8, title 4 |
/tmp/afh997 |
47 | 0 | turn 22, task 9, markreader 6, reflex 6, title 2, caption 2 |
/tmp/afh998 |
54 | 0 | turn 28, tool:view_image 10, tool:read 6, title 6, caption 2, reflex 2 |
163 rows, 0 untagged, covering turn, task, title, caption, markreader, reflex, tool:read and tool:view_image — i.e. the turn, a node's turn, the errand ladder and the ask tool, which are the three callers whose own WithCallTag this PR deleted in favour of the door. All three still write exactly the word they wrote before.
…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]>
8a22864 to
fe36b8b
Compare
…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]>
Three findings from an audit of this build's auxiliary machinery. All three are
things the code reports about itself incorrectly; none changes what any model is
asked or answers.
1.
RoleCompactionwas a priced dial wired to nothingAgent.compacttakes_ context.Contextand makes no model call — a compactionis two mechanical rearrangements of text the session already has, and the
summariser was deleted long ago.
RoleCompactionsurvived it: a constant, aTierHighassignment, a description, and a row on the Providers tab offering aperson a model for a call that never happens.
TierHigh's own five-minutepatience was derived from "a compaction summary over a full window".
Deleted, with the settings and manual text that offered it, and the patience
re-derived from the auditor — the call on that tier that really does read a
great deal before it writes.
2. Nine of 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
completeWithModelwith noprovider.WithCallTaganywhere above them. 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 forget, and forgetting it is silent.
It is an argument now —
callPurpose— so a call with no purpose does notcompile, and the door is the only thing in the package that calls
provider.WithCallTag(the turn, the ask tool and the errand ladder each usedto stamp their own).
auxiliary.go's claim thatcallRoleis "the one door all auxiliary providercalls pass through" was the false one, and now says what is true: it is the one
door every errand passes through, and it names what is legitimately not an
errand and why.
nohiddenwork_test.gois the law and it is on the laws gate: a blank purposefails the build, a
WithCallTagoutside the door fails the build, and the walkfails if it stops finding the door at all.
3. The prefix gate weighed a belt nobody runs
fixedPrefixBudget's doc comment has always said it bounds "the belt theshipping conversation door assembles". The fixture under it had no memory store,
no accounts hub, no standing items and no saved programs: 18 tools, 39,073
bytes, green with eight kilobytes to spare. A configured machine carries 23 and
weighs 53,025 — 5,025 over the 48,000 cap the gate was enforcing. Invisibly,
every day. Same shape as #576.
The gate now weighs the belt shape named
shippedBeltShape, the same "fullywired" definition
prompt_belt_test.gouses, through one shared helper, andboth arms — fixed and lean — weigh the shipped shape rather than a thin fixture. The figure becomes a
ratchet at the measurement with no headroom, 48,000 stays as the stated target,
and the test prints the shortfall on every green run.
The 5 KB is filed, not fixed:
standalone is 9,607 bytes, more than aquarter of the tool block and nearly twice the next heaviest. Trimming a schema
changes what the model is told, not a byte count, so it belongs to whoever owns
the standing belt.
Seams reported, not worked around
provider.NewClient— the standing sentinel,the memory tidy-up and the document reader — and never touch this door, so
they are outside the errand ladder. They have no
*Agentto reach it through.They are no longer outside the PURPOSE law: it now requires
withPurposeonevery own-client completion, per call, and the negative was planted on the
Spark (each of the three stripped in turn, each failing by file:line).
— that could go through
callRoleand gain the ladder's fall-through, thetier's patience and a journal row. That asks a second model on failure, which
is a behaviour change and does not belong in a fix PR.
subharness_intakebuilds its window oncontext.Background(), so aperson's interrupt does not cut it. Fixing it is part of the
callRoleconversion above.
What this PR deliberately does not do — issue #1010
An altitude review found the shape is one level too shallow in three places, and
all three are cross-package. They are filed as #1010, with #928 as the
spine, and they mostly DELETE what this PR adds:
law can only see
internal/session; five other packages reach a model byother roads.
provider.NewClienttaking the purpose, andcalllog.Recordrefusing an untagged row, deletes
withPurpose,purposeInheritedand theown-client second pass entirely, and makes the law one sentence module-wide.
lane.Rolerides on the record.cmd/aforge-replay'sroleOftable is a hand-maintained join this PR had to grow by four rows and correct
twice.
provider.WithCallRolebesideWithCallTagdeletes the table — andwith it the one rename this PR carries,
purposeSentinel = "standing-check",which moves a word collision rather than fixing it.
widestBelt,bare.WidestGrepDescriptionand the two normalisations inprefixbudget_test.goexist only becausegrepandload_capabilitysaydifferent sentences on different machines (169 bytes). One description
everywhere with the machine fact delivered at runtime deletes all of that.
That one needs the belt's owner:
bare's stated law says the opposite.Both holding actions now name #1010 where a reader meets them
(
clientdoor.go'spurposeSentinel,prefixbudget_test.go's residue note).Proof
go build ./... && go vet ./...clean.deepseek/deepseek-v4-flashcheckingcalls.jsonlhas arole on every auxiliary row and none untagged (see comment).
🤖 Generated with Claude Code