bench(lab): turn the loop - #300
Merged
Merged
Conversation
…icts A repository's position lived in the operator's head. The old driver held it in a JSON map of repository to phase, edited by whoever was debugging, which made it the one number in the system with no evidence behind it. It is now a function of two inputs, and which input a fact comes from is the whole design. The run tree holds which artifacts exist and the authoring cycle, which is already a directory name: those are derived every time and never stored, so there is no counter to drift from the thing it counts. The attempt records hold the verdict, the table that rejected it and the anchor: a judgment is not recoverable by reading a tree, so it is recorded once and never rewritten. An attempt is one file, written with O_EXCL. Append-only as a convention survives until a bad night; append-only as an open flag survives the bad night. Out of the two comes a standing. A park and a PAY are the two most worth telling apart — one is the method failing on this repository, the other is the method succeeding and waiting on a wallet — and so are the two refusals: a verdict its phase cannot emit is an agent that misbehaved, and a usable verdict with no artifact is an agent that died. The fixture tree is copied from the jellyfin campaign's layout and carries one repository of each shape it takes.
`sense-lab repo <id>` now prints where the repository stands and exits with the
code that carries it. The codes are this command's API rather than a display,
because it belongs in a loop:
while sense-lab repo <id>; do :; done
That loop stops on a park, on a PAY and on either refusal instead of spinning on
any of them, and `-show` never returns 0 so it cannot be dropped into it.
`-show` stops at the announcement, which is the point before an admission writes
anything, so it is a preview of what is about to happen rather than a second
reading of the same inputs.
A phase's wall was nowhere, and its verdict was prose inside its artifact, which is why a parked campaign could be read by a person and by nothing else. The wall is declared beside the plan the agent runs rather than as a constant in Go. A wall held in a variable is the number that gets raised in a hurry at 3am to rescue a stalled phase, and that is what CANNOT-FINISH-AT-BUDGET IS A RESULT forbids. Declared in a file, changing it is an edit somebody reviews. Each plan gains the document it owes: the phase, the repository, the cycle, the verdict and the table that carries it. Nothing else in them changes.
Two additions the crank needs, both facts nothing can re-derive. An attempt that produced no verdict says how it ended. An agent that ran past its wall and an agent whose output is not a verdict are diagnosed differently — one died, the other misbehaved — so they are separate outcomes and separate exit codes. Without them a phase that stalled leaves no record, the position is unchanged, and an unattended loop runs that phase again, forever. And every attempt records where it sits in its cycle. A cycle is not a walk down the graph: a re-entry sends work back, so cycle 1 can hold an author, a mini-bench that re-questions it, and the author again. Ordered by the graph the mini-bench sorts last, the position routes from the verdict that sent work back rather than from the attempt that answered it, and the loop re-runs the author on every turn. Measured against the crank before the order was recorded.
Nothing in the binary spawned a phase agent. The eleven plans were opened by a human, and the routing rules cycle 05 built and tested were exercised in production by an operator remembering them. That operator is the leak: six authoring cycles on one repository is roughly thirty hand-spawns, each one a chance to skip the plan, read only the latest rejection, or advance on an agent that produced a confident summary and no artifact. Advance takes a context and a repository id, and nothing else. It reads a position, runs the one phase that comes next, guards what came back and records it. Everything a phase needs is resolved from the declared graph and the config, because an options struct handed in as six arguments is the same options struct. A phase reports its own verdict, which makes every phase an unreliable narrator about itself. So four things about identity are checked, and then the one that is not the phase talking: the artifact it owed is on disk, or nothing advances. The deciding layer may not spawn. The spawner is a value the edge hands in, and depguard holds that rather than review.
`sense-lab repo <id>` now runs the phase the position owes, and `-until` cranks until it stops on its own. The loop is in the caller, so the dispatcher never knows which of the two it is in. A phase agent is spawned in the repository under study, under the wall its plan declares, inside a disposable environment carrying no more than a run arm's credential and reaching no keychain of its own. It stops at the pay call: `PAY` prints the exact command and exits waiting on a human. bench, report, harvest and board stay hand-run, because an unattended loop that can reach a paid cell is a different product with a different risk.
luuuc
changed the base branch from
lab/09-02-where-a-repository-stands
to
main
August 19, 2026 16:40
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.
Stacked on #299, which is stacked on #298. Merge in order; this PR's diff is only its own changes.
Problem
Nothing in the binary spawns a phase agent.
internal/loophas no importer, the eleven files inlab/plans/are opened by a human, and the routing rules cycle 05 built and tested are exercised in production by an operator remembering them.That operator is the leak. Six authoring cycles on one repository is roughly thirty hand-spawns, each one an opportunity to skip the plan, read only the latest rejection, or advance on an agent that produced a confident summary and no artifact. "The authoring cycle is unattended and bounded" is a law, and it currently describes an intention.
Summary
Advancetakes a context and a repository id, and nothing else. It reads a position, runs the one phase that comes next, guards what came back, records it and returns.-untilis a loop in the caller around that call, so no mode is threaded through the dispatcher.A phase reports its own verdict, which makes every phase an unreliable narrator about itself. Four things about identity are checked against the job that was dispatched and the plan that was declared — the verdict names this phase, this repository and this cycle, and carries a verdict the plan declares — and then the one thing that is not the phase talking: the artifact it owed is on disk, or nothing advances.
Two numbers left Go. A phase's wall is declared beside the plan its agent runs, because a wall held in a variable is the number that gets raised in a hurry to rescue a stalled phase. And a verdict is now a document a program can read, rather than prose inside an artifact.
The crank stops at the pay call.
PAYprints the exact command and exits waiting on a human; bench, report, harvest and board stay hand-run, because an unattended loop that can reach a paid cell is a different product with a different risk.Changes
lab/internal/crank: the dispatcher, the verdict document and the guardlab/internal/cli/crankcmd.go: the real spawner, at the edge — a disposable environment carrying no more than a run arm's credential, the agent spawned in the repository under study under its declared walllab/internal/plans: awall:key in the plan header; each plan gains the verdict document it oweslab/internal/position: an attempt records how it ended when it produced no verdict, and where it sits in its cycle.golangci.yml: the deciding packages may not importos/exec, enforced by the linter rather than by reviewA defect this found
Ordered by the graph, a re-entered author sorted before the mini-bench that sent it back, so the position routed from the rejection on every turn and
-untilwould have re-run the same phase forever, at whatever an agent costs. Attempts now record their place in the cycle, and both the ordering and the turn after a re-entry are asserted.Test plan
make cigreen: coverage floor with no new exception, zero complexity suppressions, lint cleanPAY, a stalled phase, a refused verdict and a missing artifact — no agent, no network, no spend