Development record
An autonomous run paces itself by default, and one flag sets the pace
Press Release
A run started with
abcd build(orabcd drain, or theabcd implementloop they hand over to) follows a pace it was never told: a working window, a pause after it, and a ceiling on lanes alive at once. The three numbers come from one place, layered: a flag on this run wins over the repository's abcd configuration, which wins over the machine's, which wins over the bundled default; the run record names which layer applied.--pace <work-minutes>/<pause-minutes>and--sub-agents <n>set them for one run. At the window's end the loop starts no new lane, lets a running lane finish its step and checkpoint to its branch, writesnext_eligible_atinto the state file and exits; an invocation before that time refuses and names it, so the pause survives a killed process and needs no sleeping model. Today every autonomous run carries its pace in its prompt as prose, and two runs on one machine cannot share it."Two pilots, two prompts, two paces typed by hand, and no way to know the other one was keeping to its ceiling," said a technical facilitator who ran both. "Now the pace is a line in the config and a timestamp in the state file, and a run that starts early is refused."
Why This Matters
Two pilots on one machine in the week of 2026-09-15 ran on two paces written into their prompts by hand, two hours of work then five off with two lanes here, two hours then four off with three lanes there, and neither could be sure the other honoured its ceiling. The pace exists because the model budget resets on a window; a run that ignores it runs into the reset mid-lane and loses the lane.
Mechanism
We expect a pace read from layered configuration and enforced by a timestamp in the state file to be honoured by every run without being told, because the loop that reads the state is the only thing that starts a lane; shown wrong if a run starts a lane inside a pause or above the ceiling, which the run record would show, or if the budget window the pause exists for moves in a way minutes cannot express.
Scope Conditions
- Holds for a run driven by the implement verb's loop; a session pacing itself by prompt is outside it.
- Holds per run: The ceiling counts this run's lanes and validators.
- The bundled default is a choice, not a measurement: The two pilots ran 120/300 with two lanes and 120/240 with three, and a repository overrides it in its configuration.
What's In Scope
- The three numbers, their four layers, the flags, the window clock and
next_eligible_atin the state file, the ceiling on this run's lanes and validators, the budget check before a run starts, and the checkpoint on a rate-limit response.
What's Out of Scope
- A ceiling across runs on one machine: The register's (
itd-2609150819440345), where a lane registry can live. - Mid-run telemetry and an operator's hand verbs over the state file: Dropped with
itd-29until wanted.
Decisions
Settled on 2026-09-20 with one defensible answer each, on the product thinker's ruling that such a decision is recorded rather than asked (iss-2609202055103741):
- Re-entrant pause. The pause is
next_eligible_atin the state file; an invocation before it refuses and names the time. No process sleeps. - Units are minutes, as the product thinker specified the flag; a quota-window signal, where a harness exposes one, is a later refinement and is recorded as such.
- Cross-run enforcement is the register's. This intent bounds one run; the design review found a repository file cannot bound two runs across repositories.
- Layering is flag, then repository, then machine, then bundled, the order the model-tier intent already uses.
- The bundled default is 120 minutes of work, 300 of pause, two lanes, the product thinker's numbers for this repository's runs on 2026-09-20; a repository that measured otherwise writes its own.
- The budget check and the rate-limit checkpoint come here from
itd-29, superseded on 2026-09-20 by the implement verb: A run refuses to start when the estimated cost exceeds the remaining quota where the runner reports one, and a rate-limit response checkpoints the lane and ends the window early. - A run works in parallel up to its ceiling (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)."). The validators of a round run side by side, the lanes of steps that do not need each other run side by side, and implementers and reviewers share the ceiling; criterion 6 is tested through the concurrent loop the spec's piece 6 designs.
- Steps run one after another unless their plan says otherwise, and a hand-back holds the siblings' landings (rulings DR6b and DR6c, the product thinker, 2026-09-30). 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." 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." A step's default
needsis every earlier step, opted out of per step; after a hand-back the sibling lanes run to completion and each passing one isheld, never armed, until the person releases or discards it withimplement step --release <lane>or--discard <lane>(the spec's piece 6, criteria C6, C11 and C13).
Open Questions
None open.
Acceptance Criteria
- Given no flag and no configuration, when a run starts, then it runs on 120/300 with two lanes and the run record names the bundled layer.
- Given a repository configuration and a machine configuration that disagree, when a run starts without a flag, then the repository's values apply and the record says so.
- Given
--pace 90/240 --sub-agents 3, when a run starts, then those values apply over every configured layer and the record names the flag. - Given a window that has elapsed, when the loop is invoked, then it starts no lane, lets a running lane finish its current step and checkpoint to its branch, writes
next_eligible_at, and exits 0 naming the time. - Given
next_eligible_atin the future, when the loop is invoked, then it refuses naming the time and changes no state. - Given the ceiling reached, when the loop would start a lane or a validator, then it starts nothing, names the lanes alive, and exits; the next invocation fills the slot, and the record counts the minutes the slot was waited for.
- Given a runner that reports remaining quota and an estimate that exceeds it, when a run starts, then it refuses naming both numbers and writes no state; a runner that reports no quota is named and the check is skipped out loud.
- Given a rate-limit response from a runner mid-lane, when the loop reads it, then the lane is checkpointed to its branch, the window ends early with
next_eligible_atset, and the record names the response. - Given a malformed pace or ceiling, when a run starts, then it refuses naming the value and the accepted form, and writes no state.
Typed Links
- builds_on
itd-2609201916151817(the implement verb): The loop this pace bounds. - refines
itd-2609170822093401(the model tier): The same configuration layering.
Audit Notes
Fidelity review — receipt rcp-c411be255805 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:922986f5804db2851ba4918cd59959f541b9b4df9c2aa7a626ce91f58ab15c81 Input attestations: diff:3f21f58b5d869ea6ed7299b6ad5c38a32a468f6f..8a08de576cdaa182b11719116cb64e43f5bcbc7a on build/run-2610100550297632-lane-1@sha256:73677a02544e3a04a092b71888d0014e06226aefae31de36621b3d27d78320d0;
Acceptance rollup: MET 7 · MET_WITH_CONCERNS 2 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: The bundled constants are 120/300/2 (pace.go:28-30); TestARunWithNoConfigurationRunsOnTheBundledPace asserts the state and the run record name 120/300, 2 sub-agents and the bundled layer, and passes on a scratch copy of 8a08de576; the CLI test asserts the same line in implement status. evidence: internal/core/implement/loop/pace.go:28 — "BundledWorkMinutes = 120" evidence: internal/core/implement/loop/pace_test.go:82 — "the record names the pace and the bundled layer" evidence: internal/surface/cli/build_pace_surface_test.go:61 — "pace: 120/300 minutes, 2 sub-agents (bundled)"
- ac-2 — MET: resolvePace reads each key through layered.Load (flag, repo, machine, bundled); TestTheRepositoryPaceWinsOverTheMachines sets disagreeing machine and repo files, asserts 100/200/4 from layer repo with origin .abcd/config.json, and that the record names the repository's file and not the machine's; it passes. evidence: internal/core/implement/loop/pace.go:200 — "func resolvePace(roots layered.Roots, f paceFlags) (Pace, error)" evidence: internal/core/implement/loop/pace_test.go:105 — "wantPace(t, st.Pace, 100, 200, 4, "repo")" evidence: internal/core/implement/loop/pace_test.go:110 — "the record names the repository's file and not the machine's"
- ac-3 — MET: TestThePaceFlagsWinOverEveryLayer starts with both files set and --pace 90/240 --sub-agents 3, asserts 90/240/3 from layer flag with origins '--pace 90/240' and '--sub-agents 3' and the record naming both flags; the CLI test asserts the rendered line names the flag layer for each value; both pass. evidence: internal/core/implement/loop/pace_test.go:143 — "wantPace(t, st.Pace, 90, 240, 3, "flag")" evidence: internal/core/implement/loop/pace_test.go:144 — "st.Pace.WorkMinutes.Origin != "--pace 90/240" || st.Pace.SubAgents.Origin != "--sub-agents 3"" evidence: internal/surface/cli/build_pace_surface_test.go:85 — "pace: 90/240 minutes, 3 sub-agents (work from --pace 90/240, the flag layer"
- ac-4 — MET: advance closes an elapsed window before any move (windowElapsed/closeWindow write next_eligible_at and the pause entry, the call returns nil error with Next naming the time); the pace test verifies the implementer's receipt is still taken inside the pause and the lane advances, and C11 verifies two lanes' receipts both verify with next_eligible_at written once. evidence: internal/core/implement/loop/loop.go:647 — "if until, ok := windowElapsed(*st, now); ok {" evidence: internal/core/implement/loop/loop.go:708 — "st.NextEligibleAt = &until" evidence: internal/core/implement/loop/pace_test.go:293 — "the running lane checkpoints inside the pause" evidence: internal/core/implement/loop/parallel_test.go:757 — "both receipts are verified during the pause"
- ac-5 — MET: advance refuses with a 'pause' contention naming next_eligible_at in RFC3339 before any state change; TestAPauseRefusesUntilNextEligibleAt asserts the refusal names 2026-09-25T13:00:00Z and no stage ran, and the pace test asserts the state bytes are identical after the refusal. evidence: internal/core/implement/loop/loop.go:632 — "return false, contend("pause", "", "", "the run is paused until "+st.NextEligibleAt.UTC().Format(time.RFC3339)," evidence: internal/core/implement/loop/loop_test.go:673 — "if r.Stage != "pause" || !r.Contention || !strings.Contains(r.Reason, "2026-09-25T13:00:00Z")" evidence: internal/core/implement/loop/pace_test.go:304 — "a refused step inside the pause changes no state and performs nothing"
- ac-6 — MET: moveAgent at slotsInUse >= ceiling hands out nothing, sets CeilingReached and names the lanes alive; tookSlot records the whole minutes waited when the next call serves the item. C1-C3 (TestTheCeilingCountsValidatorsAndHoldsTheWaitingWork) assert the names, the unchanged waiting time, the slot filled by the next call and the record line '14 minute(s)'; it passes. evidence: internal/core/implement/loop/schedule.go:538 — "if st.slotsInUse() >= st.ceiling() {" evidence: internal/core/implement/loop/schedule.go:686 — "took a freed slot after waiting %d minute(s) at the run's ceiling of %d" evidence: internal/core/implement/loop/parallel_test.go:356 — "the intent-auditor of lane-1 took a freed slot after waiting 14 minute(s)"
- ac-7 — MET_WITH_CONCERNS: budgetCheck runs after every other check and before the run directory is made; an estimate over a reported quota fails the row naming both numbers (TestAQuotaUnderTheEstimateRefusesTheStartNamingBoth asserts '5 agent run(s) left', 'estimated to start 6' and zero runs on disk); a runner reporting none is named and the row says 'skipped' in the checks and the run record. Concerns: (1) the comparison is reached only through the Options.Quota test seam, because QuotaReporter is implemented by no shipped runner (runner.go:135-137), so today every real run skips the check; (2) the estimate is a lower bound that omits fix rounds and syncs (budget.go:13). evidence: internal/core/implement/loop/loop.go:332 — "budget := budgetCheck(chk, o)" evidence: internal/core/implement/loop/budget.go:133 — "Detail: "the run's estimate exceeds the quota its runner reports: "" evidence: internal/core/implement/loop/budget_test.go:79 — "a refused budget writes no state" evidence: internal/core/implement/loop/budget_test.go:112 — "func TestARunnerThatReportsNoQuotaIsNamedAndTheCheckSkipped" evidence: internal/core/runner/runner.go:135 — "QuotaReporter is a Runner that reports its remaining quota. Neither shipped"
- ac-8 — MET_WITH_CONCERNS: The claude runner reads a rejected rate_limit_event or an assistant rate_limit error as ReasonRateLimited; Dispatch returns it without fallback; Drive routes it to rateLimitWindow, which writes next_eligible_at once (a running pause kept), records the response with the lane and runner, and checkpoints the hit lane; the end-to-end test through Drive and the fake claude asserts all of it and passes. Concerns: (1) 'checkpointed to its branch' is delivered as save-aside plus reset to the branch's last commit, so the agent's uncommitted work lives in the run's aside directory, not on the branch; (2) only the claude CLI's response shapes are recognised; an opencode limit still falls back as an ordinary refusal (dcn-2610100000000002); (3) a limit met by a host-run agent never reaches the loop (drive.go:152-158 sees only Dispatch failures). evidence: internal/core/runner/claude.go:101 — "func claudeRateLimit(out []byte) string {" evidence: internal/core/runner/dispatch.go:115 — "if fl.Reason == ReasonRateLimited {" evidence: internal/core/implement/loop/drive.go:158 — "return rateLimitWindow(repoRoot, runID, res.Lane, aw, fl, o)" evidence: internal/core/implement/loop/ratelimit.go:123 — "st.NextEligibleAt = until" evidence: internal/core/implement/loop/ratelimit_test.go:93 — "the record names the response and the lane it came from" evidence: internal/core/implement/loop/ratelimit.go:15 — "uncommitted work is kept for review and never built on), and its await"
- ac-9 — MET: parsePaceFlags and the resolver's range check refuse at stage pace naming the value and paceForm (the accepted form); TestAMalformedPaceIsRefusedAndWritesNoState covers 16 malformed flag and file cases and asserts the value, the form and an absent run tier; the CLI test asserts exit 2 with the form named; both pass. evidence: internal/core/implement/loop/pace.go:62 — "var paceForm = "--pace takes < work-minutes>/< pause-minutes> in whole minutes" evidence: internal/core/implement/loop/pace_test.go:202 — "runTierAbsent(t, repo.Root())" evidence: internal/surface/cli/build_pace_surface_test.go:111 — "if code != 2 || !strings.Contains(errOut, "refused at pace") || !strings.Contains(errOut, "< work-minutes>/< pause-minutes>")"
Gap audit:
- honoured:
- The three numbers come from one place, layered flag over repository over machine over bundled, and the run record names which layer applied evidence: internal/core/implement/loop/pace.go:200 — "func resolvePace(roots layered.Roots, f paceFlags) (Pace, error)" evidence: internal/core/implement/loop/pace_test.go:147 — "the record names the flags"
- At the window's end the loop starts no new lane, writes next_eligible_at and exits; an invocation before that time refuses and names it, so the pause survives a killed process evidence: internal/core/implement/loop/loop.go:631 — "if st.NextEligibleAt != nil && now.Before(*st.NextEligibleAt) {" evidence: internal/core/implement/loop/loop.go:707 — "func closeWindow(st *State, now, until time.Time) {"
- A ceiling on this run's lanes and validators, with the minutes a slot was waited for counted in the record evidence: internal/core/implement/loop/schedule.go:671 — "func tookSlot(st *State, key, role string, now time.Time) {" evidence: internal/core/implement/loop/parallel_test.go:321 — "if st.SlotsInUse() != 2 || st.Ceiling() != 2 {"
- A run refuses to start when the estimated cost exceeds the remaining quota where the runner reports one, naming both numbers and writing no state evidence: internal/core/implement/loop/budget.go:126 — "if s.runs > q.Remaining {" evidence: internal/core/implement/loop/loop.go:334 — "if !budget.OK {"
- A rate-limit response is never fallen back on; it ends the window early with next_eligible_at written once and the record naming the response, the runner and the lane evidence: internal/core/runner/dispatch.go:115 — "if fl.Reason == ReasonRateLimited {" evidence: internal/core/implement/loop/ratelimit.go:116 — "if until == nil || !now.Before(*until) {" evidence: internal/core/implement/loop/ratelimit_test.go:185 — "next_eligible_at is written once"
- The budget line and the rate-limit block are wired on the CLI (build, build next, drain) and the plugin surface evidence: internal/surface/cli/drain.go:132 — "cfg, err := loadRunners(cmd, roots)" evidence: internal/surface/cli/build_pace_surface_test.go:190 — "func TestBuildNamesTheBudgetCheckAndTheRunnersItSkipped" evidence: commands/build.md:180 — "Last, once every other check passes, the budget check asks each runner a role" evidence: commands/implement.md:288 — "A runner's rate-limit response ends the window early in the same way"
- diverged:
- The rate-limited lane is 'checkpointed to its branch': delivered as the agent's uncommitted work and partial receipt saved aside under the run directory and the worktree reset to the branch's last commit, so nothing new reaches the branch evidence: internal/core/implement/loop/ratelimit.go:157 — "aside, err := saveAside(repoRoot, root, st.RunID, *lane, lane.Awaits[k], why, aw.Role == RoleImplementer, now)" evidence: internal/core/implement/loop/ratelimit_test.go:108 — "lane-2's worktree is reset to its last commit" evidence: dcn-2610100000000002 — ""Checkpointed to its branch" reads as the restart of 2026-10-09 reads a gone agent"
- The budget comparison exists only behind an optional QuotaReporter that no shipped runner implements, so every real run today skips the check out loud; the comparison path is exercised through the Options.Quota test seam evidence: internal/core/runner/runner.go:139 — "type QuotaReporter interface {" evidence: internal/core/implement/loop/budget.go:139 — "detail = "skipped, as no runner of the run reports a quota: " + detail" evidence: internal/core/implement/loop/loop.go:57 — "Quota func(name string) (runner.Quota, bool, error)"
- The estimate from the spec's size is a lower bound: fix rounds and syncs are not counted evidence: internal/core/implement/loop/budget.go:13 — "Fix rounds and syncs"
- missing:
- Rate-limit detection for the opencode runner: an opencode limit is still an ordinary refusal and falls back as before evidence: dcn-2610100000000002 — "an opencode limit stays a refusal and falls back as before" evidence: internal/core/runner/claude.go:80 — "if what := claudeRateLimit(res.stdout); what != "" {"
- A rate limit met by a host-run sub-agent: the loop reads only a Dispatch failure, and no verb lets the host report one evidence: internal/core/implement/loop/drive.go:152 — "out, err := d.Dispatch(ctx, req)"
Scope-condition dispositions:
- cond-2609202134346930 — survived: Every enforcement point is inside the implement verb's loop: the pause and window close in advance, the ceiling in moveAgent, the budget check in start, and the rate-limit checkpoint reached only from Drive's Dispatch; a session pacing itself by prompt, and a host-run agent's limit, are outside what the loop can see, as the condition assumed. evidence: internal/core/implement/loop/loop.go:639 — "The window clock (itd-2609201925079472) is the run's, one for every" evidence: internal/core/implement/loop/drive.go:158 — "return rateLimitWindow(repoRoot, runID, res.Lane, aw, fl, o)"
- cond-2609202134341920 — survived: The ceiling is this run's pace.sub_agents counted against the awaits in this run's state file (implementers and validators alike), the budget estimate is per run, and a rate limit pauses only the run it hit (the implementer's report notes a drain's own window does not end early), so the per-run assumption held. evidence: internal/core/implement/loop/schedule.go:538 — "if st.slotsInUse() >= st.ceiling() {" evidence: internal/core/implement/loop/parallel_test.go:314 — "r.Slots != 2 || r.Ceiling != 2" evidence: internal/core/implement/loop/ratelimit.go:81 — "func rateLimitWindow(repoRoot, runID, laneID string, aw Await, fl *runner.Failure, o Options) (StepResult, error) {"
- cond-2609202134341625 — survived: The bundled 120/300/2 is three named constants with the comment that a repository that measured otherwise writes its own under pace in .abcd/config.json; the repository override is tested, and the rate-limit pause falls to BundledPauseMinutes only when the run carries no pace.
evidence: internal/core/implement/loop/pace.go:23 — "The bundled pace: 120 minutes of work, 300 of pause, two lanes (decision 5,"
evidence: internal/core/implement/loop/pace_test.go:99 — "repoConfig(t, repo,
{"pace": {"work_minutes": 100, "pause_minutes": 200, "sub_agents": 4}})" evidence: internal/core/implement/loop/ratelimit.go:117 — "pause := BundledPauseMinutes"
Grounds
- pursued: we expect a pace read from layered configuration and enforced by a timestamp in the state file to be honoured by every run without being told, because the loop that reads the state is the only thing that starts a lane; shown wrong if a run starts a lane inside a pause or above the ceiling, which the run record would show
Frontmatter
| id | itd-2609201925079472 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | minor |
.abcd/development/intents/shipped/itd-2609201925079472-an-autonomous-implementation-run-paces.md
open on GitHub ↗ · commit history ↗
References2
- itd-29Domain Experts Run A Spec Autonomously And Recover From Anything Without Touching Gitrelated
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is openbuilds on
Referenced by19
- iss-2610262032080136The press release lists the itd-29 `/abcd:run` operator surface as in-scope plumbing; itd-2609201925079472 says those hand verbs were dropped with itd-29.related
- iss-2610262037482641The pacing intent's bundled default has a 300-minute pause; the build chapter's bundled default has no pause.related
- iss-2610262041445723The loop glossary entry says abcd never owns the run loop; the shipped pacing intent describes the loop abcd's own build, drain and implement verbs run.related
- iss-2610262043285832itd-2610100704270254 sets a run's sub-agent budget at eight middle-class agents from repository config; itd-2609201925079472 already ships the run's ceiling at two lanes, layered from flag to bundledrelated
- iss-2610262045422454itd-2609212103565953 starts each step's lane only after the previous has merged; itd-2609201925079472 runs independent steps' lanes side by siderelated
- iss-2610262045503347--release lands a held lane on implement step but gives back a reservation on buildrelated
- itd-6RepoPrompt over MCP is one opt-in reviewer route, beside the host's own agentsbuilt on by
- itd-82`abcd drain` fixes the issues that need no decision, and hands the rest back by kindbuilt on by
- itd-2609211116005482`abcd build next` picks the readiest planned intent itself, and writes down whybuilt on by
- itd-2609221656373558Two sessions share one run, and the run measures which way of dividing the work is bestbuilt on by
- itd-2610100704270254An abcd run sets how many sub-agents it keeps alive by the models they run onbuilt on by
- itd-2610170634394993Headless agents in parallel coordinate through the shared remote, not conversationrelated
- itd-2610240922461281The review set is a setting, in model classesbuilt on by
- itd-2610262127138979A preflight waits for its turn as one whole runbuilt on by
- itd-2611040344170206Unattended work runs on a named profile, one per way of payingbuilt on by
- itd-2611041549461012A session that takes over a run steps on from what abcd wrote downbuilt on by
- itd-2611041549471712A person stops the unattended runs of each account that chose a folder, with one commandbuilt on by
- spc-2609202134341288an-autonomous-implementation-run-paces-itself-by-default-andimplemented by
- spc-2609301921521360the-budget-check-and-the-rate-limit-checkpointimplemented by
mentions15
- dcn-26092500000000052026-09-25 — The layered configuration resolver has one home, `internal/core/layered`, and two file families; every later consumer reads through it rather than opening a file of its own (lane implementer, autonomous run A, on spc-2609180535002478 part 1). Precedence is flag, then repository, then machine, then bundled, and every value comes back with the layer and the origin that supplied it. The families are `layered.Config`, which is `.abcd/config.json` in the checkout the session resolved and `~/.abcd/config.json` on the machine, and `layered.OracleRouting`, which is `.abcd/config/oracle-routing.json` and `~/.abcd/oracle-routing.json`, the files itd-2609170822093401 names. Scalar keys go in `Config` under a namespace: `pace.*` (itd-2609201925079472), `oracle.review` (itd-6), `roles.<role>.runner` (itd-2609201916056194) and `match.threshold` (itd-2609212137116617). The per-agent routing table keeps its own file, as its intent names, and no new file family is added without an entry here. Loudness: an absent file is an absent layer; a present file that is malformed, symlinked, carries a key twice, trails content, declares the wrong `schema_version`, or (machine layer) is not the caller's own owner-only-writable file is an error naming it; a consumer claims its namespace and every key in it (`Stack.Claim`, with `*` for an open segment such as the role name), and an unknown key under a claimed namespace is refused. Because ahoy writes `oracle.backend` into the shared file, whoever claims `oracle` lists `backend` too. A winning value that does not decode or fails its check is refused, and nothing falls through to a lower layer or to the default.
- 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-26101000000000012026-10-10 — Ruling of the product thinker on the bundled pace (given in a build session, on the report that its run had entered a 300-minute pause): "Remove that forever, we don't need a pause", then "it must be set to zero by default". The bundled pause of decision 5 of itd-2609201925079472 (120 minutes of work, 300 of pause) becomes 0: `BundledPauseMinutes` is 0, so a run that sets no pace runs window after window with no pause, and a repository or machine that wants one sets `pace.pause_minutes`. The working window, the sub-agent ceiling and the fix rounds are unchanged. The intent's record keeps decision 5 and its first criterion as written (120/300), which this entry supersedes for the pause; iss-2610100546338578 carries the change.
- dcn-26101000000000022026-10-10 — Four points spc-2609301921521360 left open, settled by lane-1 of run-2610100550297632 in building it (itd-2609201925079472 criteria 7 and 8). (1) A runner's quota is counted in agent runs (`runner.Quota.Remaining`, reported through the optional `runner.QuotaReporter`), and the estimate from the spec's size is the least the run starts: per step to build, one implementer and one round of the two reviewers, and the fidelity audit once for an intent; fix rounds and syncs are not foreseen. Neither shipped runner reports a quota (the claude CLI and opencode expose none a launch can read before it starts), so today every run names its runners and skips the check out loud; the comparison is reached by a runner that reports one. (2) The rate-limit response is read from the claude CLI's own events, a `rate_limit_event` whose `rate_limit_info.status` is `rejected` or an assistant message whose `error` is `rate_limit`, on a run that did not finish; opencode's error events carry no rate-limit shape this build has verified, so an opencode limit stays a refusal and falls back as before. A rate limit is never fallen back on: every route spends the budget the window paces. (3) "Checkpointed to its branch" reads as the restart of 2026-10-09 reads a gone agent (iss-2610080620372731): the limited agent's uncommitted work and partial receipt are saved aside for review, never built on, its worktree reset to the branch's last commit, and its await dropped so the first step after the pause hands the same work to a fresh agent; a limited validator has only its partial return saved aside, since it edits nothing and may share the worktree with its round. Every other lane with work in flight is checkpointed at its branch's head in the record and left running, its agents' receipts still taken inside the pause, because their agents are not this process's to stop. (4) The pause after a rate limit is the run's own pause minutes, not the reset time a harness reports (decision 2 of the intent), and a pause already running is kept.
- dcn-26101000000000052026-10-10 — Three defects the reviewers of PR #886 (itd-2609201925079472, run-2610100550297632) noted below their bar are deferred, not fixed in the change that set the bundled pause to zero: iss-2610100913086397 (a rate-limited validator whose partial return cannot be saved aside leaves its lane stuck, its named `--restart` refused), iss-2610100913088696 (the rate-limit checkpoint matches an await by receipt path alone, against its comment) and iss-2610100913096412 (the run-start quota query has no deadline and its error is not home-redacted). Reason: each needs a tested change to the rate-limit and budget code in `internal/core/implement/loop/`, which steps 3 to 5 of spc-2609212015054359 (itd-82, the drain) rework next, and none is reachable in a run today without a second fault: a refused save-aside on a rate limit, an operator restart racing the dispatch, or a quota-reporting runner, of which none ships. They are taken up with the rate limit's own wait (iss-2610100602595553), or by the drain's steps where those touch the same code.
- iss-2609202055103741The interview rule makes the agent offer options that are not real choices, and asks a question where only one answer is sound. The GRILL domain in .abcd/rules.json says options widen and never recommend, no starred default, the null answer always available, and every question through the interactive tool one at a time. Followed literally on 2026-09-20 during the peer-listing interview, that produced a question whose three options were one sound answer beside two the record's own principle (loud-staging) already rules out, and the product thinker answered "why these options? Isn't 1 the obvious choice here?". The rule as written confuses two things: not steering the human (right) and manufacturing alternatives so a question has three of them (wrong). Ruled by the product thinker on 2026-09-20: the rule should not offer nonsense options; if there are no sensible alternatives, do not show them; if there is a single genuine answer, record it with the reason and do not put the question to the product thinker or the technical facilitator at all. Wanted: the GRILL domain reworded to say that a question is asked only where two or more answers are each defensible on the record, that the option set is exactly those answers plus the null answer, and that a decision with one defensible answer is recorded as a decision line naming why no question was asked; the itd-201 draft (every question asked one at a time, in plain language) is where the surface rule lives and should carry the same change.
- 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-2609211105021406What abcd implement would have to carry to launch the pilot run without its prompt, from having run it: (1) the run file's part 1 as data: the design decisions, one per lane, that the run is not allowed to reopen, and a stop that names the decision it lacks; (2) the lane table: record ids, the files each touches, the sibling lanes touching the same files, the base sha, and the worktree path under the machine-scoped store; (3) the roles and models per role, with a fresh agent per fix round and a reviewer that never implemented; (4) the pace: window length, the pause as idle wake-ups, the agent ceiling with reviewers counted, and the tally fields written per session (lanes opened, finished, PRs, ceiling-wait minutes, agent-minutes, tokens); (5) the gate ladder per lane: preflight and fmt-check under the HOME alias where the username collides, the issue-resolution gate on the PR form, the push, the PR body re-read and lint outbound, auto-merge with the queue's method, and the rule that an armed PR is never pushed to; (6) the pre-authorised remedies, today the window recalibration, as named recipes the lane may run once; (7) the stop conditions as a list the orchestrator checks after every report; (8) the handover shape, NEXT.md's pilot section, so a crashed session is resumed from it (this run's orchestrator died once and was resumed from NEXT.md alone, the scratchpad and the run file both lost). Evidence from the sibling pilot on the same day, Dessau pilot 2 (its account is at that repository's for_abcd/pilot-2026-09-21-2-experiment.md): the outer loop ran as a script end to end in 82 minutes with zero stops for the work; every step that needed a hand was record-writing (Departures at close, captures for audit concerns, reviewers missing from the session ledger), and the pause never fired because the run fit one window, so itd-2609201925079472's pacing is untested past the first window. Implementer context peaked at 170k there against 349k in the first pilot.
- 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-2610100602595553A rate limit gets no wait once the pause is zero: pacing's rate-limit checkpoint (itd-2609201925079472) ends the run's window early and sets next_eligible_at to now plus the run's pause, and with the bundled pause now 0 (iss-2610100546338578) the next step hands the same work out again at once, so a rate-limited run can hit the same limit again and again. The product thinker ruled on 2026-10-10 that a rate limit waits on its own, independent of the pause, as work after pacing and the drain (itd-82).
- iss-2610251558434724docs/reference/terminology.md: three crosswalk positions no longer fit their own label definitions once the stale facts are corrected, and need a ruling rather than an edit. Durable execution stays WATCHING although the pacing intent (itd-2609201925079472, shipped) checkpoints lanes to their branches and abcd build resumes a run from its state file (internal/core/implement/loop/ratelimit.go:3-20, loop.go:825; internal/surface/cli/build.go:103). Agent2Agent stays WATCHING although its tracking record itd-33 is superseded by itd-2609091034175565, so no live entry tracks it as the label requires (terminology.md:13-14). Orchestration says the orchestration-substrate role is declined while abcd's own implement loop sequences a run's agents and gates each stage on its receipt (internal/core/implement/loop/budget.go:11-13).
- itd-2609150819440345Which session holds which worktree, branch or record is coordinated entirely by conversation, so every new session repeats a handshake that nothing records. A session joining work in progress has no way to ask what is already claimed: it messages the peers it can see, waits for replies, and rebuilds a picture that the sessions before it had already built and did not write down. One measured encounter cost four messages and about fifteen minutes before any work began, and the picture it produced is not durable, so the session after that pays again. The convention that a diff you did not make is a peer's work depends on knowing who the peers are and what they hold, which is precisely the thing no artefact carries. The repository already records this gap for the narrow case of detecting a peer session before mutating git state; the wider case is claim rather than presence, and the two want the same substrate. Whatever holds it should be as cheap to write as it is to read, because a coordination record nobody updates is worse than the chat it replaced.
- itd-2609170822093401abcd proposes the model each agent deserves, and facilitates it where it can
- spc-2609211924346308loop-toward-acceptance