Repository navigation
The turn decides, informed; promotion inherits; the ceiling is a runaway net - #923
Merged
Merged
Conversation
santoshkumarradha
added a commit
that referenced
this pull request
Sep 11, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 11, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
force-pushed
the
feat/turn-promotes-itself
branch
9 times, most recently
from
September 11, 2026 23:49
0728a2e to
b2dd09d
Compare
santoshkumarradha
force-pushed
the
feat/turn-promotes-itself
branch
from
September 12, 2026 00:10
b2dd09d to
d84120b
Compare
This was referenced Sep 12, 2026
…way net A one-line read-only design question read home-panel files for ten rounds in 48 seconds and was taken away from the conversation that was answering it. The round-10 ceiling fired, a summary reader said `split`, and a cold worker opened on a brief plus stub pointers: its first six calls re-read the chat's own results, then fourteen more files, 551k input tokens at 24% cached over 393 seconds, and it never ticked an item. Three things were wrong at once. The decision was made by the wrong party — a reader of a summary rather than the model holding the context. It was made on the wrong signal — a count of rounds, which measures nothing about whether the work is going anywhere. And the move threw away everything the turn had read. So the count no longer moves anything, and what it buys instead is a sentence. THE TURN DECIDES, INFORMED. The rungs below the last one call nobody and move nothing. They put one note into the running turn at its next step, through the one ambient door a loop nudge already uses: the facts it cannot see about itself (rounds so far, distinct files read, bytes of results held) and the three roads on, in plain words — answer now; carry the rest on in a room with `quick_task` and `inherit`; hand out what it has not read. The cost is stated once, where the choice is: a worker that does not inherit opens on a brief about your work instead of on your work, and reads it all again. No timer, no model call, no message[0]. PROMOTION INHERITS. `quick_task` gains `inherit`: the worker is a read-only fork of the caller's transcript, so its first request carries the caller's messages and tool results verbatim and it reads nothing twice. It is bounded by a property rather than a number — refused, in a result the model reads, when the caller's context does not fit the worker model's own working room (`compactThresholdOf`, the build's own line for how far a conversation may grow), and the refusal names the road still open. THE RUNAWAY NET. `readMark` survives as the last rung only, and it no longer asks about rounds: it fires when the turn's context has passed the room it can still work in, when the loop detector has already nudged twice, or when the wall has run out. Each of those is a fact the build already had. A turn reading ten files in 48 seconds reaches none of them. AND THE CARRY-ON ROAD PROMOTES. When the net does fire, the carry-on goes through the same admission door as the tool, with the drawn items as its checklist and the context inherited. The brief road survives only where inheritance cannot go — the rung that fired because the context is too big to carry — and there the brief now carries the compiled results themselves rather than pointers at calls the worker would have to run again. Deleted with it: `checkpointSplitNote` and the `split` decision, the seam mark, `quickFromDrawing`'s bare-spec path, and the stub-pointer section for an inherited worker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t mark #962's fixture holds a drawing past the turn's last step, and it opened that window by crossing the FIRST mark. With the rungs below the last one buying nothing, the first mark asks nobody, so the fixture now takes the turn to THE RUNAWAY NET instead: every scripted round reports a fuller window ([checkpointFillAt]) until the context bound fires, and the note rungs' own rows are dropped from what it returns, because a row that cost no call is not a row about a drawing landing late. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
santoshkumarradha
force-pushed
the
feat/turn-promotes-itself
branch
from
September 12, 2026 09:37
d84120b to
0f6a62a
Compare
…asker's words in its heading The probe "the run said the brief could not be written, what happened" reached adaptive-runs in the last of four slots on dev and lost that slot once this change's task pages said more about briefs. Retrieval is the feature, so the heading now says it the way a person asks it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
The rebase onto dev, resolved against #923. Two mechanisms were about to exist for one shape and now there is one: the ceiling's carry-on rung reads [Agent.contextRoomFor] — the net's own arithmetic — rather than a second opinion about how full a window is, and [checkpointMeter.compacted] states why it does not clear the net's [checkpointMeter.outOfRoom] latch: a turn that bought room by folding is a turn whose transcript is a summary of its results, which is exactly the transcript a promotion must not carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
The rebase onto dev, resolved against #923. Two mechanisms were about to exist for one shape and now there is one: the ceiling's carry-on rung reads [Agent.contextRoomFor] — the net's own arithmetic — rather than a second opinion about how full a window is, and [checkpointMeter.compacted] states why it does not clear the net's [checkpointMeter.outOfRoom] latch: a turn that bought room by folding is a turn whose transcript is a summary of its results, which is exactly the transcript a promotion must not carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
The page already said a long answer is not moved for being long; what it did not say is that the fold runs first and hands the reply back, and that the counting restarts from the round the room was bought on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
…g is deleted #923 took the count out of the decision — the ladder climbs no further than the two notes, and the runaway net is what moves a running turn now. That left one thing standing and it was an ORDER, not a mechanism: the net reads a quantity this loop reduces on its own, every step, for free, and the reading stood one statement ABOVE the reduction. So two statements moved, and the compact-and-continue rung this lane wrote against the old shape is deleted whole — checkpointCanCarryOn, the meter's boughtAt offset and compacted(), the "compacted" journal word, the ceiling's carry-on branch, the looped.go bypass, the sidecar_law endingDoors edit and the two test-fixture worlds. Two mechanisms for one shape is the thing the bar forbids. What guards the rule is foldbeforeprice_law_test.go, which reads loop.go's own tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
The rebase onto dev, resolved against #923. Two mechanisms were about to exist for one shape and now there is one: the ceiling's carry-on rung reads [Agent.contextRoomFor] — the net's own arithmetic — rather than a second opinion about how full a window is, and [checkpointMeter.compacted] states why it does not clear the net's [checkpointMeter.outOfRoom] latch: a turn that bought room by folding is a turn whose transcript is a summary of its results, which is exactly the transcript a promotion must not carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
The page already said a long answer is not moved for being long; what it did not say is that the fold runs first and hands the reply back, and that the counting restarts from the round the room was bought on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
santoshkumarradha
added a commit
that referenced
this pull request
Sep 12, 2026
…g is deleted #923 took the count out of the decision — the ladder climbs no further than the two notes, and the runaway net is what moves a running turn now. That left one thing standing and it was an ORDER, not a mechanism: the net reads a quantity this loop reduces on its own, every step, for free, and the reading stood one statement ABOVE the reduction. So two statements moved, and the compact-and-continue rung this lane wrote against the old shape is deleted whole — checkpointCanCarryOn, the meter's boughtAt offset and compacted(), the "compacted" journal word, the ceiling's carry-on branch, the looped.go bypass, the sidecar_law endingDoors edit and the two test-fixture worlds. Two mechanisms for one shape is the thing the bar forbids. What guards the rule is foldbeforeprice_law_test.go, which reads loop.go's own tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
DO NOT MERGE — the owner runs the test binary first (
~/af-promote/bin/aforge, shasum in the lane report).Owner's ruling, 2026-09-11 14:20 EDT: "we should not force a task via a hard turn count; we need to give the model enough in the prompt to choose; when it chooses it needs to weigh whether it is worth it for the task to re-read everything it has."
What was measured
A one-line read-only design question (conversation
7598fe73c5ce3a9a, 13:59–14:08). The chat read home-panel files for ten rounds in 48 s. The round-10 ceiling fired; a mark reader on a 4k-token account saidsplit; a handoff writer spent 39 s and 41k input drawing a quick node; the worker opened on the raw sentence, four items and aCALLS THAT HAVE ALREADY RUNstub list. Its first six calls re-read the chat's own results (116 KB of stubs), then fourteen more files — 551k input tokens, 24% cached, 393 s, never ticked an item. Asked why, the chat stopped it and answered from its own earlier reading: "I over-read. I have what I need".Three faults in one road: the decision made by the wrong party (a summary reader, not the model holding the context), on the wrong signal (a round count, which says nothing about progress), and the move throws the context away.
The shape
1. The turn decides, informed. The rungs below the last one (
checkpointNotes = checkpointMarks - 1) call no reader and move nothing. They inject ONE note at the turn's next step, through the single ambient door a loop nudge already uses (enqueueAmbientNote) — never message[0], no timer, no model call:(one line in the transcript; wrapped here, and the three figures are the turn's own.)
2. Promotion inherits.
quick_taskgainsinherit(default false) — a field on #929'squickAsk, admitted throughadmitQuick, so the tool and the carry-on road ask for a promotion the same way: the worker is a read-only FORK of the caller's transcript, so its first request carries the caller's messages and tool results verbatim (prefix identical, cache hits) plus the brief as its opening message. One door —quickAskgained a field andadmitQuickis still the only way a quick node is born. Schema prose:Bounded by a property, not a number:
inheritFitsrefuses when the caller's context exceedscompactThresholdOf(workerWindow)— the build's own already-derived line for how far a conversation may grow — and the refusal names the road still open (hand out with a brief). The window read is the worker's, not the caller's: atask.modelpin or the crew's worker seat hands tasks to a cheaper model every day, so the bound is asked below #929's own resolver, of the model the work will actually leave on (workerModelFor).3. The carry-on road promotes. When the net fires, the carry-on goes through the SAME admission door as the tool, with the drawn items as its checklist and the context inherited. The brief road survives only where inheritance cannot go (the rung that fired because the context is too big), and there the brief now carries the compiled results themselves under
WHAT THIS WORK ALREADY FOUND OUTrather than pointers at calls the worker must run again.4. The runaway net.
readMarksurvives only as the LAST rung, and no longer asks about rounds.turnHasRunAwayreads ONE fact — the turn's context has passed the room it can still work in (compactThresholdOf(a.window)) — and the other roads to the same ceiling were already written and are not repeated in it:looped.go's nudge ceiling (handOverLoopingTurn),turnwall.go's wall (asked ahead of this ladder) and the meter's own disproved completion claim. Each a fact the build already had, with the derivation in prose on the constant. Its reading is areadBesidesidecar that may only INTERRUPT (sidecar_law_test.goreds an awaited reading on a road that carries the turn on). A turn reading ten distinct files in 48 s reaches none of these — pinned byTestATenRoundReadingTurnIsToldAndNotMoved.5. The prompt, and it comes DOWN.
inheritis a verb law now — one schema property, 72 bytes, registered atquick_task(classverb) rather than as a page sentence — and the road lawquick_task's description said word for word afterbeltfacts.gohad come to say it is deleted, giving back 145. Measured againstdev 84016ad09in a detached worktree of it: lean 31,341 → 31,267 and fixed 39,073 → 39,000 on a machine with ripgrep; 31,448 → 31,375 and 39,180 → 39,107 on one without, which is the shape CI measures. Budgets 31,500 and 48,000, and the note itself is in neither figure: it rides the event, at the boundary where it matters.A net that fires once says so. The net measures a condition rather than a boundary, and a full context stays full — and three of
handOverRunningTurn's endings LEAVE THE TURN RUNNING (no brief came back, this conversation is holding work of its own, the reader says nothing is left). Without a latch each of those was followed by a fresh mastermind call and an unconditionalcutGenerationon every remaining step.checkpointMeter.netFiredAtrecords the firing andnetRestedholds the net off forcheckpointPricerounds of real work — ONE predicate for all four endings, in the unit the meter already used for a disproved claim, and pinned byTestADeclinedCeilingDoesNotReadTheWorkAgainEveryRound(3 readings with the latch over the same script that took 6 without).Deleted
checkpointSplitNote,checkpointDecisionSplit,checkpointSeamMark,quickFromDrawing's bare-spec path, the sketch-split gate on the carry road, the stub-pointer section for an inherited worker, and every manual sentence saying a turn is moved at round ten.Manual, same PR
chat/tasks.mdrewritten — "An answer that runs long is told, and decides for itself", "When a reply is taken out of your hands", and a new section oninherit— plus three probes inchat_test.go(why did it stop reading and start a task,can it keep what it read,why did it not become a task) and the cross-reference inhow-tasks-run.md.Pinned end to end
inherit_test.godrives the real runner both ways. A promoted worker's FIRST REQUEST carries its caller's own tool result verbatim, opens on the caller's page plus the one paragraph that is true of a promotion, and is handed none of theCALLS THAT HAVE ALREADY RUNsection. A conversation too big for the worker's window is refused in a result the model reads, and no worker is started.Proof
make test-laws(21 packages, incl.sidecar_law_test.go,admission_law_test.go,iconlaw),go test ./internal/manual/, the law registry, both prefix budgets, the schema law,go build ./...,go vet, and focused sweeps across the checkpoint / quick / route / carry / handoff / write-seam families.Review, second pass — the nine findings
Coordinator's six:
inheritdescription and thebeltfacts.gosentence until the lean prefix is 31,482 of 31,500 ondev 84016ad09; the budget was not touched, and the before/after above is measured on that base.how-tasks-run.mdcontradictedtasks.mdabout which tasks inherit a transcript. Its "what a task can do while it runs" paragraph now says the transcript is inherited only by a promotion —quick_taskwithinherit, or a reply the ceiling carried on — and that every other task gets one assembled brief.readTurnFactswalked the whole session. It walksmessages[meter.opened:]now, andturnOpenedAtfinds the message the person opened THIS turn with (walking back over the tool traffic to the newest person's message, then over a steer that sits on the ask it steered).TestTheNotesFiguresCountThisTurnAndNotTheOnesBeforeItbuilds a two-turn transcript with a steer in it and asserts the note counts one file, not three.promotedFromTurnhad lostif read.sketch.handsBack { return nil }. Restored, withTestATurnHoldingCoordinationIsNotPromotedWithItAsAChecklist— which fails without it.turnHasRunAway. Corrected: the function reads context; the loop, the wall and the disproved claim reach the same ceiling by roads that were already written.outOfRoomlatched and never cleared. It stays one-way, and the reason is now prose on the field: the only thing that can put a turn back under the line is a compaction, and a folded transcript is exactly what a promotion must not carry.The checkpoint owner's three:
netFiredAt/netRested).screen.md'staking stockrow,models-and-cost.md's markreader paragraph and role table row, andtasks.md's "looking at the work decides everything else" all say what the code does now. And because[taking stock]is now both a status-line phase word and the model-facing note's lead,screen.mdgains its own##section separating them (a new heading rather than prose added to an existing one — BM25 length normalisation charges the padded section its own probes).TestARacedVerdictWithoutBreadthArmsNothingOfItsOwnkept its name and lost its assertions. Replaced byTestARacedVerdictWithoutBreadthWritesNoBreadthOfItsOwn, which asserts what is actually true now: two narrow readings arm no breadth, and either reader sayingWidearms it.checkpoint.go's own header was three claims stale beside this change (a sketch at every mark, a look that costs a call, "nothing is injected into the turn at any mark") and now says what the file does: the notes tell and ask nothing, the ceiling is a property and not a count, and the one reading that must come back is the sidecar's at the net.Proof at this head (
d84120b30, rebased ontodev 84016ad09)go build ./...,go vet ./internal/session/,make test-focus PKGS=./internal/session RUN='Prefix|Inherit|Promot|Coordination|LawRegistry', the checkpoint / quick / route / verdict / handoff families,go test ./internal/manual/(green,TestHeldOutQuestionsReachTheirPageincluded),make test-focus PKGS=./internal/tui3 RUN='Manual', andmake test-laws(25 packages).Rebased through #929 (the task-start wave), #973 and #975:
inheritis aquickAskfield, the carry-on road answers a*quickAskwithinherit: true, and both go throughadmitQuick. There is one constructor, not two.Spark, isolated clone
~/af-suite-923at this head,make pr-ready BASE=84016ad09behind the box's one-suite lock:Test binary for the owner:
~/af-promote/bin/aforge,shasum e66947bc1473cb29becf763a7e55f33a5869de4e.The two CI reds, and what they were
The lean prefix cell measures an environment-dependent quantity, and that is a defect of its own. CI read 31,555 where the laptop read 31,484 at the same sha. The cause is
grep:bare.grepToolDescriptionprobes for ripgrep once per process (exec.LookPath("rg")) and a machine without it carries the longer fallback description — 134 bytes more on the GitHub runner, which has norg. Reproduced on the laptop by hidingrgandfdfromPATH: the same tree then measures 31,520, matching CI to two bytes. So the honest baseline for this gate is the no-ripgrep one,devsits at 31,448 there, and this PR now sits at 31,375 — 73 bytes belowdev, in the environment the gate actually runs in. Filed as #981 rather than patched here: a budget whose reading moves with what is installed on the runner cannot be reasoned about by the lane that trips it.TestAContinuationSayingNothingIsLeftDropsTheCeilingHandoverwas this PR's own fixture, and the mechanism it exposed is a real boundary. The turn answers the handover withNOTHING LEFT TO DO;checkpointMeter.believeDonecharges that claim once and armsaskAgainAt = rounds + checkpointPrice, so ten more rounds of REAL work disprove it and the turn is met again — pre-existing law, pinned byTestTheRunningModelsSayS0IsBelievedOnceAndThenMet. This PR widenedcheckpointSlackfromcheckpointMarks + 4(7) tocheckpointPrice + checkpointMarks(13) so scripts could outlast the sidecar's gap, and 13 tail steps straddle that price: how many of them become rounds of work depends on how many the reading eats, which is why it failed 13/30 on the Spark and 0/30 here. The fixture was asserting the scheduler.The fix is in two parts, neither of them a rerun:
checkpointClaimSlack = checkpointPrice - 1is the slack for the three fixtures whose turn CLAIMS IT IS FINISHED and carries on (checkpoint_test.go×2,unattendeddoor_test.go). By construction they cannot reach the claim rung, so each tests the ending it is named after and nothing else. The constant says why in its own doc.TestABelievedClaimIsMetAgainOnlyAfterAPriceOfRealRoundspins the boundary on the meter, with no scheduler in it: every round of the quiet rings nothing and leaves the net unrested, the round that completes the price ringscheckpointMarksthrough the meter rather than the net, the claim is not believed a second time, and a batch that only watched work already out buys none of the price. It fails on a broken latch (netRestedforced true: "the net was rested 1 rounds after it fired, 9 short of its price").Spark at this head:
go test -count=30 -run '^TestAContinuationSayingNothingIsLeftDropsTheCeilingHandover$'→ 30 RUN, 30 PASS, 0 FAIL.Folded in from the peer's follow-ups
netFiredAtwas a zero-value sentinel and round zero is a real round (a turn whose opening boundaries all watch never leaves round zero). The latch is now two fields for two facts,netFired boolandnetFiredAt int.checkpoint.go's new header said "three times the handoff price" wherecheckpointMarkAt(2)ischeckpointRatiotimes it. The figure is interpolated from the constant, not restated.newQuickTaskSpecwas built twice on the inherit path. The branch is hoisted: a promotion is built INSTEAD of the plain node, never after it.screen.mdlisted three firing conditions and there are four. The believed claim disproved by ten more rounds of real work is named in both places the page describes the moment.The rest of the review — the two windows behind one line (
TrustedWindowFor), the transcript walked twice a boundary, the unpinned double-composed page,awaitReading's silent sleep-poll, andcheckpointMarkAt(checkpointMarks)being production-dead — are filed together as #979, with the reviewer's own wording and an acceptance line each.🤖 Generated with Claude Code