Development record
`abcd build next` picks the readiest planned intent itself, and writes down why
Press Release
abcd build nextchooses the next intent to build, says why on the record, and builds it. It reads every planned intent that passes every refusal checkabcd build <itd-N>would run (READY, no open question, no unanswered claim, not held, not blocked, no peer holding it), scores each on facts its own record holds — how clear its acceptance criteria are, whether there is an obvious way to test it, how small the change it asks for looks — takes the readiest, oldest among equals, and writes onto that intent a grounds entry marked as the run's: who else was considered, why each lost, and what would show the pick wrong. The entry is the lane's first commit, so the reason reachesmainwith the work and stays on the branch as history if the lane is discarded. Then it hands the intent to the same machineryabcd build <itd-N>uses and takes it to delivered. One intent per run, because "next" means one;--until-emptyor--max <n>keeps it going under the pace rule, which is howabcd drainreads by default, because "drain" means all."I used to open the run file every morning and pick by feel, then forget by Friday why Tuesday's pick was that one," said a product thinker who had planned forty-eight intents and could build one a day. "Now the run picks, the intent says why in its own record, and when a pick turns out wrong the reason is there to argue with."
Why This Matters
Forty-eight planned intents were READY in this repository on 2026-09-20, and the file that routes them into batches was written by hand, ordered by a person's sense of what comes first, with no line saying why. Every autonomous coding platform surveyed on 2026-09-21 (Copilot's coding agent, OpenHands, Jules, Devin, Cursor's cloud agents, Claude Code and Codex routines) is triggered by a human assigning a task or a label; none publishes a policy for choosing among a backlog, so a run that is meant to be unattended still begins with a human's choice. Two things the same survey found do have evidence: readiness is predictable from the record alone (static features of the task predict an agent's success at 0.76 to 0.84 AUC before any code is written), and small, tidy work merges far more often than large features (84.7 per cent for clean-ups against 64.5 per cent for features across 878 agent pull requests in one large repository). A pick made from those facts, written where a person can later check it, is what turns the run file's hand-ordered batches into something the binary does.
The reason has to be checkable. Agent-written rationales, when audited, were factually wrong more than half the time in one repository's decision log; the survey found no evidence that recording a reason improves later review, only that an unrecorded one cannot be reviewed at all. So the record the run writes is computed, not composed: the candidate set, each candidate's score and the rule that placed it, and a falsifier the lane's own outcome can meet.
Mechanism
We expect a pick made from the record's own facts, with its reason written as computed comparison and a falsifier, to choose intents that deliver and to be correctable when one does not, because the facts that predict an agent's success are in the acceptance criteria, the test path and the footprint before a lane starts, and a written falsifier is what a later reader checks the outcome against; shown wrong if the falsifier on a failed pick did not name what actually failed, or if picks scored readiest end unachievable (itd-50's verdict) more often than not over ten or more picks.
Scope Conditions
- Holds for a repository abcd manages whose planned intents carry acceptance criteria in Given-When-Then form and a linked spec, so the readiness score has fields to read; an intent with a placeholder criteria section is not a candidate.
- Holds where the intent store's grounds section accepts an appended entry the run writes beside a human's; the human's entry is never edited or displaced.
- Holds where
blocked_byandbuilds_onedges are declared on the records; an intent with an unshipped blocker is not a candidate, and the edges are not otherwise ranked on.
What's In Scope
- The candidate set: every intent in
planned/that passes every refusal checkabcd build <itd-N>runs before it starts (READY, no open question, no unanswered claim section, not held, no peer holding it, nothing unshipped inblocked_by, a superseded blocker read as the intent that replaced it, per decision 9); the pick never writes onto an intent the build would then refuse. - The score, from the record alone, each component named in the reason: criteria clarity (count and shape of the Given-When-Then bullets), a test path (the spec's
## Footprintsection naming the tests, absent today on every spec), and the expected footprint (that section's package list); three parts at equal weight, the bundled default, revisable after ten picks; age is not a part of the score and breaks ties only. Until a spec carries the section, its test-path and footprint parts read zero and the reason says so. - The reason, written onto the chosen intent's
## Groundssection as one entry marked as the run's (pursued:prefixed with the run's identity and date), carrying the candidates considered with their scores, the rule that placed the winner, the runner-up and why it lost, and the falsifier: a lane outcome the state file can meet (fix rounds past the pace rule's count, or the lane handing back as unachievable under itd-50's stage). The marker ("picked by run <id> on <date>") goes inside the entry's text after thepursued:token, since the token vocabulary is closed. The entry is committed as the lane's first commit on the lane branch. - The hand-over: the chosen intent goes to the implement machinery as
abcd build <itd-N>would send it, with one difference: after the loop's branch step and before the implementer starts, the pick step commits the grounds entry as a record-only commit at the branch base, and the loop's receipt verifier counts the implementer's commits from after it. - The gate:
abcd intent ready's grounds row skips a run-marked entry when it names the most recent conjecture, so the human's entry stays the one it reports. - The run record: names each pick, its score table, and whether its falsifier was met.
- The run count: one by default;
--max <n>and--until-emptycontinue under the pace rule, each further pick written the same way. - Refusals: no candidate (says so, names each excluded intent and the check that excluded it, writes nothing); a tie the score cannot break beyond age (takes the oldest and says the tie was broken by age).
- The spec template: gains a
## Footprintsection (packages, tests) the score reads;intent planseeds it empty. - The command page:
commands/intent.md(or the build page once it exists) says whatnextdoes, its two flags and its refusals.
What's Out of Scope
- The build itself, the lane, the validators, the landing:
itd-2609201916151817. - The pace, the ceiling and the pause:
itd-2609201925079472. - A derived priority over the dependency graph:
itd-78; this intent filters on edges and does not rank on them. - The issue ledger:
abcd drain(itd-82) is the run over issues, and it hands over to the same machinery. - Any estimate the agent makes of its own confidence as an input to the pick.
Decisions
Ruled by the product thinker on 2026-09-21, in the interview that filed this draft:
- The verb is
build, for people;implementis the machinery, for agents.abcd build <itd-N>andabcd build nextare what a person types; both hand over toabcd implement, whose step-level words (step,receipt) a driving host calls. The planned implement intent is amended to say so; which verbs the command list shows a person and which it shows an agent is a separate record (iss-2609211119023345). - Readiest first, oldest among equals. Not oldest-first, not most-important-first; the evidence for readiness as the predictor is the reason, and the order is declared a heuristic.
- The reason lives on the intent itself, as a grounds entry marked as the run's, so opening the intent shows why it was picked and what would prove the pick wrong.
- One by default, many by flag, and the defaults differ from
drainon purpose: "next" reads one and "drain" reads all;--max <n>and--until-emptyare the two flags both share. - The reason is computed, never composed. Candidates, scores, placing rule, falsifier; no prose the agent writes about its own choice.
- The reason is the lane's first commit on the lane branch (ruled 2026-09-21 on the design review's finding): a record-only commit the pick step makes at the branch base before the implementer starts, so it reaches
mainwith the work and stays on the branch if the lane is discarded; the receipt verifier counts the implementer's commits from after it. - The ordering is declared a heuristic with the evidence in Why This Matters; it is revised on the record, never silently.
- Equal weights at first, declared as the bundled default and revisable after ten picks; age breaks ties and is not scored.
- A superseded blocker waits on its replacement (amended 2026-09-29 by ruling BZ2 of 2026-09-29, decision dcn-2609290000000007 landing with lane recRulings; the ruling supersedes the reading this record carried until then, that every blocker outside
shipped/, a superseded one included, blocks). The blocked check follows a blocker'ssuperseded_byto the intent that replaced it, transitively, and the intent is a candidate only when the last intent of that chain has shipped. A chain that loops, ends at a record this checkout does not hold or at a decision (adr-N), or stops at a superseded record naming no successor blocks, and the refusal names the chain. Amendment, 2026-09-30: the product thinker's rulings CF1 and CF2 of 2026-09-30 (dcn-2609300000000009, the entry recording the eighteen rulings of that day) settle two of those ends, as lane cfSettled built them infollowBlocker(internal/core/intent/startcheck.go): a chain ending at a decision (adr-N) is settled when that ADR's status isaccepted(CF1), and refuses naming the decision when it carries another status or no status, or when this checkout's decision store does not hold it; and a blocker, or the last intent of its chain, that sits indisciplines/is settled as a standing rule (CF2), as a shipped one is. A chain that loops, ends at an intent this checkout does not hold, or stops at a superseded record naming no successor still blocks, and a settled chain names the record that settled it.
Open Questions
- Whether a run-made grounds entry needs its own token. RESOLVED: no. The product thinker ruled on 2026-09-29 (ruling BZ1 of autonomous run A's owed list, recorded in dcn-2609290000000007) that a run-made entry keeps the shared
pursued:kind with the run's marker opening its text, as built; a token of its own for a machine's conjecture was the alternative not taken.
Acceptance Criteria
- Given planned intents of which some pass every refusal check of
abcd build <itd-N>and some fail one (not READY, an open question, an unanswered claim, held, blocked, a peer holding it), whenabcd build nextruns, then only those that pass are candidates, and the refusal for an empty candidate set names each excluded intent and the check that excluded it, writing nothing. - Given two or more candidates, when the pick is made, then the chosen intent's
## Groundssection gains exactly onepursued:entry whose text opens with the run's marker, naming every candidate with its score, the rule that placed the winner, the runner-up and why it lost, and a falsifier the state file can meet; no existing entry is changed; the entry is the lane branch's first commit; andabcd intent readystill reports the human's entry as the most recent conjecture. - Given candidates whose scores tie, when the pick is made, then the oldest is taken and the entry says the tie was broken by age.
- Given a pick, when the lane starts, then it is the lane
abcd build <itd-N>would start for that intent, with the same brief, worktree, validators and landing, and one record-only commit at the branch base carrying the grounds entry, which the receipt verifier does not count as the implementer's work. - Given the default run count, when the picked intent is delivered or its lane stops, then the run reports and exits without picking again; given
--max <n>or--until-empty, then it picks again under the pace rule and writes each further reason the same way. - Given a pick, when the lane's state file shows fix rounds past the pace rule's count or an unachievable hand-back, then the run record names the pick as falsified and the intent's entry is not edited.
- Given
abcd intent planmints a spec, when the spec stub is read, then it carries an empty## Footprintsection (packages, tests) for the author to fill. - Given a spec without a
## Footprintsection, when the score is computed, then its test-path and footprint parts read zero and the reason says the spec carries no footprint. - Given the command page, when it is read, then it states what
nextdoes,--maxand--until-empty, and every refusal. - Given
--json, when any of the above renders, then the candidate set, scores, the reason and every refusal are in the payload.
Prior Art
- The run file of 2026-09-20 (
abcd-autonomous-run.md), whose hand-ordered batches this pick replaces. itd-78(the dependency graph): the derived priority this intent filters on and does not build.- The state-of-the-art pass of 2026-09-21 (in the record-families of the platforms surveyed and the psychometrics and dotnet/runtime evidence cited above).
Typed Links
- builds_on
itd-2609201916151817(the implement machinery,abcd build <itd-N>): the pick hands over to it and adds nothing to the lane. - builds_on
itd-2609201925079472(the pace):--until-emptyand--maxrun under it. - refines
itd-78(the dependency graph): this intent filters onblocked_byand does not derive a priority; that draft is where a derived one would come from. - related
itd-82(abcd drain): the sibling run over the issue ledger; the two share--maxand--until-emptyand the hand-over toabcd implement, and differ in their default count by the meaning of their names. - builds_on
itd-50(the audit-driven fix round): the unachievable verdict is one of the pick's falsifiers.
Audit Notes
Fidelity review — receipt rcp-c99b85cd869d (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:9307d185086739eeb259935a6253f4140f9ba45e9d3fa9cc5a3e7e9d6710d123 Input attestations: diff:a09dd42b6de9a6556d5ddc49b8810f65b5064b61..1868cf1e3c1c4410ca55e3063268c5dd12c26bfc@sha256:c97dda675ecbfa3d7930648aab344083d9830ce13c2997c7b459116930a3c8db; log:.abcd/.work.local/run/run-2610110908001704/lane-1/validate/round-1/fix/dod.log (implementer's make preflight at 1868cf1e3, exit 0, receipt minted)@-; test:auditor's own run on a git-archive copy of 1868cf1e3: go test ./internal/core/intent/ -run 'Pick|Readiness|Choose'; ./internal/core/spec/ -run Footprint; ./internal/core/implement/loop/ -run 'Next|Series|Pick|Removed|Entry|Record|Falsif'; ./internal/surface/cli/ -run 'BuildNext|ImplementRecord' — all ok@-;
Acceptance rollup: MET 8 · MET_WITH_CONCERNS 2 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: candidates() judges every planned intent with the build's own check() rows and records the first failing check per intent, plus the run check; an empty set is refused at the pick naming each exclusion; nothing is written before the refusal (beginSeries judges the set before EnsureRealDirAll, the single pick before startPick). Loop and CLI tests assert the exclusions by check and the absent run tier; they pass on my copy.
evidence: internal/core/implement/loop/next.go:144-156 — "chk, err := check(repoRoot, id, session, snap) ... set.Excluded = append(set.Excluded, Excluded{ID: id, Check: row.Name, Reason: row.Detail})"
evidence: internal/core/implement/loop/next.go:266-275 — "no planned intent passes every check
abcd build <itd-N>runs; %d excluded: %s" evidence: internal/core/implement/loop/check.go:29-36 — "CheckReady ... CheckOpenQuestions ... CheckClaimSections ... CheckHold ... CheckBlocked ... CheckSteps ... CheckPeers" evidence: internal/core/implement/loop/nextseries.go:152-155 — "if !ok { return NextResult{}, emptyRefusal(set) } if err := fsutil.EnsureRealDirAll(" evidence: internal/core/implement/loop/next_test.go:75-128 — "TestNextCandidatesAreWhatTheBuildWouldStart ... runTierAbsent(t, repo.Root())" evidence: internal/surface/cli/build_next_surface_test.go:78-100 — "TestBuildNextRefusalCarriesEveryExclusion ... runDirAbsent(t, repo.Root())" - ac-2 — MET: PickEntryText opens with the run marker and names every candidate with its score, the rule, the runner-up and why it lost, and the falsifier; the worktree stage appends it through intent.RecordGrounds and commits that one file; isPickCommit proves the commit's parent is the base and its record is byte for byte the base's plus one entry; ready.go skips run-pick entries when naming the most recent conjecture. next_test asserts exactly one appended entry, no changed line, first-and-only commit past the base, and the gate's detail. evidence: internal/core/intent/pick.go:232-252 — "b.WriteString(RunPickMarker + runID + " on " + ... "; candidates: ") ... "; rule: " ... "; runner-up: ... "; falsifier: "" evidence: internal/core/implement/loop/pickcommit.go:147-173 — "intent.RecordGrounds(wt, st.Intent, g) ... pickGit(wt, "commit", "-q", "-m", pickMessage(st), "--", rel)" evidence: internal/core/implement/loop/pickcommit.go:211-236 — "its record is not the base's with this run's one entry appended and nothing else changed" evidence: internal/core/intent/ready.go:349-364 — "if !isRunPick(g) { human = append(human, g) } ... most recent %s: %s ... run pick entry(ies) skipped" evidence: internal/core/implement/loop/next_test.go:181-214 — "the pick's entry is the branch's first and only commit past its base ... exactly one run-marked pursued entry is appended ... the gate still reports the person's entry"
- ac-3 — MET: PickLess orders by total then IDOlder; Choose sets TieBrokenByAge when the runner-up's total equals the chosen's, and the entry text then says the tie was broken by age; tested at the unit (ordinal id before timestamp ids) and in the series test (itd-11 over itd-12). evidence: internal/core/intent/pick.go:166-171 — "if a.Score.Total != b.Score.Total { return a.Score.Total > b.Score.Total } return IDOlder(a.ID, b.ID)" evidence: internal/core/intent/pick.go:212 — "p.TieBrokenByAge = ru.Score.Total == p.Chosen.Score.Total" evidence: internal/core/intent/pick.go:246 — "tied at %d and younger, so the tie was broken by age" evidence: internal/core/intent/pick_test.go:58-72 — "TestChooseTakesTheOldestOfATieAndSaysSo" evidence: internal/core/implement/loop/nextseries_test.go:111-113 — "second.Pick.Chosen.ID != "itd-11" || !second.Pick.TieBrokenByAge"
- ac-4 — MET: startPick calls the same start() that Start (build < itd-N>) wraps, with a RunPick beside it, so the run's stages, brief, worktree, validators and landing are the build's; the worktree stage of the pick's lane makes the record-only commit at the base and records PickSHA; the receipt verifier names a commit inside PickSHA as not the implementer's work and pickKept refuses a branch that dropped it. next_test asserts the same key/spec/lane/stage and the receipt refusal. evidence: internal/core/implement/loop/next.go:253 — "started, err := start(repoRoot, p.Chosen.ID, o, &RunPick{Pick: p, Excluded: set.Excluded})" evidence: internal/core/implement/loop/loop.go:282 — "func Start(repoRoot, key string, o Options) (StartResult, error) { return start(repoRoot, key, o, nil) }" evidence: internal/core/implement/loop/lane.go:228-235 — "if p := c.State.Pick; p != nil && p.Lane == lane.ID { sha, err := pickCommit(c, lane, lw.Path, lw.Branch, base)" evidence: internal/core/implement/loop/receipt.go:384 — "if err := pickKept(c.RepoRoot, lane, receiptRel); err != nil {" evidence: internal/core/implement/loop/receipt.go:517-532 — "case inPick: off = append(off, shortSHA(sha)+" (the pick's record-only commit, not the implementer's work)")" evidence: internal/core/implement/loop/next_test.go:169-172 — "the pick starts the build's own run" evidence: internal/core/implement/loop/next_test.go:226-234 — "the pick's commit is not the implementer's ... the implementer's own commit verifies"
- ac-5 — MET_WITH_CONCERNS: The default and --max 1 pick once and write no series (TestADefaultPickLeavesNoSeries); --max above 1 or --until-empty begins a series whose move reads the last pick's run (delivered, handed-back, removed, in-progress), honours the window clock and next_eligible_at under a fixed clock, excludes already-picked intents, and starts each further pick through the same startPick, so each reason is written the same way; the removed outcome and the lock contention now have tests, and every test passes on my copy. Concerns, named: (1) 'picks again' is one move per further
abcd build nextinvocation, host-driven as the loop is, not the run continuing on its own; (2) the pace rule's window and pause are enforced across the series, but its ceiling and fix rounds are forwarded to each run as the series' typed flags and are not applied across the series. evidence: internal/core/implement/loop/nextseries.go:130 — "func (n NextOptions) many() bool { return n.UntilEmpty || n.Max > 1 }" evidence: internal/core/implement/loop/nextseries.go:9-10 — "The loop is driven by the host session, so a series is too: eachabcd build nextmakes one move and exits." evidence: internal/core/implement/loop/nextseries.go:231-246 — "outcome, falsified, err := seriesOutcome(repoRoot, pk.RunID) ... if outcome == SeriesPickInProgress { ... return finish(changed) }" evidence: internal/core/implement/loop/nextseries.go:255-280 — "The window clock, as a run keeps it: before next_eligible_at nothing is picked" evidence: internal/core/implement/loop/nextseries.go:283-284 — "so.Pace, so.SubAgents, so.FixRounds = st.Flags.Pace, st.Flags.SubAgents, st.Flags.FixRounds" evidence: internal/core/implement/loop/nextseries.go:304-308 — "picked, err := startPick(repoRoot, so, set, p) ... st.Picks = append(st.Picks, SeriesPick{" evidence: internal/core/implement/loop/nextseries_test.go:67-79 — "TestADefaultPickLeavesNoSeries" evidence: internal/core/implement/loop/nextseries_test.go:86-155 — "TestMaxPicksAgainOnceThePickIsDelivered ... the further pick's reason is written the same way" evidence: internal/core/implement/loop/nextseries_test.go:202-244 — "TestASeriesPicksOnlyInsideItsWindow" evidence: internal/core/implement/loop/nextseries_test.go:324-392 — "TestARemovedPickIsPickedPast ... TestASeriesMoveBacksOffWhileAnotherHoldsItsLock" - ac-6 — MET: The fix-round bound hands a lane back with Verdict unachievable and no Kind once the fix rounds taken reach the cap, so the state file shows both halves of the falsifier as one event; pickFalsifiedBy matches exactly that hand-back, handBackLane appends a
pickrecord line naming the pick falsified, recordPick sets falsified/falsified_by, andimplement recordrenders it. The new loop test drives a picked run through the real validate path with --fix-rounds 1 to a hand-back and asserts the intent on the lane branch and in its worktree is byte for byte the pick commit's, carrying the one entry; it passes on my copy. evidence: internal/core/implement/loop/validate.go:184-185 — "if taken, limit := lane.fixRoundsTaken(), c.State.FixRoundCap(); taken >= limit { hb := HandBack{Verdict: VerdictUnachievable, Round: cur.Round, FixRounds: limit" evidence: internal/core/implement/loop/record.go:93-96 — "p.Falsified = true; p.FalsifiedBy = fmt.Sprintf("%s was handed back as %s after %s"" evidence: internal/core/implement/loop/record.go:103-110 — "if l.HandBack != nil && l.HandBack.Kind == "" { return l.ID, *l.HandBack, true }" evidence: internal/core/implement/loop/handback.go:31-35 — "the pick of %s is falsified: %s was handed back as %s after %s; the intent's grounds entry is left as it was written" evidence: internal/core/implement/loop/nextseries_test.go:432-486 — "TestAPickedRunHandedBackAfterItsFixRoundsLeavesTheEntryAlone ... the intent on the lane's branch is the pick commit's, unedited" evidence: internal/surface/cli/build_next_surface_test.go:190-228 — "TestImplementRecordCarriesThePick" - ac-7 — MET: intent.Plan calls spec.Create, which writes renderSpecRecord; that renderer iterates stubSections, whose entries carry
## Footprintwith the empty body- packages:\n- tests:above## Steps; footprint_test asserts the minted stub carries it present and empty, and passes on my copy. evidence: internal/core/intent/lifecycle.go:291 — "sp, err = spec.Create(repoRoot, intentID, it.Slug, opts.ProductionMode)" evidence: internal/core/spec/store.go:222 — "[]byte(renderSpecRecord(id, slug, intentID, intents, bundle, stamp, steps))" evidence: internal/core/spec/spec.go:345 — "for _, sec := range stubSections(id, intentID, steps) {" evidence: internal/core/spec/spec.go:308-312 — "{heading: FootprintHeading, body: footprintStubBody}" evidence: internal/core/spec/footprint.go:35 — "const footprintStubBody = "- packages:\n- tests:"" evidence: internal/core/spec/footprint_test.go:13-32 — "TestRenderSpecSeedsAnEmptyFootprintSection" - ac-8 — MET: readinessWith sets NoFootprint and zero-point test-path and footprint parts whose detail reads 'the spec carries no footprint' when ReadFootprint finds no section, and candidateText appends 'its spec carries no footprint' to the reason; tested at the unit and passing on my copy. evidence: internal/core/intent/pick.go:104-109 — "case !fp.Present: s.NoFootprint = true; s.TestPath = ReadinessPart{Detail: "the spec carries no footprint"}" evidence: internal/core/intent/pick.go:259-261 — "if s.NoFootprint { t += ", its spec carries no footprint" }" evidence: internal/core/intent/pick_test.go:41-54 — "TestReadinessWithoutFootprintReadsZeroAndSaysSo"
- ac-9 — MET_WITH_CONCERNS: commands/build.md's 'Pick the next intent' section states what next does, the score, the entry and the commit; 'One pick, or a series' states --max and --until-empty and the series' moves; the refusals list now names the empty set, a negative --max, two counts, another count or pace on a live series, the three series contentions (exit 3), every next.json refusal readSeries raises, the grounds-gate refusal, a run already in progress, and the build's own refusals, matching the code. Concern, named: of the pick commit's worktree-stage refusals in pickcommit.go (the intent not planned in the lane's worktree, a branch whose first commit is not the pick's, a worktree dirty with other changes, no git identity) the page states only the git-identity one.
evidence: commands/build.md:97-143 — "## Pick the next intent ... The pick starts the run
build <itd-N>would start for that intent" evidence: commands/build.md:145-174 — "### One pick, or a series ...--max <n>(above 1) or--until-emptybegins a series" evidence: commands/build.md:176-204 — "Refusals, each writing nothing: ....abcd/.work.local/run/next.jsonthat cannot be read as the series' state ... the pick's reason refused by the grounds writer's own gate" evidence: commands/build.md:206-207 — "The pick commit is made with the git identity the repository's commits are made with; with none configured theworktreestage is refused naming it." evidence: internal/core/implement/loop/nextseries.go:383-409 — "cannot be read as the series' state ... does not parse as the series' state ... names no run count a series takes ... names a pick with an outcome the series never writes" evidence: internal/core/implement/loop/pickcommit.go:120-167 — "is not planned in the lane's worktree ... is not the pick's record commit ... holds changes the pick did not make" - ac-10 — MET: NextResult serialises candidates (each with its score), excluded, pick, entry, start, series and next; the empty-set refusal carries excluded in the refusal document; a count refusal and the lock contention render as refusal documents with stage, reason and contention; the run record's pick is in
implement record --json. The CLI tests decode each and pass on my copy. evidence: internal/core/implement/loop/next.go:81-88 — "type NextResult struct { CandidateSet; Pick *intent.Pickjson:"pick"; Entry stringjson:"entry"; Start ...; Series ...; Next ..." evidence: internal/core/implement/loop/next.go:237-242 — "ref.Excluded = set.Excluded" evidence: internal/surface/cli/build_next_surface_test.go:21-72 — "TestBuildNextPicksAndCarriesTheReasonInJSON" evidence: internal/surface/cli/build_next_surface_test.go:78-100 — "TestBuildNextRefusalCarriesEveryExclusion" evidence: internal/surface/cli/build_next_surface_test.go:116-174 — "TestBuildNextMaxCarriesTheSeriesInJSON" evidence: internal/surface/cli/build_next_surface_test.go:252-273 — "TestBuildNextBacksOffWhileTheSeriesIsLocked ... ref["contention"] != true"
Gap audit:
- honoured:
- the candidate set is the build's own pre-start checks over planned/, and an empty set is refused naming each exclusion with nothing written evidence: internal/core/implement/loop/next.go:93-165 — "func candidates(repoRoot, session string) (CandidateSet, error)"
- the reason is computed (candidates, scores, rule, runner-up, falsifier), written as one run-marked pursued entry and committed as the lane branch's first, record-only commit evidence: internal/core/implement/loop/pickcommit.go:110-182 — "func pickCommit(c Context, lane *Lane, wt, branch, base string) (string, error)"
- readiest first, oldest among equals, with the tie named evidence: internal/core/intent/pick.go:164-171 — "PickLess is the pick order, the one statement of it"
- the pick hands over to the same machinery as build < itd-N>, and the receipt verifier does not count the pick commit as the implementer's work evidence: internal/core/implement/loop/loop.go:282 — "func Start(repoRoot, key string, o Options) (StartResult, error) { return start(repoRoot, key, o, nil) }" evidence: internal/core/implement/loop/receipt.go:465-486 — "pickKept refuses a receipt over a lane branch that no longer carries the pick's record-only commit"
- intent ready keeps reporting the person's entry as the most recent conjecture evidence: internal/core/intent/ready.go:346-364 — "the most recent conjecture the row names is a person's, so the pick never displaces it"
- intent plan seeds an empty ## Footprint; an absent footprint reads zero and says so evidence: internal/core/spec/spec.go:310 — "{heading: FootprintHeading, body: footprintStubBody}" evidence: internal/core/intent/pick.go:106-109 — "case !fp.Present:"
- the run record names each pick's score table and whether its falsifier was met, and the falsified pick's entry is not edited evidence: internal/core/implement/loop/record.go:82-98 — "func recordPick(st State) *RecordPick" evidence: internal/core/implement/loop/nextseries_test.go:473-482 — "the intent on the lane's branch is the pick commit's, unedited"
- --max < n> and --until-empty keep picking under the pace rule's window, each further reason written the same way, until the cap or an empty set evidence: internal/core/implement/loop/nextseries.go:187-316 — "continueSeries is one move of the series in progress, under its lock: route the last pick, stop at the cap, honour the window clock, then pick again or end with nothing left."
- the command page states what next does, its two flags and its refusals; --json carries the set, scores, reason and refusals evidence: commands/build.md:97-207 — "## Pick the next intent" evidence: internal/surface/cli/build_next_surface_test.go:21-273 — "the payload carries the candidate set with each score, the pick with its rule and runner-up, the reason's text"
- diverged:
--until-emptyor--max <n>keeps it going under the pace rule: delivered as a host-driven series, one move perabcd build nextinvocation that the host must call again, rather than the run continuing on its own evidence: internal/core/implement/loop/nextseries.go:9-12 — "The loop is driven by the host session, so a series is too: eachabcd build nextmakes one move and exits." evidence: commands/build.md:172-174 — "drive the run it starts to delivery or hand-back, then runbuild nextagain, and repeat untilseries.completeis true"- the pace rule across the series: the window and pause are enforced over the series, but the ceiling and fix rounds are forwarded to each run as the series' typed flags, not applied across the series evidence: internal/core/implement/loop/nextseries.go:283-284 — "so.Pace, so.SubAgents, so.FixRounds = st.Flags.Pace, st.Flags.SubAgents, st.Flags.FixRounds"
- missing:
- the command page states the pick commit's worktree-stage refusals: of the four pickcommit.go raises (not planned in the lane's worktree, a foreign first commit, a dirty worktree, no git identity) only the git-identity one is on the page
evidence: internal/core/implement/loop/pickcommit.go:120-167 — "is not planned in the lane's worktree ... is not the pick's record commit ... holds changes the pick did not make"
evidence: commands/build.md:206-207 — "with none configured the
worktreestage is refused naming it"
- the command page states the pick commit's worktree-stage refusals: of the four pickcommit.go raises (not planned in the lane's worktree, a foreign first commit, a dirty worktree, no git identity) only the git-identity one is on the page
evidence: internal/core/implement/loop/pickcommit.go:120-167 — "is not planned in the lane's worktree ... is not the pick's record commit ... holds changes the pick did not make"
evidence: commands/build.md:206-207 — "with none configured the
Scope-condition dispositions:
- cond-2609212015041824 — survived: a candidate must pass the build's ready check, whose acceptance_criteria row fails on a section with no top-level bullets and whose spec_link row fails without a linked spec (next_test excludes itd-14 with spec_id null at
ready), and the score reads the Given-When-Then bullets and the spec's footprint, so the score has fields to read on every candidate evidence: internal/core/intent/ready.go:185-194 — "no top-level bullets in '## Acceptance Criteria' (itd-1 discipline)" evidence: internal/core/intent/ready.go:449 — "c := ReadyCheck{Name: CheckSpecLink}" evidence: internal/core/intent/pick.go:88-96 — "mdrecord.SectionLineRangeIn(lines, mask, acHeadingRe) ... givenRe.MatchString(text) && whenRe.MatchString(text) && thenRe.MatchString(text)" evidence: internal/core/implement/loop/next_test.go:102-103 — ""itd-14": CheckReady" - cond-2609212015048560 — survived: the entry is appended through the intent store's grounds writer, the commit is verified as the base's record plus exactly one entry, the hand-back path leaves the record byte for byte as the pick commit wrote it, and the ready gate keeps reporting the person's entry as the most recent conjecture evidence: internal/core/implement/loop/pickcommit.go:184-205 — "the record at the lane's base with this run's entry appended, built as intent.RecordGrounds builds it" evidence: internal/core/intent/ready.go:346-348 — "it is counted, but the most recent conjecture the row names is a person's, so the pick never displaces it" evidence: internal/core/implement/loop/next_test.go:195-202 — "no existing line of the record changes ... exactly one run-marked pursued entry is appended" evidence: internal/core/implement/loop/nextseries_test.go:473-482 — "the intent on the lane's branch is the pick commit's, unedited"
- cond-2609212015045488 — narrowed: blocked_by filters and nothing ranks on either edge (PickLess reads score and id age only; builds_on is not read by the pick), but under decision 9's amendments a blocker excludes while its supersession chain is unsettled rather than while it is unshipped: a blocker, or the last record of its chain, that has shipped, sits in disciplines/, or is an accepted ADR releases the edge
narrowing: holds for blockers whose supersession chain ends at a record that is neither shipped, in disciplines/, nor an accepted ADR; an unshipped blocker settled by one of those does not exclude the intent
evidence: internal/core/intent/startcheck.go:150-166 — "An intent is settled when it has shipped or sits in disciplines/ ... A chain ending at a decision (adr-N) is settled when that ADR's status is
accepted" evidence: internal/core/intent/startcheck.go:189-194 — "names no unsettled blocker ... a superseded blocker is settled by the record that replaced it" evidence: internal/core/intent/pick.go:166-171 — "if a.Score.Total != b.Score.Total { return a.Score.Total > b.Score.Total } return IDOlder(a.ID, b.ID)"
Grounds
- pursued: the autonomous run's routing table is a person's hand-written file today; we expect the pick to be what replaces it after the run; shown wrong if the next run still starts from a hand-written table
Frontmatter
| id | itd-2609211116005482 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-2609211116005482-abcd-build-next-picks-the-readiest.md
open on GitHub ↗ · commit history ↗
References4
- itd-50The Audit Loop Drives An Intent To Acceptance — Or Calls For A Replanbuilds on
- itd-78abcd Knows What to Build First — Even When It's Something Smallrelated
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is openbuilds on
- itd-2609201925079472An autonomous run paces itself by default, and one flag sets the pacebuilds on
Referenced by9
- iss-2610262032106392The personas page says customer quotes use named placeholder personas from the registry; itd-2609211116005482 attributes its quote to an unnamed product thinker.related
- iss-2610262037582823The build-next intent says --max and --until-empty are flags that next and drain share; the drain chapter's run ends at its cap and its shipped surface has no --until-empty.related
- iss-2610262043273885itd-2610070550516046 requires build-next to be reconciled with it before either is built, but itd-2609211116005482 has already shippedrelated
- iss-2610262044160347The dependency graph says severity inheritance decides what to build first, but build next picks readiest first and explicitly not most-important-firstrelated
- iss-2610262044240923The manual-hold draft relies on the selector skipping specs with a non-done dependency, but the shipped pick selects planned intents by their blocked_by edgesrelated
- itd-82`abcd drain` fixes the issues that need no decision, and hands the rest back by kindrelated
- itd-2609212103565953A spec lists its steps, and the build lands them one at a timebuilt on by
- itd-2609212103568351The status board shows Now, Next and Later, computed from the recordbuilt on by
- spc-2609212015048113abcd-build-next-picks-the-readiest-planned-intent-itself-wriimplemented by
mentions21
- dcn-26092500000000282026-09-25 — The build's open-question check reads the record's own settled convention and stays fail-closed (lane implementer, autonomous run A, fix round 1 of the loop lane, on iss-2609252053576085; the lane's first ruling, that every list item under `## Open Questions` is a question whatever it says, lived only in its report and refused intents the record had settled). `intent.OpenQuestions` recognises exactly two markers, learned from the 12 planned intents that carry list items there. A section whose first non-blank line is an italic `_All resolved …_`, with at most one count word (`_All four resolved …_`), is settled whole (itd-111, itd-93); `_All but one resolved_` is not. An item explicitly marked resolved or deferred is not a question: a bold span opening with the word (`**Resolved — …**`, `**Deferred**`, `**Deferred to a follow-up intent**`, `**explicitly deferred**`, `**explicit deferral**`) or the word as a label (`resolved:`, `RESOLVED:`, `Deferred:`), anywhere in the item including its continuation lines. Everything else stays a question: an item led `**Open` whatever it goes on to say (itd-60's "Open, and not gating scope"), an item that only points to another record (itd-76's "travel with itd-126"), `**Out of scope …**`, and a question that merely mentions deferral. With the rule, itd-111 and itd-93 pass the check; itd-7, itd-60, itd-65, itd-66, itd-76, itd-82, itd-117, itd-131, itd-2609170822093401 and itd-2609211116005482 still refuse, each on an item the record does not mark settled. Relabelling those items is the product thinker's call through the planning interview, not the loop's.
- dcn-26092900000000072026-09-29 — The product thinker answered twenty-six owed rulings and the published-history question in one interview, asked one at a time by orchestrator abcd-23 of autonomous run A (recorded by lane recRulings for orchestrator abcd-cb). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, dated 2026-09-29, and are restated here because that file is not committed. Published history is left as it is, with no rewrite. The build loop's fidelity audit runs once, on the lane that closes the spec, over the whole delivery (AI). On the vendor denylist the product thinker answered, verbatim: "I think we need an allow list rather than trying to catch a deny list." The interviewer's reading of that answer, recorded as a reading and not as the product thinker's words, is that the per-provider allowlist is the control and the vendor denylist is not extended to chase spellings, with whether the bundled `anthropic/*` entry stays as a backstop left to the technical facilitator (AA(a)). Only a route the person set up on their own machine may use their paid key, and a repository's own route to it is refused, which replaces the route half of itd-2609081951381895 decision 8 (AA(b)). The live site setup, the API adapter and the decision adapter may each close on tests against a simulated service, with the live paid check recorded as owed to a person (iss-2609291925387011, iss-2609291925393913, iss-2609291925398978). itd-111's last criterion is met by an agent's timing check on macOS plus the Linux CI run, so the intent may be finished. Automated dependency updates are switched on, pushed with a personal or app token the person creates and stores as a repository secret (itd-2609221842494980; creating the token is the person's act, and the lane builds everything else and names the secret it expects). itd-6 adds a Go MCP module, proposed for the person's explicit sign-off before it is fetched; closing itd-6 on a fake server was not ruled in this interview. (A later answer the same day, H1b in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, dated 2026-09-29, rules that it is not: itd-6 is finished only after running once against the real RepoPrompt app, a check owed to a person.) For itd-148: the store under the home folder wins (adr-2609091248200336); the store draft itd-2609091014076309 owns add, list and clean-up and itd-148 uses them; a session that declares itself the coordinator is exempt from the write block in the primary checkout and every other session is blocked; and the store draft is planned first, with nothing from the store draft, the merged-worktree clean-up included, built before its planning interview (Q4). itd-148's own wait is not part of that ruling: it follows from Q2, because itd-148 uses the store's verbs, and is carried by the `blocked_by` below. itd-148 and spc-42 are rewritten to match in this change, and itd-148 is now `blocked_by` the store draft. A release retrospective is seeded from a release, the intents a tag shipped (AD); itd-24 and its spec are rewritten to the release in this change. An unpointed route is refused naming the one setting to add, as built in 35100512e, so AC11 of itd-2609170822093401 is complete (BR1). A missed target becomes `next`, whatever the following release is numbered (BS1). Spec pieces keep "step" and the lane stages are renamed, a breaking change to the JSON fields `step`, `performed` and `refusal.step` that resolves iss-2609291313276243 and corrects the glossary entry `core/step.md` (BU1), and the old command spellings are removed in that same breaking release (BT1). The text board shows Later as a count only while the site and `--json` keep the full list (BV1), and an intent in progress appears only under Now (BV2). A pause of zero is allowed (BW1); drain stays in the agents' help list until it runs end to end (BX1); a managed repository must hold the drain eligibility decision in its own record, and drain refuses there until it does (BX2); a capture without a remedy is refused (BX3), which tightens the one-line capture friction `commands/capture.md` promises, so the lane that builds it reconciles that page, the principle wording and every caller that files a capture; a run's pick reason keeps the shared `pursued:` kind with its marker (BZ1); and an intent whose blocker was superseded waits on the successor, transitively (BZ2). The five itd-48 follow-ups are planned as one intent, drafted as itd-2609291923559186 (BT2), and all warn rules share one shrink-only baseline file in which a new warning fails, drafted as itd-2609291924469783 (BT3); both drafts wait on their planning interviews. Each ruling that contradicts a shipped behaviour or another record is amended in the change that builds it, not here. itd-7's planning interview stays owed.
- dcn-26093000000000092026-09-30 — The person answered eighteen rulings of autonomous run A on 2026-09-30 (CF1, CF2, CG1, CI1, CI2, CJ1, CK1, CL1, CM1, DR1, DR2, DR4, DR5, DR6, DQ1a, DQ1b, DQ2, DQ3), relayed by the run's interviewer abcd-23 [b1e81b] and recorded by lane recRulingsDR for orchestrator abcd-8d [5bfdfb]. The answers are kept in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, lines 13 to 30, and are restated here because that file is not committed. SETTLED BLOCKERS: a blocker replaced by an accepted ADR counts as settled, so the waiting intent may start (CF1); a blocker reclassified as a discipline counts as settled too (CF2). Neither is built at this entry's base: the blocked pre-start check still refuses both shapes, and iss-2609300751191426 carries the change. AS BUILT: the App token for the dependency re-authoring workflow is minted in the shell, an openssl-signed JWT exchanged with curl and revoked on exit, with no new action (CG1); when the prompt router cannot evaluate a prompt, its envelope carries the error and leaves the active-domain list out, so the client keeps what it had (CI1); a domain that returns to the active set is re-injected on every host, one that appends to a transcript included, not only on a client that snapshots (CI2); `abcd update` shows progress only when stderr is a terminal, and saved output stays clean (DR4). BUILD: the installer (`hooks/bootstrap.sh`) writes the previous release tag into binary-meta when it swaps a binary, the session hook stays read-only, and that tag replaces the setup_version comparison of the itd-111 update notice (CJ1); the SHELL domain also teaches a repository's own guard entries, generated the same way as the bundled registry's (CK1); `step` in `abcd implement check` is renamed `stage` too, in the same breaking v0.12.0 (CM1); the fix rounds a run spends before it hands a lane back are a per-run setting beside `--pace`, for example `--fix-rounds`, default 3 (DR1); the dependency-bump intent itd-2609221842494980 ships now, with the first live bump recorded as owed until the person has created the App and its secrets (DR2); only a self-contained agent is sent to a paid provider by default, so the cold-reading positions may go while a file-reading agent such as the intent auditor is refused with the reason, and a central override, a machine-level settings list the person keeps that no agent or repository can change, may name providers that take bundled-context requests for file-reading agents (DR5); a run works in parallel, building several pieces and running reviewers at once up to its agent limit, which is new build-loop work that makes the pacing intent's criterion 6 testable (DR6); an undecided fidelity audit reopens the work, sending the feature back to a lane, and never closes like a pass (DQ1a, iss-2608290820473197); the audit moves before the merge, as part of the build loop's last lane (per ruling AI), and until that loop is ready it stays after the merge and reopens on an undecided or failed verdict (DQ1b, iss-2608290822140563); the filing-time duplicate check covers every route, the promoted inbox report, the consistency pass and the reading ingest, in a lane after remedyRequired (DQ2, iss-2609281911024185); `abcd ahoy` offers to install gh on an explicit yes (DQ3, iss-2609281911024838). KEEP: a promoted inbox report's own remedy is kept but not usable: the sender's suggestion stays in the issue's text for reference, the remedy field is `none (filed automatically)`, and a drain skips the record until a person writes the fix, so the conservative default recorded above on 2026-09-30 stands and is not widened (CL1). The person added to CL1 that when a person comes to write the real fix for such an issue, the agent offers a state-of-the-art research pass first (principle `prefer-sota`), which `commands/capture.md` says under the `capture remedy` verb. The three rulings of 2026-09-29 owed to this ledger by earlier sessions of the run, the ceilings of five and of eight sub-agents and the remedies ruling, already have their entries above, so this entry adds none.
- dcn-26101200000000052026-10-12 — A build next series counts a pick whose lane waits for a person to merge its pull request as done and moves on, as the drain does, so both verbs read that lane state through one predicate (the product thinker's ruling on itd-2609211116005482 decision 4 and criterion 5; iss-2610111014218303).
- iss-2609211119023345The command list shows a person and an agent the same verbs. abcd --help and the plugin's command pages list every verb alike, but the verbs split by who types them: a person types abcd build <itd-N>, abcd build next, abcd drain, abcd intent, abcd capture; a driving host or an agent calls abcd implement step, abcd implement receipt, abcd reading assemble, abcd intent audit ingest and the other machinery verbs a person never types. A product thinker reading the list today cannot tell which half is theirs, and the ruling of 2026-09-21 that build is the verb for people and implement the loop for the machine (itd-2609201916151817, decision 8) needs a place to show. Wanted: the command list distinguishes the two, the person's verbs by default and the agent's behind a flag such as --agent (or a section), with each command page saying which it is; the product thinker recalls an existing record asking for this cleanup, so the first step is to find and link it rather than file twice.
- iss-2609252053576085The build's open_questions check (intent.OpenQuestions, read by internal/core/implement/loop/check.go) counts every list item under '## Open Questions' as a question whatever it says, so it refuses records that follow the record's own settled convention: 12 of 50 planned intents carry list items there, and some are settled by their own words, itd-111's section opening '_All resolved or explicitly deferred at planning_' and itd-93's '_All four resolved_', itd-76's item marked '**explicitly deferred**', itd-60's items marked '**Resolved', itd-117's '**Deferred**' items. 'abcd build next' (itd-2609211116005482) would meet refusals nobody planned, and the ruling lives only in a lane report. Keep the check fail-closed, but read the markers the record actually uses.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- iss-2610111007107579The pace window clock and the locked JSON state file beside the runs exist twice in the implement loop: loop/drain.go keeps drain.json under drain.lock with window_started_at and next_eligible_at, and build next's series (loop/nextseries.go, landing with itd-2609211116005482) keeps next.json under next.lock with the same fields and the same window logic, copied because drain.go is mid-change under itd-82. Two copies is the point the one-canonical-primitive principle files it.
- iss-2610111014218303Two verbs in the implement loop read one lane state differently: build next's series counts a pick delivered only when its run is Complete(), which a lane whose pull request is left open for a person to merge never reaches, so 'build next --until-empty' in such a repository makes one pick and then only says 'drive run X' until a person merges; the drain reads the same state as DrainLanePullRequest and moves on, because the merge is a person's gate. itd-2609211116005482 decision 4 calls the two flags shared with the drain, and criterion 5's 'its lane stops' does not say which reading is meant. Found by the build-next lane's ruthless review (run-2610110908001704).
- iss-261026203324804105-intent names the phase plan as the canonical build order, but 08-abcd orders what gets built by build next's pick order.
- iss-2610262037208650itd-2610070550516046 requires the pick's intent to be reconciled with it before either is built; the pick is already built and shipped as build next.
- iss-2610262040426321itd-2610070550516046 makes the product thinker's stored order decide what is built next; record-families says sequence comes only from dependencies and the shelves.
- iss-2610262045139907itd-94 has autonomous drain runs implementing intents; itd-82 defines drain as working issues only and leaves picking intents to `abcd build next`
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-11Disembark Survives Noisy Transcripts
- itd-12Working Notes Don't Outrank Settled Decisions
- itd-14Prompts Are Versioned Like Code
- itd-2609091034175565Before an agent starts on an item, it reserves it on the shared remote, and everyone who works on the project sees the reservation
- itd-2609221009495079abcd's typed judgements can run through a decision adapter, measured in a lab before it decides anything
- itd-2610070549060530A word in a new idea marks it to revisit, and the board puts it first
- itd-2610070550516046On the dashboard, a product thinker sees the brief and their ideas together, and puts the ideas in order