Development record
`abcd drain` fixes the issues that need no decision, and hands the rest back by kind
Press Release
abcd drainworks the open issue ledger unattended: it fixes what needs no decision, and routes what does to the place a person decides it. It reads every open issue that nothing blocks, and lets through only the ones the rules say a machine may touch alone: aremedy:field on the record (captures carry one from now on; a record without it is ineligible until someone adds it), a category the run may fix, not security, notmajororcritical, nothing unshipped blocking it; a host judgement over the remedy may hand an issue back for a user-visible or trust-boundary change, and can only ever make the set smaller. Each of those goes to the same loopabcd builduses, keyed by the issue (the machinery grows an issue key for it: the brief is the record and its remedy, the landing resolves the record), one lane, one pull request, the run never approving its own pull request. An issue that needs a decision is handed back by kind: one that would change what a user sees becomes an intent draft seeded from the issue, for a person to plan; one that turns on a rule about trust or safety is flagged as needing a decision record, with the question stated; anything else is flagged with the home the decision belongs in. A lane that discovers a decision inside a "mechanical" fix stops, discards its work, and hands the issue back the same way rather than guessing. Blocked issues are skipped with the blocker named. It runs until nothing eligible is left, paced by the pace rule, because "drain" means all;--max <n>caps a run, and at a window's end it writes when it may continue and exits, the next invocation carrying on. In the morning there are three lists: pull requests to review, drafts to plan, decisions to make."I used to point the loop at the ledger and then hover, because the moment it hit something design-shaped it would either stall or, worse, decide it," said a product thinker who ran the first ledger drains by hand. "Now the mechanical ones arrive as pull requests, the ones that are mine arrive as drafts or as questions, and the one time a lane found a decision halfway through, it stopped and told me instead of finishing."
Why This Matters
The pilot run of 2026-09-20 fixed a cluster of five captured issues through lanes, reviewers and a merge queue, and the part a person did by hand at every step was the sorting: which capture a lane may take alone, which is a design decision in disguise, which is blocked. That sorting is the load-bearing, human-shaped piece, and it is the one a person should not run every night. Its failure is one-directional: a machine that decides a thing needs no decision, and then makes one. The evidence gathered on 2026-09-21 is plain about it: models detect their own ambiguity badly (the best reaches 89 per cent with a 3 per cent false-positive rate only under strong prompting; weaker ones flag 93 per cent of clear tasks as ambiguous), two teams that let a triage pass auto-dispatch a coding agent reverted it after unwanted pull requests, and the largest dataset of agent pull requests (878 over ten months) shows clean-ups and tests merging at 85 and 76 per cent while features and performance work merge at 65 and 55. Industrial fix pipelines land 15 to 25 per cent of what they attempt; most lanes end without a merge, and that has to be a normal ending, not a failure.
The record already has the sorting rule. The four-piece routing every filing goes through (capability to an intent, trust rule to a decision record, stance to a principle, plumbing to the brief) is what a decision-shaped issue needs applied to it, and a capture is not an intent: an intent is a user moment, and an issue whose decision is a rule about trust is not one. So the hand-back routes by kind rather than promoting everything into drafts a person then has to sort again.
Mechanism
We expect a rule-first eligibility filter, a lane that hands back on discovering a decision, and a hand-back routed by kind to drain the mechanical part of a ledger without a person and without a decision being made by a machine, because the rules that predict a safe unattended fix are fields on the capture (a remedy: field, category, severity, the blocked-by edges) and the hand-back is a write the verbs already make (capture promote for a user moment, a flag for the rest); shown wrong if a hand-back is then let through by a person unchanged more often than not, counted per run.
Scope Conditions
- Holds for a repository abcd manages with a merge queue or branch protection on the default branch, so a pull request the run opens has a path to the default branch that the run does not control.
- Holds for issues captured through
abcd capturewith a category, a severity and aremedy:field; a record without the field is not eligible and is listed as such, so the first drains over today's ledger are small. - Holds once the decision record for the eligibility rule exists (decision 4): an autonomous lane refuses to start without it, and the record is a prerequisite this intent names, not a step it performs.
- The rules are the fields (
remedy:, category, severity,blocked_by); the host-delegated judgement reads the remedy for a user-visible or trust-boundary change and can only hand back, never let through, and that judgement is recorded with the disposition.
What's In Scope
- Eligibility, by field: open; nothing unshipped in
blocked_by; aremedy:field present; category one ofbug,documentation,drift,inconsistency,tech-debt,ux(the fixable set); severityminorornitpick. Excluded by field:security(always a person's),majorandcritical(the hand-back unless the repository's drain record loosens it; no run flag does, decision 11), and the decision categoriesprocess,observation,architectural-insight,future-work-seed,lapse(the run file's merits set). A host-delegated judgement (adr-25) reads the remedy for a user-visible or trust-boundary change and may only hand back. - The
remedy:field:capturegains it (the oldersuggested_fixmade first-class under the name the eligibility reads) and requires it of every new issue (ruling BX3 of 2026-09-29); an automatic filer with no fix writesnone (filed automatically)(ruling H12 of the same day), which is ineligible until a person writes a real remedy withcapture remedy; a record filed before the rule carries none, and is ineligible and listed as such. - The lane: the implement loop keyed by the issue (decision 10 on
itd-2609201916151817: the brief is the record and its remedy, the checks are this record's eligibility, the landing iscapture resolve --commit), one lane per issue, one pull request per issue with the repository's merge rule applied, the run's identity never an approver, nothing pushed to a pull request after its merge is armed. - Reproduce, then fix: the issue-keyed lane's definition of done, owned by the loop (decision 10 on
itd-2609201916151817): a detector fails before the fix and passes after, and the adversarial reviewer stays the oracle with the detector as evidence for it. - The hand-back, routed by kind: a user-moment issue is promoted to an intent draft (
capture promote,itd-119's verb, onmainwith its spec open; seeded from the issue; the issue gains the intent inrelated_intentsand nothing else); a trust or safety rule is flagged as needing a decision record with the question stated, in the summary and as a marker on the issue (decision 10), and nothing is minted; an issue above the run's severity, or in a category the run does not fix, is flagged as a person's with that reason; anything else is flagged with the proposed home named. The lane's partial work is discarded. - The mid-lane stop: a lane that meets a decision stops, discards, and hands back with the reason, as a first-class outcome; the
handback:field on the lane report is the loop's (decision 10 on the parent), and this record names the values drain reads from it (a reviewer's design finding, a second package, a user-visible change). - Order: by category in the declared order
tech-debt,documentation,inconsistency,drift,bug,ux(clean-ups and text first, on the evidence that they merge most often; behaviour changes last), thennitpickbeforeminor, then oldest first; the run's summary states the rule. - Ceilings: the pace rule bounds the run (at a window's end it writes
next_eligible_atand exits; the next invocation continues);--max <n>caps issues attempted and--until-emptyis the default's own name; the pull requests opened, the promotions made and the spend are counted in the summary, and a cap hit is named. - The command page:
commands/capture.md(or the drain page) says whatdraindoes, its flags, the order, the hand-back kinds and every refusal. - The summary: every issue's disposition (pull request number, draft id, flag with home or reason, ineligible with the missing field named, skipped with blocker, stopped with reason), machine-readable and re-runnable; "no pull request, reason logged" is a normal terminal state.
What's Out of Scope
- The lane, the validators and the landing:
itd-2609201916151817; the pace:itd-2609201925079472. - Picking among intents:
abcd build next(itd-2609211116005482), the sibling run. - Merging pull requests, planning promoted drafts, writing decision records: human gates by design.
- Any change to the ledger schema beyond the
remedy:field and the decision-record marker (decision 10); the run consumeslist,resolveandpromote. - Any estimate the agent makes of its own confidence as a reason to let an issue through.
Decisions
Ruled by the product thinker on 2026-09-21, in the interview that revived this draft:
- This record is the drain run, updated for the implement machinery; no new record, and the verb keeps its name,
abcd drain, which is what a person types. It hands over toabcd implement, the machinery an agent calls. - The hand-back routes by kind: user moments become intent drafts; trust rules are flagged for a decision record; the rest are flagged with their home. Not everything promoted, not everything merely listed.
- All by default, capped by flag: "drain" reads all and "next" reads one, so the two runs' defaults differ on purpose and their flags are the same.
- The rule for "needs no decision" is a recorded rule. It decides what a machine may touch alone, so it is written as a decision record and a brief invariant before this path ships, the way
--auto-planowes its record; the spec's first delivery mints it withabcd decidefrom this decision's text and the eligibility scope above, the reviewers judge it with the diff, and until it exists an autonomous drain refuses to start. - The build machinery grows an issue key (ruled 2026-09-21 on the design review's first finding):
abcd implementtakes an intent or an issue; drain hands itiss-N. Recorded as decision 10 onitd-2609201916151817. - A
remedy:field, required from now on (ruled 2026-09-21): eligibility reads it; the backlog without it waits for someone to add it. - Order and categories declared over the ledger's own enum (scope above), so no implementer decides them alone.
- The classification is re-derived every run and recorded with each disposition in the summary; nothing is written onto the issue for it (ruled 2026-09-21; the durable-field alternative is left for a ledger that outgrows re-derivation).
- The drain's remaining work is one plan (ruled 2026-10-09): the product thinker grouped what the drain still needs to run end to end into this intent's plan: the judgement before a lane opens and the summary's remaining counts, plus three steps from issues (merged lanes finished, iss-2610090936460789; one issue or package held back for now, iss-2610090642385898; a pull request that leaves the merge queue put back, iss-2610090642376032). Pacing stays its own intent (itd-2609201925079472), built alongside; this intent builds on it. The drain joins the person's command list when this intent ships (ruling BX1 kept). Answer verbatim: "Group as proposed".
- The decision-record flag is also a marker on the issue (ruled 2026-10-10, the first open question): a trust-rule hand-back writes a marker onto the open issue, naming the decision record it needs and stating the question, as well as the line in the summary, so a person finds it without the summary. It is a write to a record the run otherwise never edits, chosen knowing that; it is not the classification, which decision 8 keeps re-derived every run. Answer verbatim: "Marker on the issue too".
majoris let through by the repository's drain record, never by a flag (ruled 2026-10-10, the second open question): invariant 19 already lets a repository's record loosen its rule tomajor, and a drain never runs looser than its record, so there is no--allow-major; abcd's own record keepsnitpickandminor. Answer verbatim: "Record only, no flag", given when the first answer, "Opt in with --allow-major", was put beside invariant 19.
Prior Art
itd-2609201916151817(the implement machinery): the lane, the validators and the landing this run dispatches onto; it supersedesitd-29, which this draft first named.itd-119/ spc-24: ownscapture promote <iss-N>, the issue-to-intent elevator the user-moment hand-back uses; the verb is onmainwith its spec open.itd-4: the ledger substrate (list,resolve) the run reads and mutates.itd-84: the four-piece routing the hand-back applies to a decision-shaped issue.- adr-25 (host-delegated judgement): the residual classifier call rides the host and only hands back.
- adr-27 (receipt-gated autonomous runs): the lane's review discipline.
SOTA
Surveyed 2026-09-21 by an independent research pass, primary sources where they exist:
- Triage that dispatches is the failure mode. GitHub's own reference triage workflows label and comment only, never assign or close; two independent repositories that let a triage label auto-dispatch a coding agent reverted it after unwanted pull requests. Adopted: the run's eligibility rules are the only thing that lets an issue through, and the hand-back's only write is a promotion for a user moment.
- The classifier is rules first. Copilot's published guidance keeps ambiguous, open-ended, security, PII, authentication and production-critical work for people and assigns bugs, tests, docs and debt to the agent; repositories converged independently on
agent-ready/needs-designlabels. Ambiguity self-detection by models is unreliable (Ambig-SWE, ICLR 2026), so the residual judgement may only hand back. - Order by category and footprint, not severity. The dotnet/runtime dataset (878 agent pull requests, ten months): clean-up 84.7, testing 75.6, bug fix 69.4, feature 64.5, performance 54.5 per cent merged; one platform's own fleet report had refactoring and performance queues at zero closure. Adopted for this run because its scope is already bounded to no-decision issues.
- Detector-first is contested. Impact-aware test selection cut regressions from 6.08 to 1.82 per cent while a bare "write a failing test first" instruction worsened them to 9.94 (TDAD, 2026); 29.6 per cent of plausible patches on SWE-bench behave differently from the reference. Adopted as reproduce-then-fix with the reviewer as the oracle.
- The safety envelope is GitHub's two designs combined: one branch, one pull request per task, the requester cannot approve, a hard session cap (Copilot's cloud agent); read-only agent job with writes through a scoped step,
max: 1pull request, daily credit and per-user rate limits,stop-after(gh-aw). Translated: per-run caps, the run's identity is not an approver, the merge queue is the path tomain. - Expect most lanes not to merge. Google's sanitizer-fix pipeline landed 15 per cent of candidates; Meta's test-repair agent 25.5 per cent over three months with a judge before human review. "No pull request, reason logged" is a normal ending.
- Not adopted: WSJF and cost-of-delay (no outcome evidence, needs human estimates); the agent's self-reported confidence as a gate (vendor feature, no published threshold); fully automatic readiness promotion by a triage model.
Open Questions
All resolved: the decision-record flag's home by decision 10, and whether major may ever be let through by decision 11 (both ruled 2026-10-10).
Acceptance Criteria
- Given an open ledger holding a
minorbugwith aremedy:field, aminorbugwithout one, amajorissue, an issue in categorysecurity, aprocessissue, and an issue blocked by an open record, whenabcd drainruns, then only the first goes to a lane; the one without a remedy is listed as ineligible; themajor, thesecurityand theprocessissues are handed back with the kind named; the blocked one is skipped naming its blocker; and every open issue receives exactly one recorded disposition. - Given an eligible issue whose remedy the host judgement reads as a user-visible or trust-boundary change, when the pass classifies it, then it is handed back with that reason, and the judgement never makes an ineligible issue eligible.
- Given an eligible issue, when its lane runs, then it is the implement loop's issue-keyed lane: the brief is the record and its remedy, the loop's definition of done holds (a detector fails before the fix and passes after), the record is resolved with the fix commit in the same change, one pull request is opened with the repository's merge rule applied, and nothing is pushed to it afterwards.
- Given a decision-shaped issue that describes a user moment, when it is handed back, then an intent draft exists seeded from the issue, the issue gains the intent in
related_intentsand nothing else, and no design or adoption verdict is recorded. - Given a decision-shaped issue that turns on a trust or safety rule, when it is handed back, then it is flagged as needing a decision record with the question stated, in the summary and as a marker on the open issue, and nothing is minted.
- Given a lane whose report carries
handback:(a reviewer's design finding, a second package or a user-visible change the remedy did not name), when the loop reads it, then the lane stops, its work is discarded, the issue is handed back with the reason, and the summary records the stop as its own outcome. - Given the default run, when eligible issues remain at a window's end, then the run writes
next_eligible_at, exits, and the next invocation after it continues; given--max <n>, then it stops at the cap and names it. - Given the command page, when it is read, then it states what
draindoes, its flags, the category order, the hand-back kinds and every refusal. - Given the decision record for the eligibility rule does not exist, when an unattended drain starts, then it refuses naming the record it needs.
- Given a completed or paused run, when its summary is read, then every issue's disposition is there (pull request number, draft id, flag with home, skip with blocker, stop with reason) with the ordering rule and every cap, in text and in
--json, and a run whose lanes all ended without a merge exits 0 with that stated. - Given a crafted issue id, when the run resolves it, then it is validated by shape and refused otherwise, and no file outside the ledger directories is read, written or moved.
Typed Links
- builds_on
itd-2609201916151817(the implement machinery) anditd-2609201925079472(the pace): the lane and its clock. - builds_on
itd-119(capture promote) anditd-4(the ledger): the writes the hand-back and the resolve make. - refines
itd-84(decompose before filing): the routing applied to a decision-shaped issue at hand-back. - related
itd-2609211116005482(abcd build next): the sibling run over intents; 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 runs on an issue lane as on an intent lane. - built on by
itd-2610301750162657(abcd's unattended work is spot-checked against specs and intents): Draws a few of the drain's fixes and hand-backs and has them checked against their specs, filing each finding against this intent's rule (adr-2609291342092738) for a person to amend. - built on by
itd-2610301850454860(abcd records how each change landed): Records which changes a drain lane landed and under which rule.
Audit Notes
Fidelity review — receipt rcp-e731cc8c71a1 (verifier intent-auditor claude (the harness exposed no model id)).
Provenance: intent-auditor@claude (the harness exposed no model id) · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:b02e64727895ee1512b9bba5901a773d4dadf01b26bec31aad53e790df9ab744 Input attestations: diff:e1f96e610526a5a14bf93e95f6632daa8bafa51e..2e297483e6873e9bcc65ce90dc43e775456cd465 (lane-2, spec step 6)@sha256:f3f411552cfb165d4a8847d8c5bd0daa7a3abc1682dc01586a21ab75782d19fb; diff:87a5dd1b41e3786b81ec24822dd25edee3a5e022..ade000d23d6078368df4d8babe9e1fc67e4fc889 (lane-1, spec step 5, PR #1370)@sha256:993dc3ba64deb9dd4a44ff6198f8301e2162dba01b9fcd80863cd1bd0496e97a; commit:80fa3da48 (spec step 1), 378348a14 (step 2), 91765d9c5 (step 3), 6594725b5953 (step 4); repository read at 2e297483e6873e9bcc65ce90dc43e775456cd465@-; intent:.abcd/development/intents/planned/itd-82-drain-ledger-triage.md@sha256:31a47f4b6c2da6e383395969302c8c4d03322a801c978755e9227eb2cb611064; spec:.abcd/development/specs/open/spc-2609212015054359-drain-ledger-triage.md@sha256:d13def14dda90a34d81a45e7ba6e0030004c318eed75ad91beb9707da9c8bd39;
Acceptance rollup: MET 9 · MET_WITH_CONCERNS 2 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: The field rule asks blocked, security, category, severity and remedy in a fixed order, so each issue gets exactly one disposition. A fixture with the criterion's six issues gives one eligible, one ineligible (remedy), three hand-backs (severity, security, category naming process) and one skip naming its blocker. The test passed when I re-ran it at 2e297483e. evidence: internal/core/capture/eligible_test.go:142 — "func TestDrainRoutesAMixedLedgerByField(t *testing.T) {" evidence: internal/core/capture/eligible_test.go:153 — "want 6 dispositions (one per open issue)" evidence: internal/core/capture/eligible.go:155 — "case len(iss.BlockedByOpen) > 0:" evidence: internal/core/capture/eligible.go:200 — "case strings.TrimSpace(iss.Remedy) == "":"
- ac-2 — MET: A host-judgement yes hands the issue back, before any lane opens, under its kind and with its reason. judgeAnswer records an answer over an issue that is no longer eligible and gives it no effect, so the judgement can never admit an issue. Tests cover the user-visible yes, the trust-boundary yes and the never-eligible case, and they passed when I re-ran them. evidence: internal/core/implement/loop/judgement.go:377 — "if verdict == nil || verdict.Outcome != capture.DrainEligible {" evidence: internal/core/implement/loop/judgement.go:384 — "out.Note = j.Issue + " is no longer eligible, so the judgement decides nothing: " + why" evidence: internal/core/implement/loop/judgement_test.go:198 — "func TestTheJudgementNeverMakesAnIneligibleIssueEligible(t *testing.T) {" evidence: internal/core/implement/loop/judgement_test.go:142 — "func TestAUserVisibleRemedyIsHandedBackBeforeAnyLaneOpens(t *testing.T) {"
- ac-3 — MET_WITH_CONCERNS: The issue key opens one lane whose brief is the record and its remedy. The landing resolves the issue in the lane's own change, with a Resolves: trailer and the commit, and opens one pull request armed by the ruleset's method; nothing is pushed after arming. Concern: the reproduce-then-fix definition of done is an instruction in the brief, judged by the validators. Nothing in the loop checks that a detector failed before the fix and passed after, so 'holds' rests on the implementer and the reviewer. The intent accepts that (the reviewer stays the oracle), but the criterion's wording is stronger. evidence: internal/core/implement/loop/issuebrief.go:154 — "Reproduce, then fix: write a detector (a test) that fails before the fix and passes after" evidence: internal/core/implement/loop/land.go:389 — "if _, err := capture.Resolve(capture.ResolveRequest{RepoRoot: wt, ID: r.Issue, Resolution: r.Note, Impact: r.Impact," evidence: internal/core/implement/loop/issue_test.go:349 — "func TestAnIssueLaneLandsOnePullRequestThatResolvesItsIssue(t *testing.T) {" evidence: internal/core/implement/loop/land.go:42 — "Nothing is pushed to the lane" evidence: internal/core/implement/loop/land_test.go:460 — "func TestNothingIsPushedAfterArming(t *testing.T) {"
- ac-4 — MET: A user-visible hand-back goes through capture promote and mints an intent draft. The drain test diffs the record line by line and finds exactly one added line, related_intents naming the draft, with nothing removed. Promote has no verdict path: I found no verdict or adoption write in promote.go.
evidence: internal/core/implement/loop/drain_test.go:137 — "if r.Route != RoutePromoted || !strings.HasPrefix(r.Draft, "itd-") {"
evidence: internal/core/implement/loop/drain_test.go:165 — "if len(removed) != 0 || len(added) != 1 || !strings.HasPrefix(added[0], "related_intents:") {"
evidence: commands/drain.md:562 — "|
user-visible|promoted: an intent draft seeded from the issue (capture promote) | the draft, and the issue'srelated_intents; nothing else |" - ac-5 — MET_WITH_CONCERNS: Lane-2 delivers the marker. A trust-rule hand-back, from a lane or from the judgement, is flagged in the summary with its question, and markDecisionNeeded writes
decision_needed: <question>onto the open record as the only changed line; no draft or decision record is minted. Tests check the one-line diff, the dry run and a later drain reading the marker, and no double write; they passed when I re-ran them. Concerns: first, a move planned from origin writes the marker only where this checkout holds the record as origin's default branch does; otherwise there is only the summary flag. Second, the marker is written uncommitted in the checkout, so no other clone sees it until a person commits it. Third, decision 10 says the marker names the decision record it needs; the marker holds only the question, because nothing is minted to name. Fourth, no verb clears the marker; a person edits the line out by hand. evidence: internal/core/implement/loop/drain.go:1441 — "wrote, err := markDecisionNeeded(repoRoot, issue, from, hb.Reason, origin)" evidence: internal/core/capture/decisionneeded.go:116 — "newContent, err := setScalarField(content, issueschema.DecisionNeededKey, question)" evidence: internal/core/implement/loop/drain_marker_test.go:92 — "if len(removed) != 0 || len(added) != 1 || added[0] != "decision_needed: ""+question+""" {" evidence: internal/core/implement/loop/drain.go:1481 — "return "nothing here: " + why + ", so the decision_needed: marker is not written here; a flag in this summary", nil" evidence: commands/drain.md:580 — "checkout, uncommitted, as a promotion is; a move planned fromoriginwrites" - ac-6 — MET: A receipt carrying handback ends the lane before its validators. The worktree and branch are removed, the discarded head is recorded, the lane is handed back with its kind and reason, and every later step refuses. The drain routes it by kind and counts it as its own outcome (handed-back, lanes_handed_back). The tests passed when I re-ran them.
evidence: internal/core/implement/loop/issue_test.go:289 — "func TestALaneReportHandBackStopsTheLaneAndDiscardsItsWork(t *testing.T) {"
evidence: internal/core/implement/loop/drain.go:114 — "DrainLaneHandedBack = "handed-back""
evidence: internal/core/implement/loop/drain.go:318 — "LanesHandedBack int
json:"lanes_handed_back"" - ac-7 — MET: At the window's end the drain writes next_eligible_at to its state file and opens nothing. Before that time a drain opens nothing; after it, the next invocation opens the next lane. --max stops the drain at the cap, names it in next and refuses a negative value. The tests passed when I re-ran them. evidence: internal/core/implement/loop/drain_test.go:225 — "func TestADrainPausesAtItsWindowsEnd(t *testing.T) {" evidence: internal/core/implement/loop/drain_test.go:208 — "if capped.Stopped != DrainStoppedCap || !capped.Complete || capped.Start != nil || !strings.Contains(capped.Next, "--max 1") {"
- ac-8 — MET: commands/drain.md states what the drain does, lists every flag in its argument hint and prose, and gives the category order, a rule table of hand-backs, a hand-back-by-kind table (now including the marker) and a Refusals section. A test holds the page to the marker rows.
evidence: commands/drain.md:4 — "argument-hint: "[--dry-run] [--max < n>] [--pace < work-minutes>/< pause-minutes>] [--sub-agents < n>] [--fix-rounds < n>] [--judgement < file>] [--take-over] [--skip < iss-N|path> --reason < why>] [--unskip < iss-N|path>]""
evidence: commands/drain.md:96 — "
tech-debt,documentation,inconsistency,drift,bug,ux, then" evidence: commands/drain.md:641 — "## Refusals" evidence: internal/core/capture/eligible_test.go:714 — "func TestTheDrainPageNamesTheDecisionRecordMarker(t *testing.T) {" - ac-9 — MET: Without an accepted record of the eligibility rule, both the dry run and the bare drain refuse and write nothing. The refusal names 'drain eligibility record', its four fields and how to add it. The record exists for abcd itself (adr-2609291342092738). The rule moved to each repository's own record (rulings BX2/H11), so the refusal names the kind of record needed rather than one fixed ADR id. evidence: internal/core/drainrule/drainrule.go:134 — "ErrUnrecorded = errors.New("this repository holds no drain eligibility record")" evidence: internal/core/implement/loop/drain_test.go:263 — "if _, err := Drain(repo.Root(), Options{}, DrainOptions{}); err == nil || !strings.Contains(err.Error(), "drain eligibility record") {" evidence: internal/core/capture/eligible_test.go:442 — "func TestADrainRefusesARepositoryWithoutItsOwnRule(t *testing.T) {"
- ac-10 — MET: In text and --json, the summary carries every disposition, the routes (pull request number, draft, decision-record question, home), the passed issues with their blockers, the order rule and max, plus the counts and spend. A drain whose lanes all ended without a pull request says this is a normal ending, and each move exits 0. The tests passed when I re-ran them.
evidence: internal/core/implement/loop/drain.go:568 — "res.Next = joinNext(res.Next, "no lane ended in a pull request, which is a normal ending: every lane's outcome and hand-back is above")"
evidence: internal/core/implement/loop/drain.go:255 — "Order string
json:"order"" evidence: internal/core/implement/loop/drain_counts_test.go:87 — "func TestADrainWhoseLanesAllEndedWithoutAPullRequestSaysSo(t *testing.T) {" evidence: commands/drain.md:164 — "Eachabcd drainwithout--dry-runperforms one move and exits 0, saying" - ac-11 — MET: Issue ids are checked against IssIDRe before anything is read. A traversal-shaped key such as iss-../../x is refused at the key check with no run tier written. The new marker writer checks the id shape and requireHeld before the preamble, and ledger writes are guarded against redirection outside the checkout. evidence: internal/core/implement/loop/issue_test.go:158 — "for _, key := range []string{"iss-../../x", "iss-", "iss-12a", "iss-1/2", "iss-0", "iss-02609292352131344"} {" evidence: internal/core/capture/decisionneeded.go:76 — "if !reIssID.MatchString(req.ID) {" evidence: internal/core/capture/ledgerroot_test.go:80 — "func TestLedgerRecordWriteCannotBeRedirectedOutsideTheCheckout(t *testing.T) {"
Gap audit:
- honoured:
- Eligibility is rule-first, by field, and the host judgement can only hand back. evidence: internal/core/capture/eligible.go:142 — "func eligibility(iss Issue, r drainrule.Rule, anchor deferralAnchor, opts ...eligibilityOption) DrainVerdict {" evidence: internal/core/implement/loop/judgement.go:378 — "// The judgement never makes an issue eligible: it is recorded, and"
- Decision 10: a trust-rule hand-back marks the open issue with its question, changes nothing else and mints nothing. Invariant 19 names the marker as the hand-back's flag, not the classification, so decision 8 keeps re-deriving the classification every run. evidence: internal/core/capture/decisionneeded.go:71 — "func MarkDecisionNeeded(req DecisionNeededRequest) (DecisionNeededResult, error) {" evidence: .abcd/development/brief/02-constraints/03-invariants.md:218 — "The one write a hand-back makes to an issue it does not promote is the decision-record marker"
- The hand-back is routed by kind: a user moment is promoted, a trust rule gets a decision-record flag and marker, a design finding or second package is flagged with its home.
evidence: internal/core/implement/loop/drain.go:1439 — "case HandBackTrustRule:"
evidence: commands/drain.md:563 — "|
trust-rule|decision-record: flagged, with the lane's reason asquestion|decision_needed:on the open issue, holding the question; nothing else; no record is minted |" - The merge queue is the run's path to main: an armed pull request that leaves the queue is put back or reported with the failed check (lane-1, step 5). evidence: internal/core/implement/loop/queue_test.go:77 — "func TestALandedPullRequestLeftUnqueuedAtCleanIsEnqueued(t *testing.T) {" evidence: internal/core/implement/loop/queue_test.go:171 — "func TestAPullRequestDroppedAfterAFailedMergeGroupCheckIsReportedWithTheCheck(t *testing.T) {"
- 'No pull request, reason logged' is a normal terminal state. evidence: internal/core/implement/loop/drain.go:568 — "no lane ended in a pull request, which is a normal ending"
- diverged:
- The intent says the rules are the fields remedy, category, severity and blocked_by. The delivered rule also hands back on related_intents (promoted), the new decision_needed marker, a remedy opening 'Waits on', deferred_after, and the automatic filers' machine remedy. Each of these only narrows the set, so the safety direction holds, but the rule reads more than the four fields the intent names.
evidence: internal/core/capture/eligible.go:168 — "case strings.TrimSpace(iss.DecisionNeeded) != "":"
evidence: commands/drain.md:83 — "|
handback|waits-on-ruling| its remedy opens "Waits on": the fix waits on a ruling a person has not given |" - Decision 10 says the marker names the decision record it needs and states the question. The marker holds the question only: nothing is minted, so there is no record id to name. It is written uncommitted in the checkout, and on a move planned from origin only where the checkout holds the record as origin does. evidence: internal/core/issueschema/issueschema.go:187 — "const DecisionNeededKey = "decision_needed"" evidence: internal/core/implement/loop/drain.go:1481 — "so the decision_needed: marker is not written here; a flag in this summary"
- The eligibility record is no longer one ADR this intent names as a prerequisite. It is each repository's own accepted record carrying four drain_ fields, which abcd ahoy install offers to write, and a repository's record may loosen the floors.
evidence: internal/core/drainrule/drainrule.go:146 — "const HowToAdd = "add it: run
abcd ahoy installat a terminal and accept the drain rule it offers, " +" - The detector-first definition of done is an instruction to the implementer that the validators judge. It is not a check the loop enforces. evidence: internal/core/implement/loop/issuebrief.go:156 — "conventions below state it, in your worktree. The validators judge the fix with the detector as"
- The intent says the rules are the fields remedy, category, severity and blocked_by. The delivered rule also hands back on related_intents (promoted), the new decision_needed marker, a remedy opening 'Waits on', deferred_after, and the automatic filers' machine remedy. Each of these only narrows the set, so the safety direction holds, but the rule reads more than the four fields the intent names.
evidence: internal/core/capture/eligible.go:168 — "case strings.TrimSpace(iss.DecisionNeeded) != "":"
evidence: commands/drain.md:83 — "|
- missing:
- No verb clears the decision-record marker once the decision record is written. A person removes the decision_needed: line by hand. The implementer judged a clearing verb to be out-of-scope schema surface. evidence: commands/drain.md:586 — "question as its reason. Once the decision record is written, take the"
Scope-condition dispositions:
- cond-2609212015056112 — survived: The landing arms the merge through landing.DecideMerge and the ruleset mirror's queue method; where the gate does not allow it, the pull request is left open. Lane-1 adds a watch that re-queues or reports an armed pull request that leaves the queue. The default branch is still reached through a path the run does not control. evidence: internal/core/implement/loop/land.go:968 — "d, err := landing.DecideMerge(landing.MergeQuery{RepoRoot: c.RepoRoot, Roots: roots, Tip: tip, TipName: Remote + "/" + def, Default: def," evidence: internal/core/implement/loop/queue.go:3 — "// queue.go is the landing's watch over the merge queue (spc-2609212015054359"
- cond-2609212015051754 — survived: capture refuses a new issue without a remedy. A record without one, or carrying the automatic filers' machine value, gets the outcome ineligible under the remedy rule and is listed with how to add one. evidence: internal/core/capture/capture.go:466 — "ErrRemedyRequired = errors.New("remedy required")" evidence: internal/core/capture/eligible.go:200 — "case strings.TrimSpace(iss.Remedy) == "":"
- cond-2609212015056014 — survived: No drain starts without an accepted record of the eligibility rule: both the dry run and the bare verb refuse and write nothing. abcd's own record, adr-2609291342092738, exists, so the condition holds in this repository. The record is now each repository's own rather than a single named ADR; that move is recorded under diverged. evidence: internal/core/drainrule/drainrule.go:134 — "ErrUnrecorded = errors.New("this repository holds no drain eligibility record")" evidence: internal/core/implement/loop/drain_test.go:261 — "func TestADrainRefusesWithoutTheRepositorysRule(t *testing.T) {"
- cond-2609212015052582 — narrowed: Only the field rule can make an issue eligible. The judgement can only hand back, and it is recorded with the disposition in the drain's state and summary. But the delivered rule reads more than remedy, category, severity and blocked_by: lane-2 adds decision_needed, and the rule also reads related_intents, a 'Waits on' remedy, deferred_after and the machine remedy. So 'the rules are the fields' holds only in the admitting direction. narrowing: The four named fields (remedy, category, severity, blocked_by) are the only fields that can make an issue eligible, and the host judgement only hands back. Further fields can also hand an issue back: related_intents, decision_needed (written by the drain's own trust-rule hand-back), a remedy opening 'Waits on', deferred_after and the machine remedy. So the four fields are necessary for eligibility, but they are not the whole rule. evidence: internal/core/capture/eligible.go:168 — "case strings.TrimSpace(iss.DecisionNeeded) != "":" evidence: internal/core/implement/loop/judgement.go:384 — "out.Note = j.Issue + " is no longer eligible, so the judgement decides nothing: " + why"
Grounds
- pursued: the autonomous run's capture batches are a person's hand-written routing today; we expect the drain to be what replaces them after the run; shown wrong if the next run still starts from a hand-written table
Frontmatter
| id | itd-82 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-82-drain-ledger-triage.md
open on GitHub ↗ · commit history ↗
References9
- adr-25The LLM is host-delegated by default; oracles are opt-in adaptersrelated
- adr-27The autonomous run is a pluggable seam, not a Ralph portrelated
- itd-4Nothing You Notice Gets Lostbuilds on
- itd-50The Audit Loop Drives An Intent To Acceptance — Or Calls For A Replanbuilds on
- itd-84An Intent Is Decomposed Before It Is Filed, And A Reversal Is Only Ever Flaggedrelated
- itd-119An Issue Graduates Into An Intent Without Retypingbuilds on
- 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
- itd-2609211116005482`abcd build next` picks the readiest planned intent itself, and writes down whyrelated
Referenced by19
- adr-2609291342092738A drain takes an issue alone only when its fields say it needs no decisionrelated
- iss-2610262031575425itd-82 requires a remedy on every new issue; the brief's acceptance criterion has /abcd:capture "< text>" alone produce a structured issue.related
- iss-2610262031575529itd-82 makes bare abcd drain the unattended run that changes state; 04-naming rules that a bare invocation renders and that changing state is what a sub-verb is for.related
- iss-261026203158203805-personas requires press-release quotes from the persona registry; itd-82's quote is attributed to an unnamed product thinker.related
- iss-2610262037387137itd-82 says a record filed before the remedy rule has no remedy and is ineligible; 35-drain reads such a record's legacy suggested_fix as its remedyrelated
- iss-2610262037392162itd-82 declares the drain order ends oldest first; 35-drain orders each group by a rendezvous hash on origin, with file paths ahead of directoriesrelated
- iss-2610262037409579itd-82's --max caps issues attempted and names --until-empty as the drain default's flag; 35-drain's cap counts lanes opened, and the drain has no --until-emptyrelated
- iss-2610262041372020The loop glossary says abcd never owns the run loop and an adapter supplies it; itd-82 sends drain's issues through the loop `abcd build` itself runsrelated
- iss-2610262041388352The persona glossary requires a registered persona for any press-release quote; itd-82 quotes an unnamed product thinker insteadrelated
- iss-2610262042571852The glossary reserves 'disposition' for the researcher's response to a reading item; itd-82 uses it for a drain run's per-issue outcome.related
- iss-2610262043542024itd-82 says merging the drain's pull requests is a human gate by design; itd-2610150602547271 says that in auto mode a ready pull request merges with no human steprelated
- iss-2610262044580425itd-146 files drain in the person's records group; itd-82 says drain joins the person's list only when itd-82 shipsrelated
- 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`related
- iss-2610262045199145itd-46 says quoted text alone creates a capture; itd-82 requires a remedy field on every new issuerelated
- itd-2609221009495079abcd's typed judgements can run through a decision adapter, measured in a lab before it decides anythingrelated
- itd-2610170634394993Headless agents in parallel coordinate through the shared remote, not conversationrelated
- itd-2610301750162657abcd's unattended work is spot-checked against specs and intentsbuilt on by
- itd-2610301850454860abcd records how each change landedbuilt on by
- spc-2609212015054359drain-ledger-triageimplemented by
mentions40
- 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-26092900000000162026-09-29 — Every new issue carries a remedy, and abcd's own automatic filers write one machine value when they have no fix (the product thinker's rulings BX3 and H12 of 2026-09-29, applied by lane remedyRequired of autonomous run A; partial of itd-82, whose decision 6 already required the field). BX3, verbatim: "REFUSE the filing; every new issue must carry remedy:". H12, verbatim: "'NO FIX YET' ALLOWED: automatic filers may write remedy 'none (filed automatically)'; the record is filed, drain skips it until a person writes a real remedy." As built: `capture` refuses a new issue with no remedy or a blank one, exit 2 and nothing written, naming `--remedy` and the machine value; the value is spelt once, `issueschema.MachineRemedy`, and written by every in-binary filer (the consistency pass, and an inbox report promoted without a remedy of its own, whose own remedy otherwise becomes the issue's); `abcd drain --dry-run` lists a record carrying it as ineligible, naming the automatic filer and the verb that answers it; and `capture remedy <iss-N> "<fix>"` writes or replaces the remedy on an open issue. A record filed before the rule carries none, stays readable and valid, and is listed as ineligible. The one-line capture friction that `commands/capture.md` and the principle `adversarial-review-scales-with-blast-radius` promised is tightened by the ruling, and both now say so. A person typing the machine value is REFUSED, by `capture --remedy` and by `capture remedy`, compared trimmed and case-folded: the value is useful only while it means that a machine filed the record, and a person has either a fix to name or the choice not to file; allowing it would let a hand-filed record pass as machine-filed and be skipped silently. A remedy chosen in an autonomous run cites its grounds, a prior-art or state-of-the-art check where the fix depends on outside practice (principle `prefer-sota`), in the capture's text; nothing checks that mechanically yet.
- dcn-26092900000000222026-09-29 — Every new issue carries a remedy, and abcd's own automatic filers write one machine value when they have no fix (the product thinker's rulings BX3 and H12 of 2026-09-29, applied by lane remedyRequired of autonomous run A; partial of itd-82, whose decision 6 already required the field). BX3, verbatim: "REFUSE the filing; every new issue must carry remedy:". H12, verbatim: "'NO FIX YET' ALLOWED: automatic filers may write remedy 'none (filed automatically)'; the record is filed, drain skips it until a person writes a real remedy." As built: `capture` refuses a new issue with no remedy or a blank one, exit 2 and nothing written, naming `--remedy` and the machine value; the value is spelt once, `issueschema.MachineRemedy`, and written by every in-binary filer (the consistency pass, and an inbox report promoted without a remedy of its own, whose own remedy otherwise becomes the issue's); `abcd drain --dry-run` lists a record carrying it as ineligible, naming the automatic filer and the verb that answers it; and `capture remedy <iss-N> "<fix>"` writes or replaces the remedy on an open issue. A record filed before the rule carries none, stays readable and valid, and is listed as ineligible. The one-line capture friction that `commands/capture.md` and the principle `adversarial-review-scales-with-blast-radius` promised is tightened by the ruling, and both now say so. A person typing the machine value is REFUSED, by `capture --remedy` and by `capture remedy`, compared trimmed and case-folded: the value is useful only while it means that a machine filed the record, and a person has either a fix to name or the choice not to file; allowing it would let a hand-filed record pass as machine-filed and be skipped silently. A remedy chosen in an autonomous run cites its grounds, a prior-art or state-of-the-art check where the fix depends on outside practice (principle `prefer-sota`), in the capture's text; nothing checks that mechanically yet.
- dcn-26093000000000122026-09-30 — The drain reads the drained repository's own eligibility record, which may loosen abcd's floors loudly, and it hands back every record still waiting on a person (the product thinker's rulings BX2 and H11 of 2026-09-29, applied by lane drainOwnRule of autonomous run A; partial of itd-82, whose spec stays open for the host judgement, the lane, the hand-back writes and the pace). BX2, verbatim: "the PROJECT MUST HOLD the eligibility decision in its own record (e.g. added at setup); drain refuses there until it does." H11, verbatim: "MAY LOOSEN abcd's floors (a project may let drain take major/critical and security issues). NOTE for the lane: make a loosened floor loud (drain --dry-run and the drain start name every floor the project loosened), and keep abcd's own repository at the stricter default." As built: the record is the one accepted decision record in the repository's `.abcd/development/decisions/adrs/` whose frontmatter carries `drain_categories` (an inline list, a subset of the fixable set), `drain_severities` (an inline list of severities), `drain_security` (`handback` or `take`) and `drain_remedy` (`required`, its only value, since the remedy is the brief a lane works from); abcd's own adr-2609291342092738 carries the strict baseline, which the binary also bundles as the measure a loosening is named against, and a test fails if abcd's record loosens anything. A repository without such a record, with one that is only proposed or superseded, with two accepted, or with one that misses, misspells, repeats or mis-values a field, is refused by `drain --dry-run` and bare `drain` alike, exit 2 and nothing written, never falling back to the baseline or a looser rule; widening the categories past the fixable set is refused as a decision by kind, which H11 does not name. Every loosened floor (`severity major`, `severity critical`, `security`) is named in the dry run's text, on stderr in both output modes, in `--json` as `loosened`, and in the start's refusal. `ahoy install` offers the baseline as an accepted record, written through the decision store's mint only on an answered yes; `--yes` skips it and reports `drain_rule.offered` under `optional_skipped`, as the routing offers are. The gap the remedy lanes found (50 of 54 dry-run-eligible records waiting on a ruling) is closed by BOTH hand-backs, each its own rule: a remedy opening "Waits on" (compared case-folded) is handed back as `waits-on-ruling`, because taking it would make the ruling the remedy waits on; and a record whose `deferred_after` names the current anchor tag is handed back as `deferred`, because a person carried it past this release and the waiver is that person's decision for the cycle. Both hold whatever the repository's record says. `capture defer` writes a deferral only onto a `major` or `critical` record, which H11 now lets a record take, but this ledger also carries hand-written deferrals on minor records (62 of the 231 open records on this branch are handed back as `deferred`), and the rule holds them back the same way. They are asked after the category and severity hand-backs, whose fix a ruling or a lapse would not change, and the ruling before the deferral, because it names which decision is owed; a record carrying both waits on both. The release tags are read only when an open record carries a deferral, and a failure to read them refuses the plan rather than letting a live deferral through. The threat is stated in the drain brief chapter: the record is a repository-authored file deciding what an unattended agent may do, so a contributor's pull request can loosen it; what guards it is that the record is committed history reviewed like code, a loosening is loud on every run, abcd's own repository keeps the baseline under a test, and the store is read inside the checkout so a symlink leaving it is refused.
- dcn-26093000000000172026-09-30 — The drain reads the drained repository's own eligibility record, which may loosen abcd's floors loudly, and it hands back every record still waiting on a person (the product thinker's rulings BX2 and H11 of 2026-09-29, applied by lane drainOwnRule of autonomous run A; partial of itd-82, whose spec stays open for the host judgement, the lane, the hand-back writes and the pace). BX2, verbatim: "the PROJECT MUST HOLD the eligibility decision in its own record (e.g. added at setup); drain refuses there until it does." H11, verbatim: "MAY LOOSEN abcd's floors (a project may let drain take major/critical and security issues). NOTE for the lane: make a loosened floor loud (drain --dry-run and the drain start name every floor the project loosened), and keep abcd's own repository at the stricter default." As built: the record is the one accepted decision record in the repository's `.abcd/development/decisions/adrs/` whose frontmatter carries `drain_categories` (an inline list, a subset of the fixable set), `drain_severities` (an inline list of severities), `drain_security` (`handback` or `take`) and `drain_remedy` (`required`, its only value, since the remedy is the brief a lane works from); abcd's own adr-2609291342092738 carries the strict baseline, which the binary also bundles as the measure a loosening is named against, and a test fails if abcd's record loosens anything. A repository without such a record, with one that is only proposed or superseded, with two accepted, or with one that misses, misspells, repeats or mis-values a field, is refused by `drain --dry-run` and bare `drain` alike, exit 2 and nothing written, never falling back to the baseline or a looser rule; widening the categories past the fixable set is refused as a decision by kind, which H11 does not name. Every loosened floor (`severity major`, `severity critical`, `security`) is named in the dry run's text, on stderr in both output modes, in `--json` as `loosened`, and in the start's refusal. `ahoy install` offers the baseline as an accepted record, written through the decision store's mint only on an answered yes; `--yes` skips it and reports `drain_rule.offered` under `optional_skipped`, as the routing offers are. The gap the remedy lanes found (50 of 54 dry-run-eligible records waiting on a ruling) is closed by BOTH hand-backs, each its own rule: a remedy opening "Waits on" (compared case-folded) is handed back as `waits-on-ruling`, because taking it would make the ruling the remedy waits on; and a record whose `deferred_after` names the current anchor tag is handed back as `deferred`, because a person carried it past this release and the waiver is that person's decision for the cycle. Both hold whatever the repository's record says. `capture defer` writes a deferral only onto a `major` or `critical` record, which H11 now lets a record take, but this ledger also carries hand-written deferrals on minor records (62 of the 231 open records on this branch are handed back as `deferred`), and the rule holds them back the same way. They are asked after the category and severity hand-backs, whose fix a ruling or a lapse would not change, and the ruling before the deferral, because it names which decision is owed; a record carrying both waits on both. The release tags are read only when an open record carries a deferral, and a failure to read them refuses the plan rather than letting a live deferral through. The threat is stated in the drain brief chapter: the record is a repository-authored file deciding what an unattended agent may do, so a contributor's pull request can loosen it; what guards it is that the record is committed history reviewed like code, a loosening is loud on every run, abcd's own repository keeps the baseline under a test, and the store is read inside the checkout so a symlink leaving it is refused.
- dcn-26101000000000032026-10-10 — Five points spc-2609212015054359 step 1 (scope 3, the host judgement before a drain's lane opens; itd-82 criterion 2) left open, settled by lane-1 of run-2610101724174688 in building it. (1) The host-pass shape is the loop's own: the drain writes one request, `.abcd/.work.local/run/drain-judgement.request.md`, records the issue and the sha256 of the remedy it shows in its state as `judging`, and opens nothing; the host writes strict JSON to `.abcd/.work.local/run/drain-judgement.json` and hands it back with a new flag on the bare verb, `abcd drain --judgement <file>`, which accepts only that path. The answer echoes `issue` and `remedy_sha256`, so an answer written for an earlier request, or over a remedy rewritten since, cannot be taken for this one; a remedy rewritten while the answer is out refuses the answer and the next move asks again. (2) The spec's yes/no carries a kind with a yes, `user-visible` or `trust-rule` (`trust-rule` when both, since it names the decision a person owes first), and a yes is routed exactly as a lane's hand-back of the same kind (promoted with `capture promote`, or flagged as needing a decision record with the reason as its question), with `from: "judgement"`; the intent's press release routes "an issue that needs a decision" by kind whatever found it, and a second routing for the judgement alone would be a design the record does not ask for. (3) The request tells the host to answer yes when the remedy does not let it tell, since the judgement guards a failure that runs one way and can only hand back. (4) An answer over an issue that left the eligible set since the request is recorded with `applied: false` and a note and decides nothing, which is criterion 2's "never makes an ineligible issue eligible" at the moment the answer arrives; a no is bound to the remedy's digest, so a rewritten remedy is asked about again before its lane opens. (5) `--max` keeps counting lanes, as it did: an issue the judgement hands back opened no lane, and the intent's "issues attempted" is read as lanes attempted. `abcd build <iss-N>`, a person's own start of one issue's lane, asks no judgement: the judgement is the drain's unattended pass, and the person typing the id is the judgement.
- 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.
- dcn-26101610441876732026-10-16 — The drain bundle's pre-pass answers (facilitator-on-delegation, drain-many-accounts): brief invariant 7's sentences naming what drain and implement step fetch and push are amended, in each building step that adds an act, to name the per-move fetch, the reservation before a judgement, the hand-back tombstone, the renewal of the lane in progress, the parked lanes' pull-request listing, the fork question and the sync's fetch of the default branch; brief invariant 19 gains a clause that an issue whose related_intents names an intent is not taken; a hand-back published on origin is the record of a judgement kept on the reservation, not a field classification written onto the issue, so invariant 19's re-derivation stands; and each member keeps both against its siblings itd-82, itd-2609091034175565 and itd-2609151838312703, building on the first two (grounds: the invariants are the record of what may touch the network and what a machine may take, so they move with the surface, as the principle spec-moves-with-the-surface requires).
- dcn-26110415501392642026-11-04 — The product thinker's rulings on the stop switch (itd-2611041549471712), the restart path (itd-2611041549461012) and the daily digest (itd-2611041549483696), at their filing. The stop lives on the machine that runs the work, in a shared folder chosen as an option at setup (/Users/Shared on macOS and its counterpart on Linux), on a machine no other person shares; the store ownership rule admits this one exception, and a stop that reaches the machine over a private network or through a cloud adapter is later work. The daily digest is a dated file in that same folder: the digest draft asked that one choice hold for both, and an unshared machine answers the privacy cost, so this followed from the ruling above and no question was put. A stop lets an agent in a call finish that call, then stops. A stop releases the run's claims on its work, so a peer may take it up. The drain's plugin page stays an agent's command and its terminal form stays the person's way to drive it; this changes the 2026-10-09 ruling on itd-2610090831227812 that the drain joins the person's menu when itd-82 ships.
- iss-1launch phase ownership contradicts across the record: phase-1-ahoy.md and build-sequence.md make install plus launch the first milestone, but 04-surfaces/04-launch.md says full launch builds in Phase 5. ADR-bound.
- iss-80Record id allocators (itd-N, spc-N, iss-N) are branch-local: parallel agents on separate branches each scan for max+1 and mint the SAME id. Two intents both claimed itd-82 and both merged to main (PRs #46, #47). The iss-N allocator hit the same class before (iss-77 collision, manually renumbered to iss-79; class recorded as iss-74). Resolving a collision forces a renumber, which breaks the record's stated 'ids are capture-stable, never renumbered' invariant -- so the minting scheme, not the renumber, is the defect. This capture is the MINTING half: a collision-free allocation scheme is needed (forge-minted / random-suffix / timestamp / reserve-registry -- SOTA under research). The DETECTION half is armed separately: intent_lifecycle now blocks duplicate intent ids.
- iss-381The delivery pipeline abcd runs on itself is hand-orchestrated and should be deterministic. This session hand-ran the full sequence: SOTA research -> itd-84 decomposition -> draft intent -> TWO independent adversarial reviews (design/feasibility + record-discipline) -> planning interview -> plan+spec -> test-first implementation -> TWO more adversarial reviews of the diff (ruthless + security) -> apply/refute findings -> gates (preflight, record-lint) -> atomic commits + PR -> resolve the ledger issue with provenance -> seed the deferred items back into the ledger. Every hand-off, gate, and review-pair was driven by judgement, not a deterministic harness. The individual stages already have records — itd-83 (the review bar fires itself), itd-84 (decompose before filing), itd-82 (ledger drain), itd-78 (intent-dependency graph) — but ALL are drafts/staged and NONE composes the others into one orchestrated pipeline. Honest constraint (loud-staging): the pipeline cannot be MORE automated than its stages, so full automation waits on those disciplines shipping; but the deterministic SCAFFOLD — the fixed stage order, the mandatory review-pair gate before plan and before merge, the hand-offs to the host for the human-in-the-loop steps, the ledger-seed-the-deferrals close-out — can be built now as a workflow/routine (itd-107 versioned-routine-template is the nearest delivery vehicle). Routing note: this is a big, cross-record idea, not a decomposed intent — it likely needs /abcd:ideate then itd-84 decomposition before it becomes filable, and it may reshape rather than duplicate itd-83/82/78.
- 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-2609292352131344An unknown flag under --json is refused with no JSON envelope on stdout: abcd ahoy --bogus --json (and any verb) prints cobra's 'abcd: unknown flag: --bogus' on stderr, exits 2 and leaves stdout empty, because the flag parse fails before the persistent --json flag is read, so Run() sees json=false. The --json contract ('a refusal is a {"abcd":"error",...} object on stdout too') is broken for every flag-parse refusal; a machine caller passing an old flag (abcd version --check --json after v0.12.0) reads nothing.
- iss-2610090642376032A pull request the implement loop lands can leave the merge queue silently. Twice it sat CLEAN with auto-merge armed and never entered the queue (#837, #844; the enqueue mutation then failed with a forge error and only toggling auto-merge queued it), and four times across this run and the v0.13.2 cut a failed merge-group check (the macOS job cap, or a flaky test) dropped it from the queue (#839, #854, #867, #869) with nothing re-queuing it. The loop arms auto-merge and walks away, so a run that trusts it stalls unseen; one drop cost about 45 minutes in this run.
- iss-2610090642385898A drain has no way to hold back one issue or one package for now. A peer's verbal hold is invisible to it (only implement claim or a branch carrying the record keeps a lane off an issue), and while a fix is in flight in one package the only way to keep the drain out of that package is to stop the whole drain, so this run drove a colliding lane anyway and let whichever landed second rebase.
- iss-2610090936460789A lane's worktree and branch are cleaned up only by a final 'abcd implement step' run after its pull request merges, and nothing runs it: the land stage waits (exit 3) until the merge, the drain records the lane as 'pull-request' and moves on, and no hook, drain move or schedule comes back. On 2026-10-09 all 18 overnight drain lanes sat merged but still at the land stage, their worktrees in the store and their runs counted as building on the status board (iss-2610090824041378). The step was also refused when it was finally run, because the run's pacing window had elapsed ('nothing before 14:36Z'), so even finishing a merged lane waits hours.
- 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-2610101610446956abcd intent ready reported itd-82 READY while abcd build itd-82 refused it at check (open_questions) for two open questions in its record: the readiness verb does not run the check the build makes, so READY does not mean the build will start.
- 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-2610120137339391Two lane-discard paths in the implement loop delete a lane's branch with `git update-ref -d` without first checking whether another worktree has that branch checked out: `abcd implement step --discard <lane>` (internal/core/implement/loop/hold.go, the update-ref after removeLaneWorktree) and a handed-back receipt's discard (internal/core/implement/loop/receipt.go, discardReceiptLane's update-ref). git refuses `git branch -d` for a checked-out branch but not `update-ref -d`, so if the person (or a peer session) has checked the lane's branch out in a worktree other than the lane's own, that worktree is left on an unborn branch with its HEAD dangling. The same gap in landMerged was found by the security review of itd-82 step 3 (run-2610101724174688 lane-3) and closed there by a branchCheckedOutElsewhere check in land.go; these two siblings were outside that step's scope.
- iss-2610161639025702Preflight throughput sets the pace of all work on this machine, and nothing keeps it in bounds. On 2026-10-16 the load gate admitted one make preflight at a time, each took about 40 minutes, and about 20 waiters were queued across seven sessions. Every reviewed branch had its own preflight, so a seven-step feature needed seven full runs, and records-only branches paid the same full cost as code. Sessions waited hours, and the queue's ordering after the reboot starved late waiters (fixed separately on fix/load-queue-start-order). No rule tells sessions to batch work, and no part of preflight scales its cost to what a change touches.
- iss-2610241412028278A session holding several push-ready branches drives each through preflight and landing by hand: on 2026-10-24 this session had four (docs/itd-82-landed-steps, fix/landing-marks-step-landed, fix/lapsed-release-hold, fix/name-ungated-tests), and with the rule relayed that day of one preflight per session at a time it started each preflight itself, watched for the 2-hour gate timeout (three runs timed out and had to be restarted by hand), ran 'abcd land' after each that passed, and kept the order in its own notes and wake-ups. No verb holds a session's queue of branches, so the chaining, the re-queue after a timeout and the landing all depend on the session staying alive and remembering.
- iss-2610260459250412abcd build, run in a fresh store worktree of a checkout abcd manages, refuses at the state stage because .abcd/.work.local/ is not a directory there, and its remedy names abcd ahoy, which installs abcd rather than making the tier. AGENTS.md sends every concurrent session to its own store worktree made with a plain git worktree add, which never carries the gitignored tier, so every build or drain started that way is refused until the tier is made by hand with mkdir. Seen on 2026-10-26 restarting build itd-82 from ~/.abcd.noindex/worktrees/<root-sha>/itd-82-build.
- iss-2610261837062463A build's reservation lapses while the host waits on an agent, because only implement step renews it (renewReservation in internal/core/implement/loop/reservation.go): build itd-82's run-2610260500113538 reserved itd-82 until 07:00Z, its implementer then held the implement stage for about thirteen hours under the shared machine's load gate, and no step ran to renew it. Revived after the lapse, the reservation is judged like a new one (crowd.go), and a build's automated act backs off on any overlap with the same person's other copy, so the renewal was refused for loop.go, in the footprint of iss-2610142134353345, live from the main checkout. The run goes on to validation holding no reservation, and the land fence will stop it, though nothing of the other record's work touches the lane's files.
- 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.
- iss-2610262037594127The menu intent keeps drain on the agents' list until itd-82 ships; the drain chapter calls drain the verb a person types, with itd-82 still open.
- iss-2610270934594826abcd drain --unskip names the wrong thing when the issue it is given is held back by a path: with internal/core held back, --unskip iss-A for an issue whose found_at lies in internal/core says iss-A was not held back, while the same move lists iss-A among the held-back issues (applySkips in internal/core/implement/loop/drain_skip.go). The command that would lift the hold, --unskip internal/core, is still shown, so nothing is lost, but the summary line is false. Found by the round-2 ruthless review of itd-82 lane-1 (build/run-2610260500113538-lane-1).
- iss-2610270935000288abcd drain --unskip given alone, on a drain that has ended, begins a new drain: the skip list is applied after a finished drain's state is reset, so a command meant only to lift a hold starts a drain run the person did not ask for. Found by the round-2 ruthless review of itd-82 lane-1 (build/run-2610260500113538-lane-1).
- iss-2611031146263490A merge-queue drop's cause is diagnosed and filed by hand: on 2026-11-03 five pull requests left the queue, three at the ci-watchdog deadline and two on timing-bound tests, and each time the coordinator read the run's failed log, told a watchdog stop from a flaky test, captured the flake, launched its fix and re-armed. itd-82's queue watch re-arms and names the dropped check, but a failing test does not become a ledger record a drain lane could fix.
- iss-2611040337065203The brief lags delivery and a release cut pays for it: the v0.17.0 cut's docs review went through at least five rounds on 2026-11-04, returning 10, 14, 1 and 9 false brief sentences, nearly all in one chapter, 35-drain.md, because itd-82 shipped with a review that only had to exist at its close. Each round costs a reviewer and a fixer while every merge is held by the release reservation.
- itd-29Domain Experts Run A Spec Autonomously And Recover From Anything Without Touching Git
- itd-97The Facilitator Is a Mode, Not a Person
- itd-116Validated GitHub Issues Become Ledger Entries Without Retyping
- itd-2610090831227812A person who types `/abcd:` sees the commands people use
- itd-2610161016391855A stuck drain lane hands itself back
- itd-2611031342000215The status line shows each role what it needs, in a table a repository can tune
- spc-24an-issue-graduates-into-an-intent-without-retyping-abcd-capt
- spc-2609202134338445one-verb-takes-a-single-intent-from-ready-to-delivered-witho
- spc-2610100613109045a-person-who-types-abcd-sees-only-the-commands-people-use