Development record
an-autonomous-implementation-run-paces-itself-by-default-and
Summary
The design record for itd-2609201925079472, from the six decisions on the intent (2026-09-20).
Scope
- The pace configuration:
pace.work_minutes,pace.pause_minutes,pace.sub_agentsread through the layering the model-tier intent uses (flag, repository.abcd/config.json, machine~/.abcd/config.json, bundled 120/300/2); one resolver reports the value and the layer it came from (criteria 1 to 3, 9). - The window clock in the implement verb's state file: the window's
start,
next_eligible_at, the lanes alive; written by the loop at every step (criteria 4, 5). - The ceiling: the loop counts the agents alive from the state (implementers and validators together) and starts nothing above the ceiling, exiting with the lanes named and the wait accumulated (criterion 6). The ceiling is reachable because a run works in parallel up to it (piece 6).
- The budget check (from
itd-29): where the runner reports remaining quota, an estimate from the spec's size is compared before the run starts; a runner that reports none is named and the check is skipped out loud (criterion 7). - The rate-limit checkpoint (from
itd-29): a runner's rate-limit response ends the window early with the lane checkpointed to its branch andnext_eligible_atset (criterion 8). - Concurrent lanes and validators (ruling DR6, 2026-09-29): one run hands work to several agents at once, up to its ceiling: the validators of one round together, and the lanes of steps that do not need each other side by side. The section "Concurrent lanes and validators" below is the design; its criteria C1 to C13 are how criterion 6 is tested.
Out of scope
A ceiling across runs (the register's), which includes the further picks
abcd build next --max and --until-empty make: each pick is its own run,
and picks stay one after another. Telemetry and hand verbs. A time limit on
an agent that never hands back its receipt: its slot stays taken until the
receipt comes back or the person stops the run.
Approach
One lane, test-first, on the implement verb's state file; the resolver is the one canonical layering primitive shared with the model tier. The bundled default lives in one constant the run record names.
How the criteria are satisfied
1 to 3 and 9 by piece 1; 4 and 5 by piece 2; 6 by pieces 3 and 6, tested through C1 to C13 below; 7 by piece 4; 8 by piece 5.
Evidence the build must answer
- The pause has never fired. Three Dessau pilots on 2026-09-21 ran the scripted outer loop under a pacing window, and each finished inside its first window, so the gate that refuses an early start and the resume on
next_eligible_atare untested by a real pause; the abcd pilot of 2026-09-20/21 paced by hand (idle wake-ups from the orchestrator's own scheduler) and broke its second pause on the facilitator's word. The build proves the pause with a test that sets the clock past the window and watches the refusal, and the first real run records whether the pause fired. - The ceiling turned reviews into a queue: 40 minutes of a two-hour window with a lane waiting on the two-agent ceiling, 27 minutes for one review (iss-2609211105014235). That record proposed reviewers counted separately from implementers, or a ceiling set from the lane shape; its resolution ruled instead that reviewers take the same slots as implementers, and piece 6 ("The count") holds that ruling. What this spec takes from the record is the queue, not the separate count: the validators of one round run side by side, and an open lane's reviewers take a freed slot before any new lane (piece 6, "The order in which waiting work takes a freed slot", and C5).
Concurrent lanes and validators (ruling DR6, 2026-09-29)
The product thinker ruled, verbatim: "per-run agent limit: WORK IN PARALLEL
— a run may build several pieces and run reviewers concurrently up to its
limit (new build-loop work; then AC6 is testable)." Without this piece the
loop hands out one agent at a time, so a run never has more than one agent
alive and criterion 6's ceiling, which is at least 1, can never be reached.
This section is the design that makes it reachable. The pieces it
changes are the implement verb's (itd-2609201916151817: the state file, the
step interface, the validators and the landing) and the step parser of
itd-2609212103565953; they are specified here because the ceiling is this
spec's and nothing else needs them.
What this piece is built on
- Piece 9 of
spc-2609202134338445, the landing (the pull request armed through the forge client, the repository's merge rule, the ancestor check, the cleanup), is what the landing rules stand on, and it is built: the land stage (internal/core/implement/loop/land.go) performs the six steps of the landing (prepare, records, push, pull request, arm, merged). Every rule of "Two lanes that touch the same files" below, and criteria C8, C9, C10 and C13, extend that stage; none of them waits on another piece.
The count
- A slot is one agent the run has handed work to and not yet taken a verified receipt from: an outstanding await on any lane of the run. The count is the number of outstanding awaits in the state file, and the state file is its only source; nothing else is counted and nothing is stored beside it that could disagree.
- Implementers and validators count alike, in any mix of roles: the first
implementer of a lane, the fresh implementer of a fix round, the fresh
implementer of a sync (below), the ruthless reviewer, the security
reviewer and the intent-auditor. That reviewers take the same slots as
implementers is already settled (
iss-2609211105014235's resolution and the decision log of 2026-09-23, 2026-09-24 and 2026-09-29) and is not reopened here. - The ceiling is the run's
pace.sub_agents, with the layer that supplied it, as piece 1 resolves it. The session that drives the loop is not a slot: it is handed no brief. - A stage the binary performs itself takes no slot and is never held by the
ceiling: the worktree, the brief, the landing's
ghcalls and the merge of a sync.
Isolation
- Every lane has its own worktree in the machine-scoped store,
~/.abcd/worktrees/<root-sha>/<run-id>-<lane-id>, on its own branchbuild/<run-id>-<lane-id>, exactly as the implement verb's piece 6 lays one out. Two lanes never share a worktree or a branch. - The validators of one round read the lane's worktree at the head the round
names, at the same time, and treat it as read-only: whatever a validator
runs, it runs on a copy (
git archive <head>), asAGENTS.mdasks of a verifier. No implementer is handed the lane while its validators are out: a fix brief is written only once every validator of the round has returned. - Every
implement stepandimplement receiptholds the run tier's advisory lock for its read and its write of the state file, as it already does, so receipts that several agents hand back at once are applied one after another and none is lost.
How a slot is freed
- A slot is freed when its receipt is verified:
implement receipt <path>looks the path up among every outstanding await of the run, not only the first lane's, and advances the lane that await belongs to. A path no outstanding await names is refused, naming the awaits there are, and frees nothing. - A receipt the verifier refuses frees nothing: the agent still owes it, and its slot stays taken.
- A lane handed back holds no slot, because a hand-back is decided only once every validator of its round has returned.
- A freed slot is filled by the next
implement step, never by the receipt call itself: the receipt call verifies and advances one lane and exits, as every invocation does.
The order in which waiting work takes a freed slot
Each implement step performs one move. It first performs any stage of any
lane the binary owns (those take no slot). When the move needs an agent, it
takes the first waiting item in this order:
- Work on a lane already open, before any new lane: the round's validators
and the fix or sync implementers. A lane already open is closer to
landing, and its reviewers are what queued for 40 minutes in the record
iss-2609211105014235carries. - Among open lanes, the lane of the lower-numbered spec step first.
- Within one lane's round, the validators in the order the round lists them: the ruthless reviewer, the security reviewer, then the intent-auditor on the lane that closes the spec.
- Then the first implementer of a new lane, for the lowest-numbered pending step whose needs have landed (below).
The order is a function of the state alone, so two runs of the same state hand out the same work.
Which steps may run side by side
- A spec step may carry a
- needs:line beside its- packages:and- tests:lines:- needs: none, or- needs: 1, 3, naming earlier steps by number. - A step without the line (ruling DR6b). The product thinker ruled,
verbatim: "(a) ONE AFTER ANOTHER BY DEFAULT: a step runs alongside earlier
ones only if its plan says so; nothing already planned changes; reviews
run side by side; the 2026-09-21 wording stands." So the default
needsof a step is every step before it, and running beside earlier steps is an opt-in the step declares for itself (- needs: none, or a list naming only the steps it waits for). The loop never infers independence, from the- packages:lines or from anything else. A step without the line keeps the orderitd-2609212103565953rules (criterion 2: the next step's lane starts only after the previous step has merged), and that intent's wording of 2026-09-21 stands unamended, so every stepped spec written so far builds exactly as it did. The validators of a round run side by side whatever the steps declare (below). - A lane opens for a step only when every step it needs has landed: its pull
request is an ancestor of the default branch, or the spec at the default
branch marks it
landed:. Its branch is then cut from the default branch, so it holds what it needs. - The parser refuses a
needsthat names the step itself, a later step, or a step the spec does not list, naming the line, and the readiness gate reports the refusal before a run starts. - A
needsline across a remainder.spec close --remaindercarries the steps not marked landed, in document order, and renumbers them from one (spec.Unlanded,spec.RenderSteps, and the renumbering the close does before it mints; a step's number is its position,steps.go'sStep). Every other indented line is carried verbatim, so a- needs: 1, 3copied as written would name the wrong step, or one the remainder does not list, and the parser would refuse the remainder the close had just written. The copy therefore rewrites theneedsline, the one indented line it does not carry verbatim:- a named step that landed is satisfied and leaves the list;
- a named step that did not land is carried, and is renamed to its number in the remainder;
- a list left empty is written
- needs: none, never removed, because an absent line means the default, every earlier step (ruling DR6b), not "nothing"; - a step without the line stays without it: the earlier steps the remainder lists are exactly the unlanded earlier steps, and the landed ones are satisfied, so the default reads the same over the remainder as over the spec it came from.
The close's result names each needs line it rewrote, before and after.
The rewrite is chosen over dropping the line because it is a total
function of the parse the close already holds, with nothing guessed: a
step is in the remainder exactly when it has no landed: (the predicate
Unlanded cuts on), so every step a needs line names is either landed
(satisfied) or carried, and the carried steps keep their order, so the map
from old number to new is one-to-one. Dropping the line would turn an
explicit list into the default, every earlier step (ruling DR6b), which
runs the step later than its plan declared. Rewriting a satisfied
need to its landed: marker instead would give the parser a second
vocabulary for a need that constrains nothing. The steps spec
(spc-2609212138246060, scope 4) names this exception to its verbatim
copy.
- The validators of one round always run side by side, stepped spec or not: parallel reviewers need no declaration.
The state file
The state goes to schema version 8 (version 7 is the landing's, piece 9):
lanes[].awaitsis a list of awaits, each with its role, brief, receipt path andsince. A lane has one entry while its implementer works and one per validator while its round is out. It is a new key, not the version-7awaitingwith its type changed: the state is decoded strictly (a field the version does not name is refused), and the version is peeked first to pick the shape the decode holds the file to, so a key that read an object in one version and a list in the next would be a second, silent branch inside one key. Version 8 does not writeawaiting.waitingis a list of the work the ceiling holds back, each with its lane, its role andsince, the time the ceiling first held it. It is written when a step finds the ceiling reached, and an item leaves it when it takes a slot, with a run-record entry naming the lane, the role and the whole minutes it waited.lanes[].syncsrecords each sync (below): the sibling lanes whose landing caused it, the default branch's sha merged in, whether it conflicted, and the head it produced.lanes[].holdrecords a held lane (ruling DR6c):since,cause,headandbefore; the lane stages gainheldanddiscarded.- A state file of version 7 or lower reads as a run whose lanes each have
zero or one await (its
awaitingobject becomes a one-entryawaits), and runs on unchanged; the writer writes version 8. A version-8 file is refused by an abcd that knows only version 7, naming the version, as every schema step already is. - A file that claims a version older than what it carries is refused, in the
shape of every earlier step's refusal (
capped()andlanded()ininternal/core/implement/loop/state.go: a pace in a version-1 file, a pick in a version-1 or version-2 file, and below them a validation, a fix-round cap and a landing): a file of version 7 or lower that carriesawaits,waiting,syncsorhold, or a lane stageheldordiscarded, is refused naming its version and what it carries that the version never wrote, with the remedy those refusals give (the loop is the file's only writer; restore it or remove the run directory). A version-8 file that carriesawaitingis refused the same way, since version 8 never writes it. implement statusnames the slots in use out of the ceiling, and every lane alive with its stage and each role it awaits. The status block reports one row per lane alive, not only the first.
Two lanes that touch the same files
- Landing is one lane at a time within a run: at most one lane holds an armed pull request that has not merged. A lane whose round passes while a sibling's pull request is armed waits at its landing, holding no slot; when several wait, the lane of the lower-numbered spec step arms first.
- Before a lane arms its pull request, the loop checks whether a sibling lane of the same run has landed since this lane's base. If one has, the loop syncs the lane: it merges the default branch into the lane's branch with a merge commit in the lane's worktree. It never rebases, so no commit a validator judged is rewritten and every sha the record cites stays reachable.
- A clean merge moves the lane's head, so a fresh round judges the new head: no verdict stands over a head it did not read (the invariant of the implement verb's piece 8).
- A merge that conflicts is aborted, leaving the branch where it was, and the conflict goes to a fresh implementer with a sync brief naming each conflicting path and the sibling lane whose landing brought the other side. The receipt is verified as a fix receipt is, and must also carry the default branch's merged sha as an ancestor of the new head; then a fresh round judges it.
- A sync does not count against
--fix-rounds. Each sync answers a sibling landing, and a run has a fixed number of lanes, so syncs are bounded by the lane count. A round after a sync that does not pass is a fix round like any other and counts. - The lane that closes the spec is synced before it arms whatever it touches (a merge that finds the branch already holds the default branch changes nothing and needs no round), so its head holds every lane the run landed. A lane is the closing lane when it reaches its landing with no step pending, no other lane open, and no lane of the run handed back. If its last passing round had no auditor, the sync's fresh round carries one.
- After a hand-back, no lane closes the spec. A handed-back lane is neither done nor open, and the delivery lacks its step, so no auditor judges the delivery as whole and no lane runs the close; the spec stays open for the person's replan. The siblings open at the hand-back finish and are held (ruling DR6c, "After a hand-back, the siblings finish and are held", below).
- The intent-auditor reads the whole delivery (ruling AI) as the run's own
lanes' changes, never as a sha range from the base of the run's first lane
to the closing head: after a sync, that range also holds every commit the
default branch gained since the base, work from outside the run included.
The delivery is the diff of each of the run's pull requests, lane by lane:
a lane's head against the default-branch sha it last merged in (the last
entry of its
syncs), or against its base when it never synced. Each of those shas is an ancestor of the lane's head, so each diff holds that lane's commits and its conflict resolutions and nothing a sibling or an outside change brought in; a landed lane's head stays reachable after its worktree is cleaned up, because piece 9's ancestor check holds it in the default branch. For a run of one lane that never synced, the delivery is the base-to-head range the implement verb's piece 8 reads. - Movement of the default branch by anyone outside the run is left to the repository's merge rule, as it is for a single lane: the loop syncs only on its own siblings' landings.
The pace and the fix rounds, per lane
- The window clock is the run's, one for every lane. When the window
elapses,
implement stepstarts no agent on any lane; every outstanding await may still hand back its receipt, and those receipts are verified and free their slots.next_eligible_atis written once for the run. - A rate-limit response on any lane's agent ends the window for the whole run, because every lane spends the same budget; every lane with work in flight is checkpointed to its own branch, and the record names the lane the response came from.
--fix-roundsis a per-run value that each lane spends on its own: a lane counts its own fix rounds against the run's cap, and a sibling's rounds never count against it.- A lane that exhausts the cap is handed back alone; what that does to the lanes open beside it is the next section.
After a hand-back, the siblings finish and are held (ruling DR6c)
The product thinker ruled, verbatim: "(c) FINISH, BUT HOLD THEM: when one piece is handed back, pieces in flight finish but nothing merges until the person re-plans; the person then decides whether the held pieces land as they are." The rules it implies:
- Who finishes. A hand-back stops only the lane handed back. Every sibling lane open at that moment runs to completion: its implementer, its rounds and its fix rounds go on under the same ceiling, window and cap, and it ends either held (below) or handed back itself. No new lane opens after a hand-back, and pending steps stay pending, because the run's intent is the person's to replan. No lane closes the spec (above).
- What
implement stepanswers in between. The single refusal of every step at a hand-back (loop.go:569-570at7f6eb5579, DR1's shape, itd-50 criterion 2) narrows: a step moves any sibling that still has work, and refuses, naming the hand-back and every held lane with the way out below, only once no lane of the run has anything left to do but wait for the person. The handed-back lane itself still starts nothing, as built. - The
heldstate. A sibling whose round passes after a hand-back does not land. It takes the lane stageheld, besidehanded-back: it holds no slot, starts nothing, and is never armed. The hold stops the landing before the first of its steps that reaches past the machine or merges: a lane that has not pushed is held before the push, so a discard leaves nothing on the forge; a lane that pushed and opened its pull request is held before arming, its pull request left open and unarmed. A lane already armed when the sibling is handed back (one landing at a time, so at most one) is disarmed through the forge client (gh pr merge <n> --disable-auto, beside the arming'sgh pr merge <n> --auto) and held; where the forge refuses the withdrawal, the step refuses naming the pull request and the person decides, and a lane whose pushed head the default branch already holds had landed before the hand-back and is recorded as landed. - What the run records.
lanes[].holdcarriessince(the time the lane was held),cause(the handed-back lane's id),head(the sha its passing round judged) andbefore(the landing step it stopped before:push,arm). Each hold writes a run-record entry naming the lane, the cause and the head; each release or discard writes one naming the person's choice. - What
implement statusshows. One row per held lane, beside the rows of the lanes alive: the lane, its step, the stageheld, the head judged, the lane whose hand-back caused it, the step it stopped before, and the way out (the two flags below). A held lane is not counted among the slots in use. - The person's decision, per held lane. The run's intent is re-planned
outside the loop (itd-50 criterion 3), and the loop cannot read a replan's
content, so the person's word on each held lane is what the loop acts on.
It is given through the verb that already moves the run,
implement step, with one of two flags naming one lane per invocation, as every invocation performs one move:implement step --release <lane-id>: the lane lands as it is. Its stage returns tolandand its landing resumes at the step it stopped before, by the landing rules above: synced first when a sibling landed after its base, with a fresh round over a moved head. It closes the spec only if it is the closing lane, which a run with a hand-back never has.implement step --discard <lane-id>: the lane does not land. The loop closes its pull request if it opened one, removes its worktree from the machine store and deletes its branch, and gives the lane the terminal stagediscarded; its step stays unlanded in the spec, so a replanned remainder carries it. Either flag is refused, changing nothing, when the lane it names is notheld, and while any lane of the run still has an agent out or a round to run, naming those lanes, so the person decides over the whole set of held pieces at once. The existingimplement release <record>is not reused: it removes a session's claim on a record, a different noun.
- The run stays stopped on its hand-back after every held lane is released
or discarded; the way past the handed-back lane is as built
(
handedBackWayOut, iss-2609301303434847).
Criteria (Given, When, Then)
Each is a test through the step interface, with fake agents writing the
receipts and returns, on the clock Options already carries; the host-playing
end-to-end test of the implement verb is the pattern. C2 and C3 are criterion
6 made concrete.
- C1, the count. Given a run with
--sub-agents 2whose lane reaches its validate stage, whenimplement stepis called twice, then the ruthless and security reviewers both await at once, the state file carries two awaits on that lane, andimplement statusnames 2 of 2 slots in use, each with its lane and role. - C2, the ceiling reached (criterion 6). Given C1's run with both
reviewers out, when
implement stepis called again, then it hands out no brief, exits 0 naming every lane alive with the role and receipt path it awaits, and writes the held work intowaitingwith the time it was first held; a second call before any receipt leaves that time unchanged. - C3, the slot filled and the wait counted (criterion 6). Given C2's
run and the clock moved on 14 minutes from the
implement stepcall that first found the ceiling reached (thesinceC2 wrote, not the moment the work became ready), when one reviewer's receipt is verified andimplement stepis called, then the held work takes the freed slot and the run record carries an entry naming its lane, its role and 14 minutes waited. - C4, implementers and reviewers together. Given
--sub-agents 3, lane 1 with both reviewers out and step 2 marked- needs: none, whenimplement stepis called until lane 2's implementer is handed its brief (the worktree, the brief and the implementer are one move each), then lane 2 opens in its own worktree and its implementer takes the third slot; the next call finds the ceiling reached. A stage the binary performs itself proceeds at the ceiling. - C5, the order. Given a full run where lane 2's security reviewer,
lane 1's fix implementer and a new lane for step 4 all wait, when one
slot frees and
implement stepis called, then lane 1's fix implementer takes it; at the next freed slot lane 2's reviewer; step 4's lane opens last. - C6, needs. Given a spec whose step 2 has no
- needs:line, when lane 1 is open, then no lane opens for step 2 until lane 1's pull request is an ancestor of the default branch, even with a slot free. Given- needs: noneon step 2, then its lane opens at the next free slot. Given- needs: 3on step 2, then the parser refuses naming the line. (The first case is ruling DR6b: a step without the line needs every step before it.) Given a spec of four steps where steps 1 and 3 are markedlanded:, step 4 carries- needs: 1, 2and step 2 carries- needs: 1, whenspec close --remainderruns, then the remainder lists old step 2 as step 1 with- needs: noneand old step 4 as step 2 with- needs: 1, the close names both rewritten lines, and the remainder parses. - C7, isolation and the receipt. Given lanes 1 and 2 each awaiting an implementer, when lane 2's receipt is handed back, then lane 2 advances and lane 1 is unchanged; the two worktrees and branches differ; a receipt path no await names is refused and the count is unchanged.
- C8, a clean sync. Given lane 1 has landed a change to one file and lane 2, branched before it, changed another file, when lane 2 reaches its landing, then the loop merges the default branch into lane 2 with a merge commit before arming, a fresh round judges the merge head, and no fix round is counted.
- C9, a conflicting sync. Given lane 1 has landed a change to a file lane 2 also changed, when lane 2 reaches its landing, then the merge is aborted with the branch unchanged, a fresh implementer is handed a sync brief naming the file and lane 1, a receipt whose head does not contain the merged sha is refused, and a verified one opens a fresh round; no fix round is counted.
- C10, one landing at a time. Given lanes 1 and 2 both waiting at
their landing with passing rounds, when
implement stepis called, then lane 1 arms its pull request first, and lane 2 arms only after lane 1's pull request has merged and lane 2 has synced and passed a fresh round. - C11, the pace across lanes. Given two lanes each with an agent out
and the window elapsed, when
implement stepis called, then it starts nothing on either lane, both receipts are still verified, andnext_eligible_atis written once. Given--fix-rounds 1and lane 1 failing its second round, then lane 1 is handed back, lane 2 runs on to its passing round and is held, never armed (ruling DR6c), no new lane opens, and no lane closes the spec. - C12, the schema. Given a version-7 state file with one lane
awaiting its implementer, when it is read, then the lane has one
await and the run advances on it; the next write is version 8 and carries
awaits, neverawaiting. Given a version-7 file carryingawaits,waitingorsyncs, or a version-8 file carryingawaiting, when it is read, then it is refused naming the version and the key. - C13, the hold and the person's decision (ruling DR6c). Given
--sub-agents 3,--fix-rounds 1, steps 2 and 3 both marked- needs: none, lane 1 handed back while lane 2's implementer is out, when lane 2's receipt is verified, its fake validators pass andimplement stepis called until nothing moves, then lane 2's stage isheldwithhold.causenaming lane 1,hold.headits judged head andhold.beforepush; the fake forge records no push, no pull request and no arming; no lane opens for step 3; the run record names the hold;implement statusshows lane 2 held with its cause, head and the two flags, and 0 slots in use; and the nextimplement steprefuses naming lane 1's hand-back and lane 2 held. Whenimplement step --releasenames lane 1 (handed back, not held), then it is refused and the state file is byte-identical. When it names lane 2, then lane 2's stage island, the run record names the release, and the following steps take it through the landing on the fake forge. Given the same held lane in a second run, whenimplement step --discardnames it, then its worktree and branch are gone, its stage isdiscarded, step 2 stays unlanded in the spec, and the fake forge records nothing.
Where the loop assumes one lane at a time
At the base of this amendment (c5b4305f6), each of these holds the single
lane or the single agent in place, and the build replaces or reads past it:
internal/core/implement/loop/state.go:214: "Lanes are the lanes opened so far, one at a time, in order."internal/core/implement/loop/state.go:242-244: oneAwaitingper lane.internal/core/implement/loop/state.go:410-420:current()is the first lane not done, the only lane any call acts on.internal/core/implement/loop/loop.go:550and:574:Advanceworks oncurrent()alone and returns while that lane awaits, so nothing else starts.internal/core/implement/loop/loop.go:619and:726: the next lane opens only when the current one is done, andloop.go:510opens it fromPending[0].internal/core/implement/loop/loop.go:683:Receiptlooks for the await oncurrent()only.internal/core/implement/loop/loop.go:493,:743and:947: the start result, the next move and the status block each report one lane.internal/core/implement/loop/loop.go:556-557andinternal/core/implement/loop/handback.go:3-8: oncecurrent()is handed back, every later step refuses, so the whole run stops at a hand-back (DR1's shape, itd-50 criterion 2). Ruling DR6c replaces it: the siblings in flight finish and are held, and a step refuses only once nothing is left to move ("After a hand-back, the siblings finish and are held").internal/core/implement/loop/state.go:171-183at7f6eb5579: the lane stages end atdoneandhanded-back;heldanddiscardedjoin them.internal/core/implement/loop/land.go:28-31at7f6eb5579: the landing arms once the lane's round passes, with no state that stops it before the push or the arming.internal/core/implement/loop/validate.go:3-5and:145-154: the round hands out "one fresh agent at a time", returning at the first validator without a verdict.internal/core/implement/loop/validate.go:219-222at7f6eb5579:auditsHeredecides the closing lane as the last lane opened with nothing pending. With- needs: nonea later lane can finish first, so the closing lane is re-derived from the rule above: it reaches its landing with no step pending, no other lane open, and no lane of the run handed back.spc-2609202134338445,## Progress, the entry for lane fidelityOnce (piece 8): the validators run "one at a time".itd-2609212103565953,## What's In Scope("The loop") and criterion 2 of its## Acceptance Criteria, andspc-2609212138246060,## Scopeitem 3: the next step's lane starts after the previous has merged. This holds unchanged for a step with no- needs:line; a step marked otherwise is the exception DR6 admits, and that intent's wording is amended when the needs line is built.
Footprint of this piece
- packages: internal/core/implement/loop, internal/core/spec,
internal/core/intent (the remainder's
needsrewrite), internal/surface/cli, internal/core/statusblock, internal/core/site - commands/:
commands/implement.md(andcommands/build.mdwhere it names the loop's moves) documentsimplement step --release <lane-id>andimplement step --discard <lane-id>, so the two flags are wired on the plugin surface as on the CLI - a shape change, not only new content:
statusblock.Startedcarries oneLaneper run (loop.go:948-957atc5b4305f6fills it fromcurrent()alone), and the block reports one row per lane alive, soStartedcarries every lane alive and each reader of it changes with it, the site's status page (internal/core/site/status.go) included - built on: piece 9 of
spc-2609202134338445, the landing (land.go), which C8 to C10, C13 and the landing rules extend ("What this piece is built on") - tests: C1 to C13 through the step interface with fake agents and a fake
forge; the
needsparser over a stepped spec; the remainder'sneedsrewrite; a version-7 state file read and advanced, and the version refusals
Progress
- Landed before this piece: pieces 1 and 2 (the pace's layering and the window clock, criteria 1 to 5 and 9), and the ceiling recorded with the run.
- Landed (lane dr6Build): pieces 3 and 6, criterion 6. A run works in
parallel up to its ceiling (ruling DR6): a slot is an outstanding await, the
state file's
awaitson any lane, counted againstpace.sub_agents, and implementers and validators take the same slots (internal/core/implement/loop/schedule.go). Eachimplement stepperforms a stage the binary owns on any lane first, then gives a free slot to the first waiting work in the order this spec gives; at the ceiling it hands out nothing, names every lane alive and recordswaitingwith the time first held, and the move that serves an item records the minutes it waited.implement receiptlooks its path up across every lane. A step's- needs:line is parsed with the default of every earlier step (ruling DR6b,internal/core/spec/steps.go), andspec close --remainderrewrites it against the remainder's numbering (spec.CarryUnlanded). Landing is one lane at a time; a lane whose sibling landed since its base is synced with a merge commit and judged by a fresh round, a conflicting sync goes to a fresh implementer, and a sync counts no fix round (sync.go). After a hand-back the siblings finish and are held before their push or arming, an armed one disarmed, untilimplement step --releaseor--discard(ruling DR6c,hold.go); no lane closes the spec, and the closing lane's audit reads each lane's own diff. The state is schema version 8.implement statusnames the slots in use and the held lanes, and the status block reports one row per lane alive. C1 to C13 are tests through the step interface (internal/core/implement/loop/parallel_test.go, and C6's parser and remainder ininternal/core/spec/needs_test.goandinternal/core/intent/steps_test.go). - Remaining: pieces 4 and 5, criteria 7 and 8. The budget check and the rate-limit checkpoint wait on a runner that reports its quota and its rate-limit responses (itd-2609201916056194); they travel in the remainder spec this close mints.
Frontmatter
| id | spc-2609202134341288 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-2609202134341288-an-autonomous-implementation-run-paces.md
open on GitHub ↗ · commit history ↗
References1
- itd-2609201925079472An autonomous run paces itself by default, and one flag sets the paceimplements
mentions16
- dcn-26093000000000052026-09-30 — Ruling DR6 (the product thinker, 2026-09-29, verbatim: "per-run agent limit: WORK IN PARALLEL — a run may build several pieces and run reviewers concurrently up to its limit (new build-loop work; then AC6 is testable).") is designed into spc-2609202134341288 as its piece 6 and recorded as decision 7 of itd-2609201925079472 (lane dr6Spec of autonomous run A; records only, nothing built). A slot is one outstanding await in the run's state file, implementers and validators alike; the validators of a round run side by side; a spec step runs beside earlier ones only when it declares `- needs:` (absent, it needs every earlier step, so itd-2609212103565953's in-order landing holds for every spec written so far); a lane lands one at a time and, when a sibling landed after its base, is merged with the default branch (never rebased) and judged by a fresh round, a sync that does not count against `--fix-rounds`. Criterion 6 is tested through the spec's criteria C1 to C12.
- dcn-26093000000000202026-09-30 — Rulings DR6b and DR6c (the product thinker, 2026-09-30, relayed by the orchestrator of autonomous run A) close the two questions the DR6 spec review left open in spc-2609202134341288 and are recorded as decision 8 of itd-2609201925079472 (lane dr6Spec, amend round 3; records only, nothing built). DR6b, verbatim: "(a) ONE AFTER ANOTHER BY DEFAULT: a step runs alongside earlier ones only if its plan says so; nothing already planned changes; reviews run side by side; the 2026-09-21 wording stands." A step without a `- needs:` line needs every earlier step, so itd-2609212103565953's wording of 2026-09-21 stands. DR6c, verbatim: "(c) FINISH, BUT HOLD THEM: when one piece is handed back, pieces in flight finish but nothing merges until the person re-plans; the person then decides whether the held pieces land as they are." After a hand-back the sibling lanes run to completion and each passing one takes the lane stage `held` (before its push, or before arming once pushed; an armed one is disarmed), recorded in `lanes[].hold` and shown by `implement status`; the person releases or discards each with `implement step --release <lane>` or `--discard <lane>` (the claim verb `implement release` is a different noun and is not reused). Tested by the spec's criterion C13.
- dcn-26093000000000262026-09-30 — The implement loop's state file is schema version 9 where a run works in parallel (ruling DR6, spc-2609202134341288), not the version 8 that spec's amendment names: the runner's record (itd-2609201916056194) reached the default branch first, on the v0.12.0 cut, and took version 8, so the two are numbered in the order they landed (integration integ24d of autonomous run A, `internal/core/implement/loop/state.go`). A version 7 or 8 file migrates its lane's one `awaiting` into `awaits` and is written back at version 9; a version 7 or 8 file carrying what only version 9 writes, and a version-9 file carrying `awaiting`, are refused naming the version and the key. The closed spec keeps its text; brief 34-build.md names the three versions.
- dcn-26100200000000192026-10-02 — Ruling DR6d-1 of the product thinker (2026-10-02, relayed by abcd-a2 [75597f]; same local-tier file as the H3-0 entry above; recorded by lane rd2), verbatim: "preparing parallel steps' worktrees: ONLY WHEN A HELPER IS FREE — a step's worktree is made just before an agent takes it; at most the agent ceiling's worth on disk." Not built at this entry's base; lane dr6d of autonomous run A builds it in the build loop of spc-2609202134341288.
- iss-2609211105014235Under the two-sub-agent ceiling, reviews run one at a time in the gaps and each fix round is a fresh agent, so the pattern implement, review, fix, security review, fix consumes the whole ceiling for one lane: in the pilot's first two-hour session lane D never opened (40 minutes of pure ceiling wait) and lane B's review waited 27 minutes; in the second session D waited 30 minutes before a stop. Measured cost across the run: 178 agent-minutes in 120 wall-minutes (session 1), 149 in 122 (session 2). Wanted, as a pacing input for itd-2609201925079472: the ceiling counts reviewers separately from implementers, or the window length is set from the lane shape (implement plus two reviews plus two fix rounds is about 90 agent-minutes per lane), rather than a flat two.
- iss-2609240227236354itd-6 passes its readiness gate (intent ready itd-6: READY, all seven checks ok) but cannot be built from today's tree: its spec spc-2609211950427074 stands on four things that do not exist. (1) The build loop's validator stage the adapter implements, itd-2609201916151817 (planned, spec spc-2609202134338445 open; there is no build verb). (2) The layered oracle.review resolver the model-tier and pacing intents share, itd-2609170822093401 and itd-2609201925079472 (both planned, specs spc-2609180535002478 and spc-2609202134341288 open). (3) The command-line runner the adapters chapter entry sits beside, itd-2609201916056194 (planned, spec spc-2609221533057881 open). (4) An MCP client, which the spec's Approach names as a new dependency subject to the new-dependency sign-off; go.mod has none. itd-6 declares builds_on [itd-2] only, so the gate has no edge to refuse on and READY reads as buildable to a run that picks it.
- iss-2609301303434847A handed-back implement-loop run names no way out: handedBackRefusal's remedy (internal/core/implement/loop/handback.go) says only that the loop starts nothing further, and build next's exclusion of the intent (next.go) says 'resume it with abcd implement step', a step that refuses on a handed-back lane. The run stays live by construction (State.Complete is false at handed-back) and no verb clears it, so the person is told of no way to start over.
- iss-2610262037021391The step glossary says the build loop takes a spec's steps one lane at a time; the build chapter runs lanes in parallel up to a ceiling.
- itd-29Domain Experts Run A Spec Autonomously And Recover From Anything Without Touching Git
- itd-50The Audit Loop Drives An Intent To Acceptance — Or Calls For A Replan
- itd-2609201916056194A role runs through the command-line harness the operator chose, and every fall back to the host is recorded
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is open
- itd-2609212103565953A spec lists its steps, and the build lands them one at a time
- spc-2609202134338445one-verb-takes-a-single-intent-from-ready-to-delivered-witho
- spc-2609212138246060a-spec-lists-its-steps-and-the-build-lands-them-one-at-a
- spc-2609301921521360the-budget-check-and-the-rate-limit-checkpoint