From 3d887276f389f924c7e5fba34221d8fe8d2af9de Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:26:42 +0100 Subject: [PATCH 01/10] chore: carry the PT's compaction-survival issue onto the checklist planning branch Copied byte-identical from chore/captures-ae-1022b, where it was filed uncommitted on 2026-10-22; its remedy names the checklist intent this branch plans. Assisted-by: Claude:claude-opus-5-5 --- ...026223032-autonomous-work-does-not-reliably.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md diff --git a/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md b/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md new file mode 100644 index 0000000000..8b326a66c8 --- /dev/null +++ b/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md @@ -0,0 +1,15 @@ +--- +schema_version: 1 +id: "iss-2610222026223032" +slug: "autonomous-work-does-not-reliably" +severity: "minor" +category: "future-work-seed" +source: "user-observation" +found_during: "PT asked for it during the 2026-10-22 crash recovery" +origin: researcher-authored +production_mode: hand-written +found_at: "hooks" +remedy: "each abcd session keeps its to-do list as a file in the local tier (.abcd/.work.local/, one per session), written as it works; abcd's PreCompact hook makes the session bring it up to date before a compaction and the SessionStart and post-compaction hooks inject it back, so the session resumes from the list; deliver it as the first slice of the session checklist intent itd-2610170638184486" +--- + +Autonomous work does not reliably survive the host's automatic compaction: after a compaction a long-running session keeps only a summary, and the work it was in the middle of (which lanes it dispatched, which PRs it is watching, what it promised a peer, the next step of a multi-step plan) survives only if the session happened to write it down. The person wants every autonomous session's work to survive auto-compaction, for example through a to-do list kept in the checkout's local tier (.abcd/.work.local) that the session reads back after a compaction. Related: the draft intent every abcd session keeps a checklist (itd-2610170638184486), being planned on 2026-10-22. From e37cfa1832c27eea352c2911edff9d745f4a7f4f Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:26:44 +0100 Subject: [PATCH 02/10] chore: record the product thinker's 2026-10-22 answers on the coordination and checklist drafts Nine decisions, restated: unattended runs only where the person's forge login is absent; the short status line now, full uploads later; agents' own App tokens ship with the coordination; the model budget is shared across sessions (a typed reversal of dcn-2610180720379757 and the 2026-10-10 'each abcd run' answer); the phone and shared copies split off with no question put; the blocker list at the moment the person leaves; overnight trade-offs decided and listed; and two write-ups owed, a credential trust rule and a waiting principle. Assisted-by: Claude:claude-opus-5-5 --- ...0222025104969-unattended-agent-runs-happen-only-where.md | 6 ++++++ ...222025114805-only-the-short-status-line-is-shared-for.md | 6 ++++++ ...10222025116835-agents-own-limited-access-ships-in-the.md | 6 ++++++ ...cn-2610222025116937-the-model-budget-is-shared-across.md | 6 ++++++ ...0222025212338-the-phone-and-the-shared-copies-of-each.md | 6 ++++++ ...n-2610222025217495-sessions-list-what-could-stop-them.md | 6 ++++++ ...222025224993-a-checklist-never-holds-a-password-token.md | 6 ++++++ ...10222025225277-while-the-person-is-away-sessions-keep.md | 6 ++++++ ...0222025230680-waiting-holds-up-one-item-never-a-whole.md | 6 ++++++ 9 files changed, 54 insertions(+) create mode 100644 .abcd/work/decisions/dcn-2610222025104969-unattended-agent-runs-happen-only-where.md create mode 100644 .abcd/work/decisions/dcn-2610222025114805-only-the-short-status-line-is-shared-for.md create mode 100644 .abcd/work/decisions/dcn-2610222025116835-agents-own-limited-access-ships-in-the.md create mode 100644 .abcd/work/decisions/dcn-2610222025116937-the-model-budget-is-shared-across.md create mode 100644 .abcd/work/decisions/dcn-2610222025212338-the-phone-and-the-shared-copies-of-each.md create mode 100644 .abcd/work/decisions/dcn-2610222025217495-sessions-list-what-could-stop-them.md create mode 100644 .abcd/work/decisions/dcn-2610222025224993-a-checklist-never-holds-a-password-token.md create mode 100644 .abcd/work/decisions/dcn-2610222025225277-while-the-person-is-away-sessions-keep.md create mode 100644 .abcd/work/decisions/dcn-2610222025230680-waiting-holds-up-one-item-never-a-whole.md diff --git a/.abcd/work/decisions/dcn-2610222025104969-unattended-agent-runs-happen-only-where.md b/.abcd/work/decisions/dcn-2610222025104969-unattended-agent-runs-happen-only-where.md new file mode 100644 index 0000000000..c5ae3119e3 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025104969-unattended-agent-runs-happen-only-where.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025104969 +slug: unattended-agent-runs-happen-only-where +date: 2026-10-22 +--- +2026-10-22 — Unattended agent runs happen only where the person's forge login is absent (product thinker's ruling of 2026-10-22, itd-2610170634394993 C1): an agent left to run overnight or while the person is away runs in a cloud session, a container or a separate user account, where the person's saved forge login cannot be reached, so the promise that agents cannot use the person's admin rights is a guarantee, set up once, rather than a caveat that the login is still within reach on their own computer; asked after an example, this was chosen over saying the limit plainly and leaving the login reachable diff --git a/.abcd/work/decisions/dcn-2610222025114805-only-the-short-status-line-is-shared-for.md b/.abcd/work/decisions/dcn-2610222025114805-only-the-short-status-line-is-shared-for.md new file mode 100644 index 0000000000..dfb17f5571 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025114805-only-the-short-status-line-is-shared-for.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025114805 +slug: only-the-short-status-line-is-shared-for +date: 2026-10-22 +--- +2026-10-22 — Only the short status line is shared for now; full checklist uploads come later (product thinker's ruling of 2026-10-22, itd-2610170634394993 C2): this intent publishes one short line per session and no full checklist, public or private; uploading a session's whole checklist so another computer could resume it is later work. This settles the two rulings of 2026-10-17 that disagreed, in favour of later, and keeps the intent's decision 3 as written diff --git a/.abcd/work/decisions/dcn-2610222025116835-agents-own-limited-access-ships-in-the.md b/.abcd/work/decisions/dcn-2610222025116835-agents-own-limited-access-ships-in-the.md new file mode 100644 index 0000000000..69cc2ee21b --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025116835-agents-own-limited-access-ships-in-the.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025116835 +slug: agents-own-limited-access-ships-in-the +date: 2026-10-22 +--- +2026-10-22 — Agents' own limited access ships in the same release as the coordination (product thinker's ruling of 2026-10-22, itd-2610170634394993 C3): autonomous sessions get short-lived tokens for one job from abcd's GitHub App from the first release of this intent, not in a later piece; the person registers the App once in the browser before that release is usable, a step only they can take. This is what makes C1 work: a run kept away from the person's login needs those tokens to push diff --git a/.abcd/work/decisions/dcn-2610222025116937-the-model-budget-is-shared-across.md b/.abcd/work/decisions/dcn-2610222025116937-the-model-budget-is-shared-across.md new file mode 100644 index 0000000000..f15cfc7cff --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025116937-the-model-budget-is-shared-across.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025116937 +slug: the-model-budget-is-shared-across +date: 2026-10-22 +--- +2026-10-22 — The model budget is shared across sessions, not a fixed count for each abcd run (product thinker's ruling of 2026-10-22, itd-2610170634394993 C-confirm; a typed reversal: reverses dcn-2610180720379757 and the product thinker's 2026-10-10 answer 'each abcd run' in itd-2610100704270254): the sub-agent budget the repository sets is one pool that every abcd session draws on, so the draft's press release keeps 'a share of the model budget' and is not narrowed; the product thinker accepted that one busy session can slow the others. dcn-2610180720379757's narrowing of the press release and the model-budget scope condition is withdrawn diff --git a/.abcd/work/decisions/dcn-2610222025212338-the-phone-and-the-shared-copies-of-each.md b/.abcd/work/decisions/dcn-2610222025212338-the-phone-and-the-shared-copies-of-each.md new file mode 100644 index 0000000000..1e511725a3 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025212338-the-phone-and-the-shared-copies-of-each.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025212338 +slug: the-phone-and-the-shared-copies-of-each +date: 2026-10-22 +--- +2026-10-22 — The phone and the shared copies of each checklist become their own intent in the bundle, and no question was put (facilitator, recorded on the product thinker's 2026-10-22 ruling C2, itd-2610170638184486 K1): once the product thinker chose to upload full checklists later, the shared copies are wanted later, and the phone needs them, so keeping them inside the checklist intent would hold its sooner parts back for nothing; splitting them off is the only answer consistent with the record, so asking would have offered a choice already made. The checklist intent keeps the on-disk checklist, waits, wakes and the blocker check diff --git a/.abcd/work/decisions/dcn-2610222025217495-sessions-list-what-could-stop-them.md b/.abcd/work/decisions/dcn-2610222025217495-sessions-list-what-could-stop-them.md new file mode 100644 index 0000000000..8b25abb519 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025217495-sessions-list-what-could-stop-them.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025217495 +slug: sessions-list-what-could-stop-them +date: 2026-10-22 +--- +2026-10-22 — Sessions list what could stop them overnight when the person says they are away, and the unattended run starts once the person has answered the list (product thinker's ruling of 2026-10-22, itd-2610170638184486 K2): the person has nothing to remember to say beforehand, at the cost of a few minutes' wait before leaving; chosen over a warning ahead of leaving and over every session keeping its list current all day diff --git a/.abcd/work/decisions/dcn-2610222025224993-a-checklist-never-holds-a-password-token.md b/.abcd/work/decisions/dcn-2610222025224993-a-checklist-never-holds-a-password-token.md new file mode 100644 index 0000000000..112552de95 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025224993-a-checklist-never-holds-a-password-token.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025224993 +slug: a-checklist-never-holds-a-password-token +date: 2026-10-22 +--- +2026-10-22 — A checklist never holds a password, token or anything that retrieves one, only its kind, scope and expiry, and this is written up as its own trust rule (product thinker's ruling of 2026-10-22, itd-2610170638184486 K-split i): routed through the decomposition table as a trust rule, so it becomes an ADR and a clause of the brief's invariants rather than a line inside the checklist intent diff --git a/.abcd/work/decisions/dcn-2610222025225277-while-the-person-is-away-sessions-keep.md b/.abcd/work/decisions/dcn-2610222025225277-while-the-person-is-away-sessions-keep.md new file mode 100644 index 0000000000..08fe371042 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025225277-while-the-person-is-away-sessions-keep.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025225277 +slug: while-the-person-is-away-sessions-keep +date: 2026-10-22 +--- +2026-10-22 — While the person is away, sessions keep deciding trade-offs of security, speed and compatibility, and list each with how to undo it (product thinker's ruling of 2026-10-22, itd-2610170638184486 K-confirm): this confirms the 2026-10-17 ruling that nothing waits on the person overnight over the earlier framing that such trade-offs are raised with the technical facilitator; the list is shown on the person's return diff --git a/.abcd/work/decisions/dcn-2610222025230680-waiting-holds-up-one-item-never-a-whole.md b/.abcd/work/decisions/dcn-2610222025230680-waiting-holds-up-one-item-never-a-whole.md new file mode 100644 index 0000000000..8ffc013120 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222025230680-waiting-holds-up-one-item-never-a-whole.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222025230680 +slug: waiting-holds-up-one-item-never-a-whole +date: 2026-10-22 +--- +2026-10-22 — Waiting holds up one item, never a whole session, and this is written up as a standing principle (product thinker's ruling of 2026-10-22, itd-2610170638184486 K-split ii): routed through the decomposition table as a stance, so it becomes a principle under .abcd/development/principles/; the principle states the stance behind dcn-2610180905291297, that a declared dependency holds only the landing From fc765b8034111527d58db0de1f215998d0af9c33 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:33:19 +0100 Subject: [PATCH 03/10] docs: file the write-ups and the split the 2026-10-22 answers call for The credential rule becomes adr-2610222026265719 and brief invariant 24, and waiting-holds-one-item becomes a principle, both on the product thinker's ruling. The coordination intent's deferred trust rule and its stance are filed too: adr-2610222026262980 (proposed until the plan's sign-off) and the principle coordination-lives-on-the-shared-remote. The phone and the shared copies move to their own draft, itd-2610222029228127. Both drafts carry the night's answers. iss-2610222026223032 is linked to the checklist intent and becomes the first of its two proposed specs. The budget draft notes the reversal of its "each abcd run" answer. The calibration note grades the routing as confirmed. No intent is planned. The criteria walk goes to the product thinker first. Refs: iss-2610222026223032 Assisted-by: Claude:claude-opus-5-5 --- .../brief/02-constraints/03-invariants.md | 1 + .../brief/06-delivery/03-out-of-scope.md | 1 + ...coordination-between-headless-agents-is.md | 103 ++++++++++++++ ...-session-s-checklist-names-a-credential.md | 98 +++++++++++++ .abcd/development/decisions/adrs/README.md | 2 + ...0254-an-abcd-run-works-out-how-many-sub.md | 1 + ...394993-several-headless-agents-work-one.md | 36 +++-- ...every-abcd-session-keeps-a-checklist-on.md | 131 ++++++++++++++---- ...7-a-person-away-from-their-computer-can.md | 105 ++++++++++++++ ...coordination-lives-on-the-shared-remote.md | 45 ++++++ .../waiting-holds-one-item-never-a-session.md | 48 +++++++ .../2026-08-15-decomposition-calibration.md | 27 ++++ ...abcd-looks-for-the-person-s-forge-login.md | 6 + ...23032-autonomous-work-does-not-reliably.md | 1 + 14 files changed, 567 insertions(+), 38 deletions(-) create mode 100644 .abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md create mode 100644 .abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md create mode 100644 .abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md create mode 100644 .abcd/development/principles/coordination-lives-on-the-shared-remote.md create mode 100644 .abcd/development/principles/waiting-holds-one-item-never-a-session.md create mode 100644 .abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md diff --git a/.abcd/development/brief/02-constraints/03-invariants.md b/.abcd/development/brief/02-constraints/03-invariants.md index 5e13616426..77de826d95 100644 --- a/.abcd/development/brief/02-constraints/03-invariants.md +++ b/.abcd/development/brief/02-constraints/03-invariants.md @@ -177,3 +177,4 @@ The following are non-negotiable invariants — any architectural choice that vi 22. **A private advisory stays private until it is published, and abcd acts on GitHub for it only on an approval per act** — a private security advisory's record never enters the public ledger while the advisory is unpublished: it lives only in the embargo store, a detached worktree in the machine-scoped worktree store that is never committed, and every verb that touches the store refuses one that is on a branch, tracks a private record, or lies inside the checkout; nothing public carries an unpublished advisory's id, its reserved CVE id, its title or the location of the private records, and the outbound check refuses an artefact that does, naming where without repeating it; and publishing or closing an advisory on GitHub happens only on the person's explicit approval of exactly the plan or text shown, one approval per act, never supplied by an agent on its own, refining [adr-44](../../decisions/adrs/0044-remote-mutation-and-caller-identity.md)'s confirmed remote write. Ruled by [adr-2610120558328615](../../decisions/adrs/2610120558328615-a-private-advisory-s-record-never-enters.md), accepted by the product thinker on 2026-10-12. Consumed by itd-2610112239022766 (`abcd embargo`). 23. **No work-in-progress branch reaches the default branch, and a receipt-less push is admitted only for a host connector's declared work-in-progress prefix** — a cloud session abcd starts pushes its work, checked or not, only to ``, where the prefix is declared by that host's connector (never named in the core or the hook), ends in the path segment `abcd-wip/` with at most one segment before it, and is one entry in the pre-push hook's declared list of receipt-less prefixes, with its own commit shape (a valid record id after the prefix, every pushed commit passing the attribution gate) and this rule as its record; every other branch still needs a preflight receipt. A required check on the default branch's ruleset refuses any pull request or merge-queue entry whose source branch holds the segment `abcd-wip` or whose diff adds a cloud report file, and the work reaches the default branch only through the person's own collect, which re-runs preflight on a copy on their computer and mints the receipt there; no receipt minted in the cloud is trusted. The hook is not the boundary (`--no-verify` skips it); the required check, the ruleset and the collect are. Ruled by [adr-2610120937435856](../../decisions/adrs/2610120937435856-a-cloud-session-s-unchecked-work-goes.md), decided by the technical facilitator on the product thinker's delegation of 2026-10-12. Consumed by itd-2610120738075547 (work handed to cloud sessions). +24. **A session's checklist names a credential only by its kind, scope and expiry, never its value or anything that retrieves it** — the checklist a session keeps on disk, which is re-injected at every start and after every compaction and read by every later session in that working copy, records a credential it needs only as three typed fields: its kind, what it reaches, and when it expires; it never holds a secret, a part, hash or encoding of one, or a pointer that hands one to its reader (an environment variable's value, a key file's path, a keychain item's name, a command that prints a token). The checklist writer runs every field through abcd's native secret scanner and refuses a write that matches, naming the field and the pattern kind without echoing it, and the reader refuses to re-inject a file that matches, falling back to the last good copy; the scanner catches the shapes it knows, and the rule binds whatever it misses. Ruled by [adr-2610222026265719](../../decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md), accepted by the product thinker's ruling of 2026-10-22. Consumed by itd-2610170638184486 (every session keeps a checklist). diff --git a/.abcd/development/brief/06-delivery/03-out-of-scope.md b/.abcd/development/brief/06-delivery/03-out-of-scope.md index e2aa2a905b..a80fb6c901 100644 --- a/.abcd/development/brief/06-delivery/03-out-of-scope.md +++ b/.abcd/development/brief/06-delivery/03-out-of-scope.md @@ -149,6 +149,7 @@ That is what keeps "not hand-counted" true after the day it was written. - `itd-2610161710316538` — Drains and builds a person's session drives report how isolated they were - `itd-2610170634394993` — Headless agents in parallel coordinate through the shared remote, not conversation - `itd-2610170638184486` — Every session keeps a checklist on disk, so no run stalls and no stall spreads +- `itd-2610222029228127` — Every session's checklist reaches a shared copy and the phone **Later-phase items with no intent id.** These four were written into the brief diff --git a/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md b/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md new file mode 100644 index 0000000000..20152ccbb0 --- /dev/null +++ b/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md @@ -0,0 +1,103 @@ +--- +id: adr-2610222026262980 +slug: coordination-between-headless-agents-is +status: proposed +date: 2026-10-22 +supersedes: null +superseded_by: null +related_intents: [itd-2610170634394993, itd-2609091034175565, itd-2609151838312703] +related_rfcs: [] +related_adrs: [adr-2610222026265719] +--- + +# ADR-2610222026262980: Coordination between headless agents is bounded by deadlines, fenced by the claim's tip, version-checked, and done under each session's own credential + +## Context + +The coordination intent (itd-2610170634394993) replaces the messages +sessions sent each other on 2026-10-16 with records on the shared remote that +any session reads and acts on. Its decomposition, confirmed by the product +thinker on 2026-10-17 (the calibration note under that date), routed four +rules to an ADR rather than to the intent, because each one is a boundary +other work must not cross, not a feature: + +- That evening the load gate deadlocked between two builds of its own queue + rule (iss-2610161634043273) and no wait timed out for eight hours. +- A claim taken over from a dead holder can be acted on by that holder if it + comes back, unless a later act re-checks that the claim is still its own + (Chubby's sequencers, Kubernetes leases; the research note + `2026-10-17-parallel-headless-agent-coordination.md`). +- Two sessions on different abcd versions read each other's records; an + older reader that guesses at a newer record can wait on it forever. +- Every session on the machine used the person's own forge login, which + holds admin rights, so any session could change a setting meant for the + person alone (the reservation cap, captured on 2026-10-17). The product + thinker ruled the same day that autonomous sessions never work under that + login, and on 2026-10-22 that unattended runs happen only where the login + is absent (C1) and that each session's own App token ships with the + coordination (C3). + +The planning prep of 2026-10-18 settled the mechanics each rule rests on +(dcn-2610180720366282, +dcn-2610180720397970, dcn-2610180720379765, dcn-2610180723332655): the generation number is the claim +branch's tip, every expiry is derived when the records are read, and the +credential is a GitHub App token minted for the person. + +## Decision + +Four rules bind every coordination act abcd makes, by any verb, in any +session: + +1. **Every wait has a deadline.** A wait on a slot, a dependency, a claim, a + merge or another session ends by a stated time, and a wait that reaches it + ends in a refusal that names what it waited on and for how long. No wait + is open-ended, and no deadline depends on a process staying alive: each is + derived from the records when they are read. +2. **A claim is fenced by its tip.** Every push, queue entry or tag made + under a claim re-checks that the claim's branch tip is still the one the + act started from. On a push the forge enforces it (a push that is not a + fast-forward is refused); for a queue entry or a tag abcd re-reads the tip + and refuses the act if it moved. A claim whose holder stopped renewing is + retaken only after its expiry plus a fixed delay. +3. **A reader refuses a coordination record newer than it knows.** From the + release that adds a version to the coordination records, a reader meeting + a version above its own refuses the record with a message naming both + versions, and never waits on it or guesses its meaning. Readers released + before that ignore the field, which is why the rule starts there. +4. **An autonomous session never acts under the person's own forge login.** + It pushes, opens pull requests and reads the forge with a short-lived + token abcd's GitHub App mints for that session, limited to the repository + and the acts its work needs. An unattended run starts only where the + person's saved login cannot be reached (a cloud session, a container or a + separate user account); abcd checks the known places a login is kept + before starting one, and refuses to start, naming where it found one. + +## Alternatives Considered + +- **These four as one trust rule (chosen).** They are the four ways + coordination by records fails silently (a wait that never ends, a stale + holder acting, a reader guessing, a session holding more than it needs), + and each is checkable at the act. +- **A coordinator process holding locks (etcd, a Temporal server).** + Rejected in the state-of-the-art pass: out of scale for one repository, + and a process that must stay alive is the failure 2026-10-16 showed. +- **Leave deadlines and fencing to each verb.** Rejected: the load gate's + eight-hour deadlock was exactly a verb that had none. +- **Say plainly that the person's login is still within reach, rather than + run where it is absent.** Offered to the product thinker on 2026-10-22 and + not chosen: a caveat is not a guarantee. + +## Consequences + +- Every coordination verb (land, build, drain, status, reserve, the load + gate) carries a stated deadline and a named refusal at its end; a test per + verb watches it end. +- Queueing and tagging gain a tip re-check; a session whose claim was taken + over cannot land under it. +- The coordination records gain a version field in the release that ships + this; the refusal binds from then on. +- Unattended runs need a setup the person does once: the App registered in + the browser (C3), and a place to run without their login. abcd states that + as a step only the person can take. +- Stated in the brief's invariants once this ADR is accepted, which happens + with the product thinker's sign-off of the intent's plan. diff --git a/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md b/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md new file mode 100644 index 0000000000..2fa6896634 --- /dev/null +++ b/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md @@ -0,0 +1,98 @@ +--- +id: adr-2610222026265719 +slug: a-session-s-checklist-names-a-credential +status: accepted +date: 2026-10-22 +supersedes: null +superseded_by: null +related_intents: [itd-2610170638184486, itd-2610170634394993, itd-2610180912189125] +related_rfcs: [] +related_adrs: [adr-2610222026262980] +--- + +# ADR-2610222026265719: A session's checklist names a credential by its kind, scope and expiry, never its value or anything that retrieves it + +## Context + +The checklist intent (itd-2610170638184486) gives every abcd session a file +on disk saying what it may decide, what it waits on and what it owns. Its +pre-run blocker sweep asks each session for a credential that is missing or +will expire before the person returns, and that answer is written into the +checklist. The coordination intent (itd-2610170634394993) gives every +autonomous session a short-lived token of its own from abcd's GitHub App +(its decision of 2026-10-22, C3), so a session knows of a credential that +matters to its night. + +The checklist is the most-read file a session has. It is re-injected into +the model's context at every start and after every compaction, written by +the agent, read back by any later session in that working copy, summarised +into the person's return lists, and, in later work, uploaded to a private +shared copy and optionally published (the checklist decision of 2026-10-22, +K1). Anything written there is copied into every one of those places. +Agent-written state is also the least careful writer in the system: a model +told to record "the token that expires at 03:00" will as readily paste the +token. + +On 2026-10-18 the agent routing the draft's decomposition proposed this as a +separate trust rule; on 2026-10-22 the product thinker ruled that it be +written up as one (K-split i). Through the decomposition table a trust rule +becomes an ADR and a clause of the brief's invariants. + +## Decision + +**A checklist records a credential only by its kind, its scope and its +expiry.** Kind is what sort of credential it is (an App installation token, +a model-service key, a signing key); scope is what it reaches (a repository +and the acts allowed, a service); expiry is when it stops working, or that it +has none. Nothing else about it is written. + +**Never its value, and never anything that retrieves it.** A checklist holds +no secret, no part of one, no hash or encoding of one, and no pointer that +would hand one to whoever reads the file: not an environment variable's +value, not a path to a key file, not a keychain item's name and account, not +a command that prints a token. The person, or the coordination intent's +issuing step, gets the credential; the checklist only says one is needed and +by when. + +**The write refuses, and the read refuses.** abcd's checklist writer checks +every field it writes with abcd's native secret scanner +(`internal/adapter/scanner`), the one capture, decide and the claim writer +already run, and refuses a write that carries a secret +pattern, naming the field and the pattern kind and never echoing the match; +the session's last good checklist stays in place. A checklist read from disk +that fails the same check is not re-injected: the reader reports the field +and falls back to the last good copy, as it does for a file it cannot parse. +The check is a pattern scan, so it catches the shapes it knows; the rule +binds whatever it misses, and the fields that name a credential are typed +(kind, scope and expiry, from fixed lists and a timestamp), never free text. + +## Alternatives Considered + +- **Kind, scope and expiry only, checked on write and read (chosen).** The + blocker sweep needs exactly these three facts to tell the person what to + renew before leaving, and none of them grants anything. +- **Store a reference the session can use to fetch the credential (a + keychain item name, a file path).** Rejected: the checklist is re-injected, + summarised and later uploaded, and a pointer that works for the session + works for every reader of every copy. +- **Store the value, encrypted at rest.** Rejected: the key would have to be + reachable by the session that re-reads the file, so it protects nothing + against that session's transcript, compaction summary or upload, which is + where the copies go. +- **Leave it to the agent's instructions, with no check.** Rejected: an + instruction is a mistake filter at best, and agent-written state is the + writer most likely to paste a value it was told about. + +## Consequences + +- The blocker sweep and the return lists can say "the App token for + intentdriven/abcd, push and pull requests, expires 03:10" and nothing more; + renewing it is a step the person or the issuing step takes. +- The checklist writer depends on the native secret scanner, so a new + pattern added there protects the checklist too; a second scanner is not + built (principle one-canonical-primitive). +- The later shared and public copies (the phone and shared-copies intent) + inherit a file with no credential in it, so their own checks start from a + clean source rather than being the only line of defence. +- The rule is stated in the brief's invariants as invariant 24, consumed by + itd-2610170638184486. diff --git a/.abcd/development/decisions/adrs/README.md b/.abcd/development/decisions/adrs/README.md index 084dce4990..ad16a63fb8 100644 --- a/.abcd/development/decisions/adrs/README.md +++ b/.abcd/development/decisions/adrs/README.md @@ -216,4 +216,6 @@ The intent lint (a Go implementation) extends to verify these reciprocally. | [adr-2610161621415708](2610161621415708-abcd-s-command-guard-steers-agents-an-os.md) | Unattended runs abcd launches execute inside an OS sandbox | proposed | 2026-10-16 | | [adr-2610161733400551](2610161733400551-a-reviewer-in-a-model-matrix-run-never.md) | A reviewer or judge in a model-matrix run is told nothing about which model wrote the copy it judges | proposed | 2026-10-16 | | [adr-2610181003541997](2610181003541997-the-away-mode-s-stop-check-reads-the.md) | The away mode's stop check reads the forge once, as its documented meaning | proposed | 2026-10-18 | +| [adr-2610222026262980](2610222026262980-coordination-between-headless-agents-is.md) | Coordination between headless agents is bounded by deadlines, fenced by the claim's tip, version-checked, and done under each session's own credential | proposed | 2026-10-22 | +| [adr-2610222026265719](2610222026265719-a-session-s-checklist-names-a-credential.md) | A session's checklist names a credential by its kind, scope and expiry, never its value or anything that retrieves it | accepted | 2026-10-22 | diff --git a/.abcd/development/intents/drafts/itd-2610100704270254-an-abcd-run-works-out-how-many-sub.md b/.abcd/development/intents/drafts/itd-2610100704270254-an-abcd-run-works-out-how-many-sub.md index 154e817f4f..fb4b16b214 100644 --- a/.abcd/development/intents/drafts/itd-2610100704270254-an-abcd-run-works-out-how-many-sub.md +++ b/.abcd/development/intents/drafts/itd-2610100704270254-an-abcd-run-works-out-how-many-sub.md @@ -43,6 +43,7 @@ production_mode: hand-written ## Decisions - 2026-10-10: the product thinker filed this as one intent ("File as proposed"), after the facilitator pointed out that their rough rule, "times 2 minus 1", gives 15 of the class below eight but 4.5, not 3, of the class above. Asked whose sub-agents the budget covers, they chose "Each abcd run": only the agents a build or a drain starts count, not a session's hand-started agents or another session's. Asked how the classes trade, they chose "Double per class": each class up counts double and each class down half, four, eight, sixteen and thirty-two from the top, mixed by share. They added that the setting is the repository's, with abcd's bundled default, and is never kept in an agent's memory or anywhere like it. +- 2026-10-22: the product thinker reversed the answer "Each abcd run" above: the budget is one pool shared across sessions, and one busy session can slow the others (dcn-2610222025116937, a typed reversal recorded on the coordination intent itd-2610170634394993, which also reverses dcn-2610180720379757). The class weights and the repository's setting stand. ## Audit Notes diff --git a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md index 1f449bc810..07993fed68 100644 --- a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md +++ b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md @@ -11,7 +11,7 @@ impact: additive origin: researcher-authored production_mode: hand-written related_issues: [iss-2610161639025702, iss-2610161634043273, iss-2610162306239284, iss-2610140638074587] -related_intents: [itd-2610100704270254, itd-78, itd-115, itd-2609201916151817, itd-82, itd-2609201925079472, itd-148] +related_intents: [itd-2610170638184486, itd-2610222029228127, itd-2610100704270254, itd-78, itd-115, itd-2609201916151817, itd-82, itd-2609201925079472, itd-148] supersedes: [itd-2609150819440345] --- @@ -35,7 +35,7 @@ Published measurements say the collisions are common, not rare: 19.8% of agent p ## Mechanism -We expect parallel headless agents to stop colliding and stop idling on each other, because every fact they now pass by message (who holds what, what waits on what, whose turn it is, how much budget is left) becomes a record on the shared remote that each agent already fetches and that any session can act on. Shown wrong if, in a measured run of three or more headless agents, the agent minutes lost to conflicts, rebuilds after a dependency landed, and waits that ended in a timeout are no lower than in a run of the same agents coordinating by message. +We expect parallel headless agents to stop colliding and stop idling on each other, because every fact they now pass by message (who holds what, what waits on what, whose turn it is, how much budget is left) becomes a record on the shared remote that each agent already fetches and that any session can act on. Shown wrong if, in a measured run of three or more headless agents, the agent minutes lost to conflicts, rebuilds after a dependency landed, and waits that ended in a timeout are no lower than in a run of the same agents with only reservations and the load gate (dcn-2610180720412979: headless agents cannot coordinate by message, so the shipped coordination is the baseline). ## Scope Conditions @@ -43,6 +43,7 @@ We expect parallel headless agents to stop colliding and stop idling on each oth - Holds for one repository on one forge whose merge queue is the only route to the default branch. - Holds for the machine budget (preflights, race runs) only among agents on one machine, which is all the process table shows. The model budget spans every account of one organisation, because the provider's rate limits do. - Holds where the forge can issue a credential narrower than the person's own login, limited by repository and by act (push to its own branches, open a pull request, read), and expiring; GitHub's App installation tokens and fine-grained tokens are the candidates. +- Holds for unattended runs started where the person's own forge login is absent (a cloud session, a container or a separate user account); on a computer where that login is saved, abcd refuses to start one (dcn-2610222025104969, dcn-2610222030477927). - Holds while at most one minor version of abcd's coordination records is in use at once; a reader meeting a newer record refuses it rather than guessing. ## Acceptance Criteria @@ -58,6 +59,8 @@ We expect parallel headless agents to stop colliding and stop idling on each oth - Given a session that stops without notice, when the central view is opened, then its line reads as live, then as not seen for a stated time, then as work another session may take over after a delay, then it is gone; it never reads as working once its renewals have stopped. - Given an autonomous session started by abcd, when it pushes, opens a pull request or reads the forge, then it does so under a credential issued for it alone, limited to the repository and the acts its work needs and expiring with it, never under the person's own login; and when it tries an act outside that set (changing a repository setting or variable, deleting a branch it did not create, reaching another repository), the forge refuses it. - Given the person's own login holds admin rights on the repository, when an autonomous session runs, then nothing the session can reach carries those rights, so a setting only the maintainer may change (the reservation cap among them) stays out of its reach. +- Given a computer where the person's forge login is saved, when an unattended run is asked to start there, then abcd refuses to start it and names each place it found a login, without printing the login; and given a cloud session, a container or a separate user account with no login saved, the run starts with its own App token. +- Given the repository sets a sub-agent budget of eight and two sessions are running, when both start sub-agents, then together they never hold more than the budget, and a session that finds the budget spent waits for a slot under a stated deadline rather than starting one. - Given an agent about to re-submit a branch after a change it overlaps with has landed, when abcd rebuilds it, then the agent is given a summary of what landed since its base. ## SOTA @@ -74,14 +77,21 @@ Path taken: 2. The forge and the shared remote are the native floor (the queue, ## Open Questions -- Who runs the coordination pass: any session on demand (each `land`, `build` or `drain` runs it before acting), a session that holds a lease to run it on a timer, or both? The state-of-the-art pass advises against a coordinator process that must stay alive. -- Where does "waits on" live: a `Depends-On:` line in the pull request body (Mergify's form, visible on the forge), a field on the claim record on the remote (visible before any pull request exists), or both? -- Does the dependent branch get rebuilt by merging the landed dependency in or by restacking it, given the repository allows merge, squash and rebase merges and a restack rewrites shas a `resolved_by.commit` stamp may cite? -- Where does the machine budget live: the load gate's slots (`load.preflights`) extended to every heavy job as weighted slots, or a separate pool? Does the sub-agent budget (itd-2610100704270254) join it, and is it sized from the provider's rate-limit headers or a fixed count? -- Which credential does a session get, and who issues it: a GitHub App installation token (short-lived, scoped per repository and permission, the kind the dependency re-author workflow already uses), a fine-grained token per run, or another? Can abcd set it up for a repository without manual steps, and where is the issuing secret kept so no session can read it? -- Does the release cut become a lease the merge path checks, replacing the rule that the cutting session has the final say by message? -- The fixed-form announcements (itd-2609151838312703) stay their own intent; the pass reads them as one input beside claims and dependency lines. Is "I am merging, hold" then an announcement the pass acts on, or a field of the short status line? -- How often may the short lines and renewals be pushed? The forge recommends at most six pushes a minute per repository; ten sessions renewing every five minutes is two. +Answered on 2026-10-18 by the planning prep (facilitator, each a decision +record) and on 2026-10-22 by the product thinker: + +- Who runs the pass: any session on demand, before it acts; no timer lease (dcn-2610180720366282). +- Where "waits on" lives: a `Depends-On:` line in the pull request body, mirrored as a hint on the status line (dcn-2610180720362692). +- Merge or restack: merge, restacking only after a hand squash or rebase merge (dcn-2610180720369860). +- The machine budget: the load gate's slots, weighted per job (dcn-2610180720376153). The model budget: shared across sessions (dcn-2610222025116937, the product thinker's reversal of dcn-2610180720379757). +- The credential: a GitHub App token minted for the person, eight-hour expiry (dcn-2610180720379765, dcn-2610180723332655); the App registered once in the browser (dcn-2610180720389628); shipped with the coordination (dcn-2610222025116835). +- The release cut: the announcements intent's release reservation, no new lease (dcn-2610180720382472). +- "I am merging, hold": a hold is a reservation; the announcement informs (dcn-2610180720389352). +- Push rate: renewals hourly, the line only on change, back-off on refusal (dcn-2610180720390166). + +Still open, for the product thinker's sign-off: + +- **How far the shared model budget reaches.** Sessions on one computer can share a pool kept on that computer, as the load gate's slots already are; sessions on several computers or accounts would need the pool kept on the shared remote, which costs a push each time a sub-agent starts or ends. The scope condition above says the model budget spans every account of one organisation; the product thinker's answer of 2026-10-22 said "shared across sessions" without naming the reach. ## Decisions @@ -92,6 +102,12 @@ Path taken: 2. The forge and the shared remote are the native floor (the queue, 5. **Autonomous sessions never use the person's own forge login (2026-10-17).** The product thinker ruled that autonomous sessions work under a weaker credential of their own, given on a need-to-know basis, and that this belongs to this intent and to the autonomous-agents bundle. It was found that day that every session on the machine used the maintainer's own `gh` login, which holds admin rights on the repository, so any session could change a setting meant for the maintainer alone (the reservation cap, captured the same day). 6. **The central view says what each session is actually doing (2026-10-17).** Restated from the product thinker's ruling relayed by the checklist draft's session: the person must never wrongly believe agents are working or waiting. A line's state is therefore derived from what the machine and the remote can see (the renewal, the process, the last commit), and what an agent writes about itself is a hint beside it, because agents' own status reports lag. +7. **Unattended runs happen only where the person's forge login is absent (2026-10-22, C1).** The product thinker chose a real guarantee, set up once, over saying plainly that the login is still within reach (dcn-2610222025104969). abcd checks for a saved login before an unattended run starts and refuses where it finds one (dcn-2610222030477927, facilitator). +8. **Only the short line is shared now; full checklist uploads come later (2026-10-22, C2).** Settles the two rulings of 2026-10-17 in favour of later and keeps decision 3 as written (dcn-2610222025114805). The later uploads are itd-2610222029228127. +9. **Agents' own App tokens ship in the same release as the coordination (2026-10-22, C3).** The person registers abcd's GitHub App once in the browser before that release is usable (dcn-2610222025116835). +10. **The model budget is shared across sessions (2026-10-22, C-confirm).** A typed reversal of dcn-2610180720379757 and of the product thinker's 2026-10-10 answer "each abcd run" in itd-2610100704270254: the budget is one pool every session draws on, and one busy session can slow the others, which the product thinker accepted (dcn-2610222025116937). The press release keeps "a share of the model budget". +11. **The trust rule and the stance are written (2026-10-22).** The trust rule is adr-2610222026262980, proposed until the plan's sign-off, then a brief invariant; the stance is the principle `coordination-lives-on-the-shared-remote`. + ## Audit Notes _Empty. Populated by intent-auditor when intent moves to shipped/._ diff --git a/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md b/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md index 52b5691114..5932b8ab6a 100644 --- a/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md +++ b/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md @@ -6,6 +6,8 @@ kind: null suggested_kind: null reclassification_history: [] builds_on: [] +related_issues: [iss-2610170619247185, iss-2610222026223032] +related_intents: [itd-2610170634394993, itd-2610180912189125, itd-2610222029228127] severity: minor origin: researcher-authored production_mode: hand-written @@ -73,20 +75,46 @@ unowned pull request. ## Mechanism -> _Prompted (the claim-recording gradient): why the authors expect this to work, as a falsifiable "we expect X because Y" — not the outcome restated. Replace this line with the claim, or with the exact token `None stated.` alone on its line to record the claim as considered and declined._ +_Facilitator-proposed on 2026-10-22, for the product thinker's sign-off._ + +We expect autonomous sessions to stop stalling and to come back from a +compaction with their work intact, because everything a session now keeps +only in its context (what it owns, what each item waits on, the promises it +made, when it looks again) is written to a file the host re-injects after +every start and compaction, and a session that tries to stop is shown that +file's next item. Shown wrong if, over a measured run of autonomous sessions +across at least one night and three automatic compactions, the share of +hours a session sits idle while holding work it could do, and the number of +items a session loses or repeats after a compaction, are no lower than in +the same run with only the stall fix (itd-2610180912189125). ## Scope Conditions -> _Required (the claim-recording gradient): the population, platform, scale, or assumptions this claim holds under, one per top-level bullet — `abcd intent plan` stamps each with a persistent identity. Replace this line with those bullets, or with the exact token `None stated.` alone on its line._ +_Facilitator-proposed on 2026-10-22, for the product thinker's sign-off._ + +- Holds for sessions on a host whose hooks fire at session start (with the + compact source), at prompt submission and at stop, and can add text to the + model's context; Claude Code is the first such host. On a host without + them, the checklist is kept but nothing re-injects it. +- Holds for a checklist that fits the host's limit on injected text (10,000 + characters on Claude Code, 2026-10-22); a longer checklist is cut to its + open items, newest first, and says what it left out. +- Holds for one working copy per checklist on one computer; the copies + beyond it are itd-2610222029228127. +- Holds while the session itself writes the checklist as it works: no hook + can make a session act before an automatic compaction, so an item the + session never wrote down is not recovered. ## Acceptance Criteria _Draft for the product thinker to walk in the planning interview: derived by -the agent on 2026-10-18 from the press release and the decisions below, none -of it yet confirmed. The away mode, the stop check and the question reroute -are criteria of itd-2610180912189125, the slice that builds on this draft, -and are not repeated here. The last three move with the phone and the shared -options if the split proposed for Thursday is confirmed._ +the agent on 2026-10-18 from the press release and the decisions below, and +revised on 2026-10-22 for the product thinker's answers of that night and +iss-2610222026223032; none of it yet confirmed. The away mode, the stop check +and the question reroute are criteria of itd-2610180912189125, the slice that +builds on this draft, and are not repeated here. The three criteria for the +shared copies and the phone moved to itd-2610222029228127 on 2026-10-22 +(dcn-2610222025212338)._ - **Given** Alice, the product thinker, has said the sessions work on until 09:00, **when** a session writes its checklist, **then** the file in its @@ -98,6 +126,16 @@ options if the split proposed for Thursday is confirmed._ - **Given** a session with a checklist, **when** it starts or its context is compacted, **then** the last complete checklist is added to its context by the session-start hook, the compact source included. +- **Given** an autonomous session partway through a multi-step plan (lanes + dispatched, pull requests it watches, a promise to a peer, its next step), + **when** the host compacts its context automatically, **then** the session + resumes from the checklist it wrote before the compaction, naming the same + next step and the same open items, with nothing it had written down lost + (iss-2610222026223032). +- **Given** a session that has done work since it last wrote its checklist, + **when** it ends a turn, **then** it is told its checklist is out of date + and which items it has not recorded, so the file is current before any + compaction can come. - **Given** a session killed while writing its checklist, or a reboot, **when** the next session starts in that working copy, **then** it reads and is given the last complete checklist, never a truncated or empty one, and an @@ -110,15 +148,17 @@ options if the split proposed for Thursday is confirmed._ records the wait, **then** its checklist names what each item waits for and when it looks again, sooner than half its reservations' lease, and its state reads waiting, never stopped. -- **Given** Alice is about to leave, **when** the orchestrating session runs - the blocker sweep, **then** every session writes into its checklist any - undecided design decision, step only a person's hands can do, work it - depends on from another session, and credential missing or expiring, the - orchestrator puts them to Alice together, and it says the run is likely to - stay autonomous only once every session reports none open. +- **Given** Alice says she is away until 09:00, **when** the away mode is + set, **then** the blocker sweep runs at once: every session writes into its + checklist any undecided design decision, step only a person's hands can do, + work it depends on from another session, and credential missing or + expiring; the orchestrator puts them to Alice together; and the unattended + run starts only once she has answered them (dcn-2610222025217495). - **Given** the sweep finds a credential that expires before the window ends, **when** the session records it, **then** the checklist holds its kind, its - scope and its expiry, and never its value or anything that retrieves it. + scope and its expiry, and never its value or anything that retrieves it; + a write that carries a secret is refused, naming the field without echoing + it (adr-2610222026265719, brief invariant 24). - **Given** Alice comes back, **when** they read what the sessions left them, **then** they see two short lists across every session: the steps only their own hands can do, and the decisions taken while they were away, each @@ -127,7 +167,10 @@ options if the split proposed for Thursday is confirmed._ meets a technical trade-off, **then** it decides and records it; **and given** Nia is set to a person and is present, **when** the trade-off is one of security, performance or compatibility, **then** it is raised with them, - and any other is decided and recorded. + and any other is decided and recorded; **and given** the person is away, + **when** a session meets such a trade-off, **then** it decides it and lists + it, with its grounds and how to undo it, for the person's return + (dcn-2610222025225277). - **Given** a feature whose design trade-offs are listed in its checklist, **when** a session asks whether the human design work for it is done, **then** the answer is yes only when every genuine trade-off has a recorded @@ -136,16 +179,6 @@ options if the split proposed for Thursday is confirmed._ wrote about itself disagrees with what the machine can see (its claim renewal, its process, its last commit), **then** the state shown follows the machine, with the session's own words beside it as a hint. -- **Given** the private shared option is on, **when** a session writes its - checklist, **then** the checklist is shared in full by default, and a person - can narrow what is shared. -- **Given** the public option is on, **when** it publishes a checklist, - **then** nothing carrying the id of an advisory not yet published leaves the - machine, by the same check announcements apply. -- **Given** the phone app on the private shared option, **when** a session's - work would otherwise stop on a question, **then** the person whose role it - belongs to (product questions to Alice, technical trade-offs to Nia) is - notified, and can only choose among the options offered or add a note. ## Scope Split @@ -223,10 +256,16 @@ session's interview on 2026-10-17: - anything `abcd mode back` prints beyond one plain line per queued step and recorded stop: grouping, formatting or a summary of the window. +- **The shared copies and the phone (itd-2610222029228127, split out on + 2026-10-22):** the private shared option, the public option and the phone + app, with the decisions about them below, which now bind that draft + (dcn-2610222025212338). + ## Decisions The product thinker's answers of 2026-10-17, restated, and the agent's -technical decisions on state-of-the-art grounds, each marked. +technical decisions on state-of-the-art grounds, each marked. The first +five bullets now bind itd-2610222029228127, where they are carried. - **Where the checklist lives:** on this computer only for now. A published option on GitHub (public) and a private shared option (for example through @@ -296,10 +335,46 @@ technical decisions on state-of-the-art grounds, each marked. process, its last commit); what the agent writes about itself is a hint, since agent-written status lags. +The product thinker's answers of 2026-10-22, restated, each a decision +record: + +- **The phone and the shared copies are their own intent** + (dcn-2610222025212338): not asked, because the answer that full uploads + come later (dcn-2610222025114805) settled it. +- **The blocker sweep runs when the person says they are away**, and the run + starts after they answer (dcn-2610222025217495). +- **Overnight trade-offs of security, speed and compatibility are decided + and listed** with how to undo each (dcn-2610222025225277). +- **A checklist never holds a credential's value** (dcn-2610222025224993), + written up as adr-2610222026265719 and brief invariant 24. +- **Waiting holds up one item, never a whole session** + (dcn-2610222025230680), written up as the principle + `waiting-holds-one-item-never-a-session`. + +The facilitator's decisions of 2026-10-22, on state-of-the-art grounds: + +- **No hook makes a session write its checklist before a compaction** + (Claude Code's hooks reference, read 2026-10-22: the PreCompact hook can + run a command or block the compaction but cannot make the model act, and + PostCompact has no decision control). iss-2610222026223032's remedy asks + the PreCompact hook to make the session bring its list up to date; that + is not available, so the checklist is kept current as the session works, + the stop check tells a session at each turn's end when its checklist is + behind, and the session-start hook's compact source re-injects it. + ## Open Questions -_None for the product thinker at this point; the acceptance criteria are walked -in the planning interview._ +For the product thinker's sign-off, not yet answered: + +- **Delivery in two specs (proposed).** This intent owns two specs, built + in order: first the checklist on disk, its crash-safe write and its + re-injection at start and after compaction, which is + iss-2610222026223032's remedy and resolves it; then the waits, trigger + promises, timed wakes, the blocker sweep, the return lists, the + facilitator's mode and the design-done answer. The issue's remedy asks + for the first as this intent's first slice; the stall fix + (itd-2610180912189125) was carved out as its own intent, so this one is + a spec of the intent, not another intent. ## Audit Notes diff --git a/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md b/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md new file mode 100644 index 0000000000..89dc32d11f --- /dev/null +++ b/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md @@ -0,0 +1,105 @@ +--- +id: itd-2610222029228127 +slug: a-person-away-from-their-computer-can +spec_id: null +kind: null +suggested_kind: null +reclassification_history: [] +builds_on: [itd-2610170638184486, itd-2610170634394993] +severity: minor +impact: additive +origin: researcher-authored +production_mode: hand-written +--- + +# Every session's checklist reaches a shared copy and the phone + +## Press Release + +> A person away from their computer can follow every abcd session's checklist and answer the questions that would stop its work from their phone. Each session's full checklist is copied beyond the computer it runs on: to a private shared copy, everything by default and narrowable, and, if the person turns it on, to a public one that never carries an unpublished advisory. An installable web app on the private shared copy notifies the person whose role a question belongs to only when a session's work would otherwise stop, and lets them choose among the options offered or add a note. + +## Why This Matters + +_Split out of the checklist draft (itd-2610170638184486) on 2026-10-22, +recorded on the product thinker's ruling that full checklist uploads come +later (dcn-2610222025114805); the text below is carried over from +that draft's decisions of 2026-10-17, not newly confirmed._ + +The checklist draft keeps each session's state on the computer it runs on, +so a session's work survives compaction and a restart there. Two things it +cannot do from there: let the person see and answer from somewhere else, and +let another computer pick up a dead session where it stopped. On 2026-10-17 +the product thinker asked for both: a private shared copy (for example +through abcdev.app) that shares everything by default, a public option on +GitHub, and the phone, so a question that would stop a session overnight +reaches the right person wherever they are. On 2026-10-22 they ruled that +full uploads, public and private, come in a later piece of work, after the +short status line the coordination intent publishes; this draft is that +later piece. + +## Mechanism + +> _Prompted (the claim-recording gradient): why the authors expect this to work, as a falsifiable "we expect X because Y" — not the outcome restated. Replace this line with the claim, or with the exact token `None stated.` alone on its line to record the claim as considered and declined._ + +## Scope Conditions + +> _Required (the claim-recording gradient): the population, platform, scale, or assumptions this claim holds under, one per top-level bullet — `abcd intent plan` stamps each with a persistent identity. Replace this line with those bullets, or with the exact token `None stated.` alone on its line._ + +## Acceptance Criteria + +_Carried over unchanged from the checklist draft's agent-derived criteria of +2026-10-18; none of it is confirmed._ + +- **Given** the private shared option is on, **when** a session writes its + checklist, **then** the checklist is shared in full by default, and a person + can narrow what is shared. +- **Given** the public option is on, **when** it publishes a checklist, + **then** nothing carrying the id of an advisory not yet published leaves the + machine, by the same check announcements apply. +- **Given** the phone app on the private shared option, **when** a session's + work would otherwise stop on a question, **then** the person whose role it + belongs to (product questions to Alice, technical trade-offs to Nia) is + notified, and can only choose among the options offered or add a note. + +## Decisions + +Carried over from the checklist draft, where the product thinker gave them +on 2026-10-17, restated: + +- **Where the copies go:** a private shared option (for example through + abcdev.app) and a published option on GitHub, both beyond this computer + (product thinker). +- **What the private shared option shares:** everything by default, + configurable later (product thinker). +- **The phone surface:** an installable web app on the private shared + option, rather than a native app, reusing the live brief session's relay + and GitHub sign-in; a native app only if lock-screen answers or widgets + prove necessary. An answer from the phone only chooses among the options + offered or adds a note. Its look is chosen in design rounds in + lab-261017071130-6fd3032 (product thinker for scope; agent for the form). +- **Who answers from the phone:** each person by their role: product + questions to the product thinker, technical trade-offs to the technical + facilitator (product thinker). +- **When the phone notifies:** configurable; by default only when a + session's work would otherwise stop (product thinker). +- **A public upload passes the embargo check (agent, on brief invariant + 22):** nothing published may carry the id of an advisory not yet + published, the same check announcements apply (itd-2609151838312703). +- **A shared copy never carries a credential (on adr-2610222026265719, + brief invariant 24):** the copies start from a checklist that names a + credential only by kind, scope and expiry. +- **Split from the checklist draft with no question put (2026-10-22):** + recorded as dcn-2610222025212338; the product thinker's + answer that full uploads come later settled it. + +## Open Questions + +- Does another computer resume a dead session from its shared copy, and if + so, what does it take over (the claims through the coordination intent's + take-over, the checklist's items, both)? +- Is the private shared copy abcdev.app's store, or the person's own private + repository on the forge? + +## Audit Notes + +_Empty. Populated by intent-auditor when intent moves to shipped/._ diff --git a/.abcd/development/principles/coordination-lives-on-the-shared-remote.md b/.abcd/development/principles/coordination-lives-on-the-shared-remote.md new file mode 100644 index 0000000000..05ff061017 --- /dev/null +++ b/.abcd/development/principles/coordination-lives-on-the-shared-remote.md @@ -0,0 +1,45 @@ +# Coordination lives on the shared remote, never only in conversation + +**The rule.** A fact one agent relies on from another (who holds which +piece of work, which change waits on which, whose turn it is to merge, how +much of a shared budget is left) is written where every participant +already looks: a record on the shared remote, or the forge's own state. +A message between sessions may announce such a fact, but the fact is never +true only because a message said it, and nothing waits on a promise made +only in conversation. Whoever needs the fact reads the record, and any +session can act on what the records say, so no one session has to stay +alive for the rest to go on. + +**Why.** On the evening of 2026-10-16 one session spent its time telling +four others when a pull request had merged, and a new session had to be +asked by message to hold its own; a headless agent reads no message, and a +promise lasts only as long as the session that made it. Since 2026-10-03 a +second person works on the project from another computer, and a cloud +session shares nothing with either but the remote; the product thinker +chose then that the way the project's own people reserve work is a claim +on the shared remote, visible to all of them. The coordination intent's +decomposition, confirmed by the product thinker on 2026-10-17, routed this +stance to a principle. + +**Bounds.** + +- It governs facts agents rely on from each other. A question put to a + person, such as the blocker sweep before an unattended run, may travel by + message, and its answer lands in each session's own checklist + (dcn-2610180720427437). +- A session's own detail (its checklist, its next step) stays on its own + computer; only the short line other sessions rely on is published. +- An outside contributor reads the records but cannot write one; the rule + binds the project's own participants. +- Composes with [enforcement-claims-are-facts](enforcement-claims-are-facts.md): + a state shown to a person follows what the remote and the machine can + see, with what an agent says about itself as a hint. + +**Live instances.** Reservations (itd-2609091034175565) put who holds what +on the remote; announcements (itd-2609151838312703) put a session's stage +there; the coordination intent (itd-2610170634394993) adds the dependency +line, the shared budget and the central view. + +**Promotion.** Once the coordination pass refuses to act on a hold or a +wait that has no record behind it, the rule is enforced and becomes a +discipline. diff --git a/.abcd/development/principles/waiting-holds-one-item-never-a-session.md b/.abcd/development/principles/waiting-holds-one-item-never-a-session.md new file mode 100644 index 0000000000..7b78c4cce8 --- /dev/null +++ b/.abcd/development/principles/waiting-holds-one-item-never-a-session.md @@ -0,0 +1,48 @@ +# Waiting holds one item, never a session + +**The rule.** When a piece of work cannot go on until something outside it +happens (a person's answer, another change landing, a credential being +renewed, a slot coming free), only that piece waits. The session holding it +records what the item waits for and when it will look again, then works on +the next item it can do. A session is never idle because one of its items +is. Two consequences follow: + +- **A dependency holds only its own step.** A branch that waits on another + change is held only from being queued: its fixes, its review, its merge + from the default branch and its preflight go ahead now, and only the + landing waits. +- **Waiting is a state, not a stop.** A session whose items all wait on + something outside says so, names each wait, and sets when it next looks, + so it reads as waiting, never as stopped, and it comes back on its own. + +**Why.** On the night of 2026-10-16 one session sat idle for about ten of +eleven and a half hours while it held work it could do alone, and the +coordinating session held three finished, reviewed steps behind a merge +order that mattered only when they landed (iss-2610170619247185). Each +wait was real, and each held far more than the thing waited for. On +2026-10-22 the product thinker ruled that this be written up as a standing +principle. + +**Bounds.** + +- It governs how work is held, not whether a question is asked: a question + only the person can answer still waits for them, and only the item it + belongs to stops. +- It does not make a session start work it is not allowed to do while it + waits; what it may decide alone is set by the person's standing + instructions and the away mode. +- An item that can never be unblocked (a dependency closed without merging, + a cycle) is not a wait: it is refused and named, so nothing waits on it. +- Composes with [enforcement-claims-are-facts](enforcement-claims-are-facts.md): + the state shown for a waiting session follows what the machine can see, + not what the session says about itself. + +**Live instances.** The stall fix's stop check (itd-2610180912189125) counts +a stacked branch with an open base as owed work, and the coordination +intent's decision that a declared dependency constrains only the landing +(dcn-2610180905291297). The checklist intent (itd-2610170638184486) gives +every item its own wait. + +**Promotion.** The stop check is the first mechanical gate on half of this +rule; once the checklist's per-item waits ship, the whole of it is enforced +and it becomes a discipline. diff --git a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md index 83f8536a81..53c4ba2916 100644 --- a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md +++ b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md @@ -2495,6 +2495,33 @@ After filing, two adversarial reviews (design and record discipline, each an Opu The SOTA survey was filed as research/notes/2026-10-16-model-matrix-sota.md, and itd-166 was widened to the asked and served sides it was routed. The routing survived as a five-record split; its plumbing row was the part the initial routing got wrong. +## 2026-10-22: the checklist and coordination routings, confirmed by the product thinker's answers + +The 2026-10-18 checklist routing (below) was graded unconfirmed until the +product thinker answered. Their answers of 2026-10-22 confirm it as proposed, +row by row: + +| part | type | home, as filed on 2026-10-22 | +|---|---|---| +| the private shared option, the public option and the phone | capability | its own draft, itd-2610222029228127, building on both intents (dcn-2610222025212338: not asked, because the answer that full uploads come later settled it) | +| the checklist records a credential's kind, scope and expiry, never its value or anything that retrieves it | trust rule | adr-2610222026265719 (accepted) and brief invariant 24 | +| a wait holds one item, never the session, and a declared dependency holds only the landing | stance | the principle `waiting-holds-one-item-never-a-session` | +| autonomous work survives the host's automatic compaction (iss-2610222026223032, filed by the product thinker the same night) | capability | the checklist intent itself, as the first of two proposed specs; not a new intent, since it is the checklist's on-disk file and its re-injection | + +The coordination intent's two deferred records were filed at the same +time: its trust rule as adr-2610222026262980 (proposed until the plan's +sign-off, then a brief invariant) and its stance as the principle +`coordination-lives-on-the-shared-remote`. + +Verdict: SPLIT, confirmed for the checklist draft; the coordination split of +2026-10-17 stands. One part of the issue's remedy did not survive a check of +the host's hooks reference: no hook can make a session write before an +automatic compaction, so the checklist is kept current at each turn's end +instead (recorded in the checklist draft's decisions). Advisory reversal +flags: one, raised by the product thinker, not the routing: the model budget +shared across sessions reverses itd-2610100704270254's "each abcd run" +(dcn-2610222025116937). + ## 2026-10-17: headless agents in parallel coordinate through the shared remote (product thinker, routing confirmed) | part | type | home | diff --git a/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md b/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md new file mode 100644 index 0000000000..c50bb3a7d4 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222030477927 +slug: abcd-looks-for-the-person-s-forge-login +date: 2026-10-22 +--- +2026-10-22 — abcd looks for the person's forge login before it starts an unattended run, and refuses to start where it finds one (facilitator, on state-of-the-art grounds, applying the product thinker's 2026-10-22 ruling C1 on itd-2610170634394993): before an away-mode run, build or drain starts unattended, abcd checks the places a forge login is kept on that machine for the forge host (the forge CLI's stored login, the token environment variables the forge CLI and git read, and git's credential helper asked for the host), reading only whether each answers and never the token itself, and refuses to start, naming each place it found one; it does not try to remove or hide a login, since the run belongs where none was ever saved (a cloud session, a container or a separate user account). A login the check cannot see is out of its reach, so the guarantee rests on where the run is started, which abcd states as the person's setup step (grounds: cli.github.com/manual/gh_help_environment; git-scm.com/docs/gitcredentials; adr-2610222026262980 rule 4) diff --git a/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md b/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md index 8b326a66c8..1eb10e323f 100644 --- a/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md +++ b/.abcd/work/issues/open/iss-2610222026223032-autonomous-work-does-not-reliably.md @@ -9,6 +9,7 @@ found_during: "PT asked for it during the 2026-10-22 crash recovery" origin: researcher-authored production_mode: hand-written found_at: "hooks" +related_intents: [itd-2610170638184486] remedy: "each abcd session keeps its to-do list as a file in the local tier (.abcd/.work.local/, one per session), written as it works; abcd's PreCompact hook makes the session bring it up to date before a compaction and the SessionStart and post-compaction hooks inject it back, so the session resumes from the list; deliver it as the first slice of the session checklist intent itd-2610170638184486" --- From e82239f45aff982657d3b2b7ffd16cd73373ac48 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:20:49 +0100 Subject: [PATCH 04/10] chore: record the sandbox probes on macOS Step 1's first macOS probes of srt 0.0.79 against real abcd work: a fenced build, test and commit in a scratch lane worktree, the credential stores, writes outside the policy, the process table, and the two harnesses starting. The evidence goes under the spec's steps; the read denies gain the login keychain, the launch gains SSL_CERT_FILE on macOS, the known limits take what was measured, and the five product questions are restated in plain words. Refs: spc-2610161751056096 Refs: itd-2610161621364075 Assisted-by: Claude:claude-opus-5-5 --- ...-a-person-who-leaves-abcd-to-run-on-its.md | 139 ++++++++++++++---- 1 file changed, 112 insertions(+), 27 deletions(-) diff --git a/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md b/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md index 9ceb2d794c..fec70545ac 100644 --- a/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md +++ b/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md @@ -109,7 +109,8 @@ The write list is derived from the launch itself; nothing in the repository wide - `~/.ssh` and `~/.gnupg`; - `~/.aws`, `~/.config/gh`, `~/.netrc`, `~/.git-credentials` and `~/.docker/config.json`; -- abcd's own credential file under its home. +- abcd's own credential file under its home; +- on macOS, the login keychain's files, `~/Library/Keychains`. Step 1's probes added this one: without it the keychain handed a stored GitHub token to a command inside the fence, although `~/.config/gh` was denied (Step 1 evidence, probe 3). The vendor's guide says why the two layers go together: "Without network isolation, a compromised agent could exfiltrate sensitive files like SSH keys". The model endpoint is reachable, so this narrows what a misled agent can send there. It is not part C's promise. @@ -153,7 +154,7 @@ So the guard keeps its place as adr-42's mistake filter, teaching inside the bou ### Checks that read the machine run outside -The load gate, the peers scan, and the reservation renewals and fences the loop takes between stages already run in abcd, outside any launch. What changes is what happens to a gate an agent reaches inside the fence: `make preflight` runs its test stages through `abcd implement load --wait`, and inside the fence that waiter would read the fence's view of the process table. On Linux the process namespace shows it only its own processes. It also could not write its hold under abcd's home. +The load gate, the peers scan, and the reservation renewals and fences the loop takes between stages already run in abcd, outside any launch. What changes is what happens to a gate an agent reaches inside the fence: `make preflight` runs its test stages through `abcd implement load --wait`, and inside the fence that waiter would read the fence's view of the process table. On Linux the process namespace shows it only its own processes. On macOS it sees none at all: `/bin/ps` cannot be started inside the fence and `pgrep` finds nothing, so `implement load` reports the check unavailable and carries on (Step 1 evidence, probe 5). It also could not write its hold under abcd's home, and on macOS today's waiter already refuses for that reason rather than opening. So a waiter never judges the machine from inside a fence: @@ -187,8 +188,10 @@ The `fence` lane is a Make target that joins `make preflight` beside `smoke` and - On macOS srt's README gives "no guarantee" for a hard link, inside an allowed path, to a file outside it. - On Linux srt's own deny scan is fixed at launch. abcd's explicit denies cover the main git directory, which exists before the launch, and not a repository the agent creates later inside its worktree. -- Go programs that check TLS through the system's trust service can fail under Seatbelt (the vendor's guide names `gh`, `gcloud` and `terraform`). Step 1 measures whether `go` itself downloads modules inside the fence, and records what it finds. -- A private module (`GOPRIVATE`) is fetched from its version-control host with the person's own credentials. One held in a store the read list denies (`~/.ssh`, `~/.netrc`, `~/.git-credentials`) cannot be read, and the fetch fails naming the module. A keychain credential helper may still answer; step 1 measures whether it does. +- Go programs that check TLS through the system's trust service fail under Seatbelt (the vendor's guide names `gh`, `gcloud` and `terraform`), and `go` itself is one of them. Measured on macOS (Step 1 evidence, probe 1): every HTTPS request `go` makes inside the fence fails with `x509: OSStatus -26276`, both a module download from `proxy.golang.org` and the checksum lookup it routes through that proxy, so a module missing from the person's cache, or from `go.sum`, cannot be fetched or verified. A build whose modules are all in `go.sum` and in the person's cache works, through the file proxy, with no network. With `SSL_CERT_FILE` naming the system's certificate file (`/etc/ssl/cert.pem`), Go reads its roots from that file instead of the trust service, and the download and the checksum verification both pass, with nothing in the fence widened. srt's own remedy, `enableWeakerNetworkIsolation`, opens the trust service and is what its README warns is an exfiltration path; it is not used. So on macOS the launch also sets `SSL_CERT_FILE` (step 7). A program that ignores that variable and asks the trust service still fails, and is named where it does. +- A private module (`GOPRIVATE`) is fetched from its version-control host with the person's own credentials. One held in a store the read list denies (`~/.ssh`, `~/.netrc`, `~/.git-credentials`, `~/.config/gh`) cannot be read, and the fetch fails naming the module. Measured on macOS (probe 3): git's keychain helper gave nothing inside the fence (`failed to get: -128`, as it does outside in a session nobody can answer a prompt in), and the GitHub helper `gh auth git-credential` could not start, so no git credential reached the fence. The keychain itself did answer: a token stored there was returned in full until `~/Library/Keychains` joined the read denies (Reads, above). The ssh agent's socket is refused inside the fence (`Error connecting to agent: Operation not permitted`), so neither a fetch over ssh nor an ssh signature can use the agent. +- srt's CLI reports no refusal of its own on macOS, with or without `--debug`: the refusal reaches the launch's stderr only as the refused program's own error, `Operation not permitted` with the path (probe 4). The kernel logs each refusal, tagged by srt (`Sandbox: () deny(1) file-write-create `, then `CMD64__END__SBX`), to the unified log, which a `log stream` running during the launch sees and a later `log show` does not. That stream also carries refusals nothing asked about (`sysctl-read kern.iossupportversion` from every shell). `Fence.Refusals` reads the program errors (step 10); reading the log stream as a second source is left to step 10 to weigh. +- Every commit inside the fence, with git 2.52, prints `error: Unable to create '/packed-refs.lock': Operation not permitted` during its ref update, and still exits 0 with its commit, ref and reflog written (probe 2). `packed-refs` stays off the write list. `Fence.Refusals` records the line like any other refusal, and the lane does not read it as a failed commit. - On Linux, srt covers a protected path that does not yet exist with a placeholder on the host, and a SIGKILL skips its cleanup. abcd's timeout kill is the group's SIGKILL (`procgroup.Kill`), so a timeout first sends SIGTERM to srt and kills the group only after a grace period (step 8). After every launch abcd also removes, outside the fence, any empty placeholder that appeared at one of srt's protected names and that git does not track, so none is committed or left in the person's worktree (step 9). Step 1 checks whether bubblewrap's own session takes descendants out of the group abcd kills. - srt quotes the argument vector into a shell string on macOS and Linux, which amends `proc.go`'s rule that the argv is never a shell line. abcd's wrapped vector is fixed words, a role name from a closed list, and paths abcd made. A path carrying a quote or a control character is refused before the launch, and a test sends a prompt through with every shell metacharacter (step 7). - The intent's press release says the person's other projects and settings "stay out of its reach". With this design that holds for changes. It holds for reads only where the read denies above reach. The wording is question (e). @@ -210,27 +213,27 @@ The `fence` lane is a Make target that joins `make preflight` beside `smoke` and ## Questions for the product thinker -Put at step 2, after step 1's probes, so each answer reads what the probes found. Each question carries the answers that can be defended; nothing is built before all five are answered. - -- **(a) srt as the one required program on the runner route.** srt needs Node 22.12 or later, and on Linux also bubblewrap and `socat` (and `ripgrep`). It would be adr-22's first declared exception, scoped to the runner route, with the per-machine off switch in place of degradation. Defensible answers: - - accept srt as the runner route's one required program; - - take nono instead, a single binary with no Node and no user namespaces, at the cost of a fence that is not Anthropic's while abcd launches Claude; - - hold the fence until one ships without a separate runtime, and leave runner launches unfenced until then. -- **(b) When srt is missing.** Defensible answers: - - offer to install it, and install only after the person's explicit yes; the install runs the package manager's own scripts with the person's full reach; - - print the install command and the off switch only, and install nothing. -- **(c) The host list.** A fenced launch reaches only the hosts it is given. Defensible answers: - - the derived list: the model endpoint; with a `go.mod`, the Go proxy and checksum-database hosts and the hosts `GOPRIVATE` and `GONOPROXY` name; plus the hosts added per machine; - - the model endpoint and the hosts added per machine only, so every Go host is the person's to name; - - the derived list, with the run naming every host it allows once at its start. -- **(d) Commit signing.** Where the person's git signs every commit, the agent's commit inside the fence cannot reach the key. Defensible answers: - - lane commits unsigned inside the fence, and signed by abcd outside it at land, which rewrites each lane commit with the same tree and message; - - the signing key readable inside the fence, so a misled agent can sign anything as the person; - - refuse a fenced run while `commit.gpgsign` is on, naming it, until the person turns signing off for runner launches or turns the fence off. -- **(e) The press release's "stay out of its reach".** It holds for writes. For reads it holds only for the credential stores this spec denies; every other file the account can read stays readable to a fenced agent. Defensible answers: - - keep the wording and confine reads further, which part A leaves out of scope today; - - narrow the wording to what holds: changes everywhere outside the agent's own work, and reads of the credential stores; - - keep the wording and state the read limit in the how-to and the brief. +Put at step 2, after step 1's probes. Each question is written for the product thinker, in plain words, and each answer says what it leads to. The mechanism behind each one is in the sections above. Nothing is built before all five are answered. + +- **(a) One extra program to install.** Fencing the agents abcd starts needs one program abcd does not ship: Anthropic's sandbox. The person installs it once, with the small runtime it needs. Everything else in abcd keeps needing nothing extra. + - Require it for the agents abcd starts. Leads to: on a machine without it, abcd will not start such an agent until the person installs it or switches the fence off for that machine. + - Use another open-source fence instead, a single file with nothing else to install. Leads to: less to install, but the fence around Claude is not Anthropic's. + - Wait for a fence that needs nothing extra. Leads to: the agents abcd starts keep running unfenced until then. +- **(b) When the program is missing.** + - abcd offers to install it, and installs only after the person says yes. Leads to: one step for the person; the install runs with everything the person's account can do. + - abcd says how to install it and how to switch the fence off, and installs nothing. Leads to: the person installs it by hand. +- **(c) Which internet addresses a fenced agent may reach.** The fence lets an agent reach only the addresses it is given. + - The AI service, what the project's build needs to download, and any the person adds. Leads to: builds that download code keep working, and anything more is the person's to add. + - The AI service and any the person adds, nothing else. Leads to: a build that needs a download fails until the person names where it downloads from. + - The first, with each run listing every address it allows when it starts. Leads to: the same reach, said out loud at every run. +- **(d) Signed commits.** Where the person signs every commit, an agent inside the fence cannot reach the signing key. + - The agent's commits are unsigned inside the fence, and abcd signs them when it hands the work in. Leads to: the work arrives signed, and the agent never holds the key. + - The agent may use the signing key. Leads to: commits are signed as today, and a misled agent could sign anything in the person's name. + - abcd refuses a fenced run while signing is on. Leads to: the person turns signing off for these runs, or turns the fence off. +- **(e) The promise that the person's other projects and settings "stay out of its reach".** The probes show the agent cannot change anything outside its own work. It can still read most of what the person can read; only the places that hold passwords, keys and sign-ins are hidden from it. + - Keep the words, and hide more from reading. Leads to: more work now, and some tools may stop working inside the fence. + - Narrow the words to what holds: it cannot change anything outside its own work, and cannot read the person's passwords, keys and sign-ins. Leads to: a smaller promise that is true as written. + - Keep the words, and state the reading limit in the guide. Leads to: the promise reads wider than what it does. ## Footprint @@ -242,12 +245,13 @@ Put at step 2, after step 1's probes, so each answer reads what the probes found 1. Probe srt on the real harnesses before anything is signed off or built - packages: .abcd/development/research/notes, .abcd/development/intents - tests: an abcd lab on macOS and on Linux with bubblewrap, each probe named in the research note: `claude --print --bare` and `opencode run` wrapped by srt 0.0.79 with the step 3 policy written by hand; `CLAUDE_CONFIG_DIR` per launch with the person's `~/.claude` and `~/.claude.json` read-only; opencode with fresh XDG folders, a per-run cache seeded by clone, a read-only credential link, and a refreshing sign-in; `go build` and `go test ./...` of abcd itself with the per-run `GOPATH` and caches, a module missing from `go.sum` verified through the checksum database, a `GOPRIVATE` module fetched from its host, and whether module downloads pass TLS under Seatbelt; whether a keychain credential helper answers git inside the fence; a commit in a linked worktree under the narrowed main `.git`, with and without `gc.auto=0`, with `packed-refs` unwritable, and with `commit.gpgsign` on; a hard link from the worktree to a file outside it on macOS; SIGTERM cleanup, and whether bubblewrap's session leaves the process group abcd kills; what opencode fetches at start with a seeded cache; a prompt with shell metacharacters. Probing builds nothing, and its findings are what step 2's questions read. Each limit found becomes a scope condition on the intent or a capture of its own, and a failure on the macOS or Linux half goes back to the person at step 2, with nono as the named alternative + - evidence: the macOS half's first probes ran on 2026-10-17 and are recorded under Step 1 evidence, after the steps. The Linux half, the agent sessions and the probes listed there as not yet run are still to do 2. Sign-off: the person answers the five questions for the product thinker (waits for the person; nothing is built before it) - packages: .abcd/development/brief, .abcd/development/decisions/adrs, .abcd/development/specs - tests: each answer recorded as a decision and folded into this spec; on a yes to question (a), the proposed marks come off adr-22's consequence and the dependencies chapter's paragraph (02-constraints/02-dependencies.md), and on a no both are withdrawn; record-lint and docs-lint pass over both and over the ADR's acceptance 3. Declare the fence seam and derive a launch's fence-neutral policy (itd-2610161621364075 criteria 1, 2, 3) - packages: internal/core/fence - - tests: fence: `Policy` names no mechanism, and `Fence` is met by a test double the runner's tests use; for a lane launch, for an interview launch, and for each runner, the write list is exactly the worktree (or the record's folder), the receipt folder, the main `.git`'s `objects/`, the lane's own `worktrees//` without its `config.worktree`, `gitdir` and `commondir`, and `refs/heads/build/` with its reflogs, the run's `GOPATH` and Go caches, the launch's temp and host-state folders and the run's opencode cache; `hooks/`, `config` and `packed-refs` are never granted; abcd's home is read-only but for the worktree; the credential stores are denied for reads; a path given through a symlink is refused; nothing read from the repository's `.abcd/config.json` changes the list; the host list is the runner's endpoint, with a `go.mod` the `GOPROXY`/`GOSUMDB` hosts and the `GOPRIVATE`/`GONOPROXY` hosts, and the machine layer's additions, and a pattern too broad to pass is named + - tests: fence: `Policy` names no mechanism, and `Fence` is met by a test double the runner's tests use; for a lane launch, for an interview launch, and for each runner, the write list is exactly the worktree (or the record's folder), the receipt folder, the main `.git`'s `objects/`, the lane's own `worktrees//` without its `config.worktree`, `gitdir` and `commondir`, and `refs/heads/build/` with its reflogs, the run's `GOPATH` and Go caches, the launch's temp and host-state folders and the run's opencode cache; `hooks/`, `config` and `packed-refs` are never granted; abcd's home is read-only but for the worktree; the credential stores are denied for reads, `~/Library/Keychains` among them on macOS; a path given through a symlink is refused; nothing read from the repository's `.abcd/config.json` changes the list; the host list is the runner's endpoint, with a `go.mod` the `GOPROXY`/`GOSUMDB` hosts and the `GOPRIVATE`/`GONOPROXY` hosts, and the machine layer's additions, and a pattern too broad to pass is named 4. Read the off switch, the backend and the host additions from the machine layer only (criterion 5) - packages: internal/core/runner, .abcd/development/brief - tests: runner: `runner.sandbox.off`, `runner.sandbox.backend` and `runner.sandbox.hosts` read from the machine file; the backend defaults to `srt` and an unknown one is refused, naming the known ones; the same keys in a repository's `.abcd/config.json` refuse the load, naming the machine's file; a host entry that is `*`, a bare top-level domain or not `host[:port]` is refused; no flag sets any of them @@ -259,7 +263,7 @@ Put at step 2, after step 1's probes, so each answer reads what the probes found - tests: loop and cli: a `build`, a `drain` lane start and an `intent interview` with a role routed to a runner, on a machine whose fence check fails, exit 2 before the run is created and start no process; a run whose roles all stay on the host checks nothing; with the switch on, the run's output says once that its launches are not fenced and names the machine file; an untested srt version is said once at the run's start 7. Wrap every launch at the launch point, with its environment (criteria 1, 2) - packages: internal/core/runner, internal/core/fence, internal/core/fence/srt, ACKNOWLEDGEMENTS.md - - tests: runner: a fenced claude and opencode launch run `srt --settings -- …` with the harness's own argv unchanged after `--`; the environment sets the per-launch temp, the run's `GOPATH` and Go caches and the host-state folders over the parent's, and seals `gc.auto=0` and `maintenance.auto=false`, and `commit.gpgsign=false` unless question (d) is answered otherwise; the lane's `config.worktree` takes the gc values when the repository has `extensions.worktreeConfig` on, and the main `.git/config` is unchanged either way; opencode's cache is seeded once per run, by clone where the filesystem offers one; the settings file sits outside every writable path and round-trips through srt's schema; a wrap failure is reason `fenced`, records no fallback and runs no other route; `Wrap`'s cleanup runs on every way a launch ends; loop: a lane whose launch is refused as fenced refuses at the runner stage naming the remedy; a path carrying a quote or a control character is refused before the launch; `fence`-tagged: a prompt carrying every shell metacharacter arrives byte for byte; a commit in the lane leaves no gc refusal; the run's fence folder is removed when the run finishes + - tests: runner: a fenced claude and opencode launch run `srt --settings -- …` with the harness's own argv unchanged after `--`; the environment sets the per-launch temp, the run's `GOPATH` and Go caches and the host-state folders over the parent's, on macOS sets `SSL_CERT_FILE` to the system's certificate file, and seals `gc.auto=0` and `maintenance.auto=false`, and `commit.gpgsign=false` unless question (d) is answered otherwise; the lane's `config.worktree` takes the gc values when the repository has `extensions.worktreeConfig` on, and the main `.git/config` is unchanged either way; opencode's cache is seeded once per run, by clone where the filesystem offers one; the settings file sits outside every writable path and round-trips through srt's schema; a wrap failure is reason `fenced`, records no fallback and runs no other route; `Wrap`'s cleanup runs on every way a launch ends; loop: a lane whose launch is refused as fenced refuses at the runner stage naming the remedy; a path carrying a quote or a control character is refused before the launch; `fence`-tagged: a prompt carrying every shell metacharacter arrives byte for byte; a commit in the lane leaves no gc refusal; the run's fence folder is removed when the run finishes 8. Kill a fenced launch in order (criteria 1, 2) - packages: internal/procgroup, internal/core/runner - tests: procgroup: a timeout sends SIGTERM to srt first and kills the group only after the grace; an srt that exits on SIGTERM within the grace is not sent SIGKILL; the kill still goes through the pid the handle holds, never a pattern; `fence`-tagged: after a timeout no descendant of the launch survives, including any bubblewrap session step 1 found outside the group @@ -284,3 +288,84 @@ Put at step 2, after step 1's probes, so each answer reads what the probes found 15. State the rule and prove it on a real run - packages: .abcd/development/brief, commands, docs - tests: a brief invariant says an agent abcd launches runs inside a fence the operating system enforces, unless the person turned it off in their user layer, and the adapters chapter describes the fence seam; `commands/build.md` and `commands/intent.md` name the refusal and the off switch; a how-to under `docs/` says how to install the fence and turn it off, crediting the package under the docs-lint allow escape; docs-lint and record-lint pass; a fenced `build` of a real intent on the built binary, on macOS and on Linux, records a refused outside write, commits in its worktree, lands its push, and leaves the person's host settings byte-identical + +## Step 1 evidence + +### macOS, 2026-10-17 + +Run on macOS 27.0 (arm64) with srt 0.0.79 from npm, Node 25.1.0, go 1.27.1, git 2.52.0, Claude Code 2.1.302 and opencode 1.18.31. Each probe ran `srt --settings /settings.json -- ` from a scratch lane worktree of abcd, `~/.abcd.noindex/worktrees//probe-lane` on a branch `build/probe-srt-lane` cut from `origin/main`, whose git directory is the main checkout's (`/.git`). `` is a fresh folder under `$TMPDIR`. The worktree, both branches it made, its nine loose objects and its receipt folder were removed afterwards. + +Nothing in the settings was widened to make a probe pass. One variant was tighter: probe 3 ran again with `~/Library/Keychains` added to `denyRead`. + +**The settings file.** srt was given real absolute paths; they are written here with `~`, `` and ``. + +```json +{ + "network": { + "allowedDomains": ["api.anthropic.com", "proxy.golang.org", "sum.golang.org"], + "deniedDomains": [] + }, + "filesystem": { + "denyRead": [ + "~/.ssh", "~/.gnupg", "~/.aws", "~/.config/gh", "~/.netrc", + "~/.git-credentials", "~/.docker/config.json", "~/.abcd.noindex/credentials.json" + ], + "allowRead": [], + "allowWrite": [ + "~/.abcd.noindex/worktrees//probe-lane", + "/.abcd/.work.local/run//lane-1", + "/.git/objects", + "/.git/worktrees/probe-lane", + "/.git/refs/heads/build", + "/.git/logs/refs/heads/build", + "/gopath", "/gocache", "/gomodcache", "/tmp", + "/claude-config", "/xdg-data", "/xdg-state", "/xdg-cache" + ], + "denyWrite": [ + "/.git/worktrees/probe-lane/config.worktree", + "/.git/worktrees/probe-lane/gitdir", + "/.git/worktrees/probe-lane/commondir" + ] + } +} +``` + +**The environment.** Each launch started from an empty environment (`env -i`), as the runner's scrubbed one is, with `HOME`, `USER`, `PATH` and `LANG`; `TMPDIR`, `GOTMPDIR` and `CLAUDE_CODE_TMPDIR` set to `/tmp`; `GOPATH`, `GOCACHE` and `GOMODCACHE` set to the run's folders; `GOPROXY=file://~/go/pkg/mod/cache/download,https://proxy.golang.org,direct`; `CLAUDE_CONFIG_DIR` and the three `XDG_*` folders set to the run's; `GIT_TERMINAL_PROMPT=0`; and the seal as `GIT_CONFIG_COUNT=3` with `gc.auto=0`, `maintenance.auto=false` and `commit.gpgsign=false`. `GOFLAGS=-p=4` kept the cold build's share of a machine running three other preflights small; it is not part of the policy. + +**Trial launch.** The spec's trial (step 5): the write inside `/tmp` landed, and the write outside was refused, `Operation not permitted`, and was not on disk afterwards. `sh` exits 1 at the refused redirection, so the trial is judged from outside, by the two files, as step 5 says, and not by its exit status. + +| Probe | What ran inside the fence | Outcome | +|---|---|---| +| 1. Build and test | `go build ./...` with every cache empty | Passed in 16 s. 75 modules came from the person's cache through the file proxy, with no network. | +| | `go test ./internal/fsutil/` | Passed. | +| | `go get rsc.io/quote/v4@v4.0.1` in a scratch module, a module not in the person's cache | **Failed**: `tls: failed to verify certificate: x509: OSStatus -26276`. Go asks the system's trust service, which the fence closes. | +| | `go get github.com/google/go-cmp@v0.7.0`, in the person's cache but not in the scratch `go.sum` | **Failed** at verification: Go asked the checksum database through `https://proxy.golang.org/sumdb/sum.golang.org/supported`, with the same TLS error. | +| | both again with `SSL_CERT_FILE=/etc/ssl/cert.pem` | Passed. The checksum database's cache was written to `/gopath/pkg/sumdb/sum.golang.org/`, under the run's `GOPATH` as the spec expects. Whether `sum.golang.org` is dialled directly, or only through the proxy, was not measured. | +| 2. Commit | `git add` and `git commit` in the lane, through the repository's own hooks (`core.hooksPath` names the main checkout's `.githooks`) | Committed, exit 0. `pre-commit`, `prepare-commit-msg` and `commit-msg` all ran inside the fence, and `commit-msg` built abcd with the run's caches. The commit's objects, the lane branch and its reflog landed in `/.git`. The seal read back as `gc.auto=0`, `maintenance.auto=false`, `commit.gpgsign=false`. Each commit also printed `error: Unable to create '/.git/packed-refs.lock': Operation not permitted`, from git's own ref update, without failing. | +| | writes to `.git/hooks/`, `.git/config`, `.git/packed-refs`, `.git/HEAD`, the lane's `config.worktree` (create), `gitdir` and `commondir`, and the main checkout's `.githooks/pre-commit` | All refused, `Operation not permitted`. Their checksums matched before and after. | +| | `git config --local srtprobe.x 1` | Refused: `could not lock config file /.git/config: Operation not permitted`. | +| | `git update-ref refs/heads/srt-probe-outside HEAD` | Refused: the ref's lock file could not be created. | +| | `git update-ref refs/heads/build/srt-probe-other HEAD` | **Allowed**, as the spec expects: the folder grant covers every lane's branch, and only the ref check after the launch (step 9) catches it. | +| | write to the lane's receipt folder; open the run's `state.json` for writing | Receipt written; `state.json` refused. | +| 3. Credentials | `git credential-osxkeychain get` for github.com | `failed to get: -128`, the same as outside in this session: the item needs a prompt nobody can answer. Nothing returned. | +| | `git credential fill` for github.com (helper: `gh auth git-credential`) | Nothing returned: `gh` could not start, `open ~/.config/gh/config.yml: operation not permitted`. | +| | `gh auth status` | Same refusal; `gh` does not start. | +| | `security find-generic-password -s gh:github.com -w`, counting the bytes only | **The keychain answered**: 75 bytes, the same as outside. The GitHub token gh keeps in the login keychain was readable inside the fence. | +| | the same, with `~/Library/Keychains` added to `denyRead` | Not found (exit 44), 0 bytes. | +| | `ssh-add -l` with the person's `SSH_AUTH_SOCK` | Refused: `Error connecting to agent: Operation not permitted`. | +| 4. Outside writes | create `~/probe-outside`, `~/.ssh/x`, `~/.abcd.noindex/trusted-roots.probe`, a file in another worktree, `~/.claude/agents/srt-probe.md`; open `~/.abcd.noindex/config.json` and `~/.claude.json` for writing; `touch`; `mkdir` | All refused, and none was on disk afterwards; `config.json` and `~/.claude.json` matched their checksums before and after. | +| | reads of `~/.ssh/known_hosts`, `~/.config/gh`, `~/.docker/config.json`; and of `~/.claude.json`, which is not denied | The three denied reads were refused; `~/.claude.json` was read. | +| | how srt reports a refusal | srt adds nothing to stderr, with or without `--debug`. The only report is the program's own: `: : Operation not permitted`. The kernel logs `Sandbox: () deny(1) file-write-create ` with srt's tag `CMD64__END__SBX` to the unified log: a `log stream` running during the launch sees it, and a later `log show` does not. | +| 5. Process table | `ps -A`, `ps -p 1`, `pgrep -f 'make preflight'`, `sysctl -n vm.loadavg` | `/bin/ps` cannot be started (`Operation not permitted`, exit 126); `pgrep` found 0 of the 9 preflight processes visible outside; the `sysctl` command was refused. Outside, `ps -A` counted 1444 processes. | +| | `go run ./cmd/abcd implement load --site preflight` | `LOAD CHECK UNAVAILABLE (preflight): could not read the process table (/bin/ps: fork/exec /bin/ps: operation not permitted); carrying on without checking for stray programs`. The load average was still read (13.9 on 16 cores). | +| | `go run ./cmd/abcd implement load --wait --timeout 20s -- /usr/bin/true` | Refused, exit 1: `the hold could not be judged or written: openat .load-wait.lock: operation not permitted`. Today's waiter does not open inside the fence. | +| 6. Harnesses | `claude --version` and `opencode --version`, with fresh `CLAUDE_CONFIG_DIR` and `XDG_*` folders; then again with the keychain denied | Both started and printed their version under both settings, with nothing refused. opencode made `opencode/repos` and `opencode/log` in the data folder, `opencode` in the state folder and `opencode/bin` in the cache folder. No agent session ran and no model was used. | + +**What this changes in the spec.** + +- The read denies gain `~/Library/Keychains` on macOS (Reads; step 3's tests). +- On macOS the launch sets `SSL_CERT_FILE` to the system's certificate file, so `go` can fetch and verify modules without srt's weaker trust-service option (Known limits; step 7's tests). +- The Known limits now state how srt reports a refusal on macOS, the `packed-refs.lock` line each commit prints, and what the keychain and the ssh agent answered. +- The checks that read the machine: on macOS a fenced command sees no processes at all rather than only its own, and today's waiter already refuses rather than opening. The conclusion, that the gate is read outside, is unchanged. + +**Not yet probed.** The Linux half, all of it. `claude --print --bare` and `opencode run` as real sessions, with the person's `~/.claude` and `~/.claude.json` read-only, and opencode's seeded cache, read-only credential link and refreshing sign-in. `go test ./...` over the whole module (only `./internal/fsutil/` ran). A `GOPRIVATE` module. A commit without the gc seal, and one with `commit.gpgsign` on (the person's git does not sign). The hard link from the worktree to a file outside it. SIGTERM cleanup. A prompt carrying shell metacharacters. From 9813b8dfbcb2e5f23f1e3415ea4286759d61132f Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Sat, 17 Oct 2026 08:22:39 +0100 Subject: [PATCH 05/10] docs: say what a sandboxed check sees of the process table on macOS Refs: spc-2610161751056096 Refs: itd-2610161621364075 Assisted-by: Claude:claude-opus-5-5 --- ...2610161621415708-abcd-s-command-guard-steers-agents-an-os.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md b/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md index c33e90f051..877cf7019a 100644 --- a/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md +++ b/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md @@ -51,7 +51,7 @@ We will decide limiting the network as a separate, later record. ## Consequences -- The machine-load gate and every `pgrep`-style waiter run in abcd, outside the boundary: inside a sandbox `ps` sees only the sandbox's own processes, so a check run there would read a busy machine as clear. +- The machine-load gate and every `pgrep`-style waiter run in abcd, outside the boundary: inside a sandbox the process table is not the machine's: on macOS `ps` cannot start at all and `pgrep` sees no process (the spec's step 1 evidence, 2026-10-17), and elsewhere it may see only the sandbox's own, so a check run there cannot read the machine. - A run without a boundary is the person's choice, recorded in their user layer under `~/.abcd.noindex/`; no repository file can make it for them. - Network limiting is deferred to its own record. When it comes, its honest claim is a declared list of hosts, which narrows mistakes and does not stop code being sent elsewhere: allowing the forge allows a push to any repository the credential reaches. - Drains and builds a person's own session drives stay unconfined by abcd; reporting their isolation is proposed as a separate part of the intent. From 8463c117a4b600f0a403606576344d2ebb0144f7 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:46:22 +0100 Subject: [PATCH 06/10] docs: fix the lane E records after the Opus and Sonnet review round 1 The login check reads only signals that carry no secret and never asks a credential helper; the coordination ADR fences abcd's pushes and tags with an atomic claim renewal and states the one-call window left for a queue entry; the credential ADR separates the product thinker's rule from the facilitator's enforcement; criteria that could not pass as written are narrowed (protected prefixes, one computer, owned work); two questions the rulings leave open are recorded on the coordination draft; the calibration note grades only what the answers named as confirmed. Assisted-by: Claude:claude-opus-5-5 --- .../brief/02-constraints/03-invariants.md | 2 +- ...coordination-between-headless-agents-is.md | 39 ++++++++++++------- ...-session-s-checklist-names-a-credential.md | 18 +++++---- ...394993-several-headless-agents-work-one.md | 11 ++++-- ...every-abcd-session-keeps-a-checklist-on.md | 11 ++++-- ...7-a-person-away-from-their-computer-can.md | 7 ++-- .../2026-08-15-decomposition-calibration.md | 17 ++++---- ...abcd-looks-for-the-person-s-forge-login.md | 2 +- 8 files changed, 66 insertions(+), 41 deletions(-) diff --git a/.abcd/development/brief/02-constraints/03-invariants.md b/.abcd/development/brief/02-constraints/03-invariants.md index 77de826d95..f23ee9862d 100644 --- a/.abcd/development/brief/02-constraints/03-invariants.md +++ b/.abcd/development/brief/02-constraints/03-invariants.md @@ -177,4 +177,4 @@ The following are non-negotiable invariants — any architectural choice that vi 22. **A private advisory stays private until it is published, and abcd acts on GitHub for it only on an approval per act** — a private security advisory's record never enters the public ledger while the advisory is unpublished: it lives only in the embargo store, a detached worktree in the machine-scoped worktree store that is never committed, and every verb that touches the store refuses one that is on a branch, tracks a private record, or lies inside the checkout; nothing public carries an unpublished advisory's id, its reserved CVE id, its title or the location of the private records, and the outbound check refuses an artefact that does, naming where without repeating it; and publishing or closing an advisory on GitHub happens only on the person's explicit approval of exactly the plan or text shown, one approval per act, never supplied by an agent on its own, refining [adr-44](../../decisions/adrs/0044-remote-mutation-and-caller-identity.md)'s confirmed remote write. Ruled by [adr-2610120558328615](../../decisions/adrs/2610120558328615-a-private-advisory-s-record-never-enters.md), accepted by the product thinker on 2026-10-12. Consumed by itd-2610112239022766 (`abcd embargo`). 23. **No work-in-progress branch reaches the default branch, and a receipt-less push is admitted only for a host connector's declared work-in-progress prefix** — a cloud session abcd starts pushes its work, checked or not, only to ``, where the prefix is declared by that host's connector (never named in the core or the hook), ends in the path segment `abcd-wip/` with at most one segment before it, and is one entry in the pre-push hook's declared list of receipt-less prefixes, with its own commit shape (a valid record id after the prefix, every pushed commit passing the attribution gate) and this rule as its record; every other branch still needs a preflight receipt. A required check on the default branch's ruleset refuses any pull request or merge-queue entry whose source branch holds the segment `abcd-wip` or whose diff adds a cloud report file, and the work reaches the default branch only through the person's own collect, which re-runs preflight on a copy on their computer and mints the receipt there; no receipt minted in the cloud is trusted. The hook is not the boundary (`--no-verify` skips it); the required check, the ruleset and the collect are. Ruled by [adr-2610120937435856](../../decisions/adrs/2610120937435856-a-cloud-session-s-unchecked-work-goes.md), decided by the technical facilitator on the product thinker's delegation of 2026-10-12. Consumed by itd-2610120738075547 (work handed to cloud sessions). -24. **A session's checklist names a credential only by its kind, scope and expiry, never its value or anything that retrieves it** — the checklist a session keeps on disk, which is re-injected at every start and after every compaction and read by every later session in that working copy, records a credential it needs only as three typed fields: its kind, what it reaches, and when it expires; it never holds a secret, a part, hash or encoding of one, or a pointer that hands one to its reader (an environment variable's value, a key file's path, a keychain item's name, a command that prints a token). The checklist writer runs every field through abcd's native secret scanner and refuses a write that matches, naming the field and the pattern kind without echoing it, and the reader refuses to re-inject a file that matches, falling back to the last good copy; the scanner catches the shapes it knows, and the rule binds whatever it misses. Ruled by [adr-2610222026265719](../../decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md), accepted by the product thinker's ruling of 2026-10-22. Consumed by itd-2610170638184486 (every session keeps a checklist). +24. **A session's checklist names a credential only by its kind, scope and expiry, never its value or anything that retrieves it** — the checklist a session keeps on disk, which is re-injected at every start and after every compaction and read by every later session in that working copy, records a credential it needs only as three typed fields: its kind, what it reaches, and when it expires; it never holds a secret, a part, hash or encoding of one, or a pointer that hands one to its reader (an environment variable's value, a key file's path, a keychain item's name, a command that prints a token). The checklist writer runs every field through abcd's native secret scanner and refuses a write that matches, naming the field and the pattern kind without echoing it, and the reader refuses to re-inject a file that matches, falling back to the last good copy; the scanner catches the shapes it knows, and the rule binds whatever it misses. Ruled by [adr-2610222026265719](../../decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md), the rule ruled by the product thinker on 2026-10-22, its enforcement decided by the technical facilitator on their delegation. Consumed by itd-2610170638184486 (every session keeps a checklist). diff --git a/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md b/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md index 20152ccbb0..194fd8f933 100644 --- a/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md +++ b/.abcd/development/decisions/adrs/2610222026262980-coordination-between-headless-agents-is.md @@ -53,12 +53,15 @@ session: ends in a refusal that names what it waited on and for how long. No wait is open-ended, and no deadline depends on a process staying alive: each is derived from the records when they are read. -2. **A claim is fenced by its tip.** Every push, queue entry or tag made - under a claim re-checks that the claim's branch tip is still the one the - act started from. On a push the forge enforces it (a push that is not a - fast-forward is refused); for a queue entry or a tag abcd re-reads the tip - and refuses the act if it moved. A claim whose holder stopped renewing is - retaken only after its expiry plus a fixed delay. +2. **A claim is fenced by its tip.** A push of the work branch or a tag + that abcd makes under a claim travels in one atomic push with a renewal + of the claim branch, so if the claim's tip moved the forge refuses the + whole push. A queue entry cannot share a push: abcd re-reads the claim's + tip immediately before it enqueues and names the pull request's head it + expects, which leaves a window of one call that this rule states rather + than claims closed. A push made outside abcd is not fenced, and the rule + does not claim it. A claim whose holder stopped + renewing is retaken only after its expiry plus a fixed delay. 3. **A reader refuses a coordination record newer than it knows.** From the release that adds a version to the coordination records, a reader meeting a version above its own refuses the record with a message naming both @@ -66,11 +69,19 @@ session: before that ignore the field, which is why the rule starts there. 4. **An autonomous session never acts under the person's own forge login.** It pushes, opens pull requests and reads the forge with a short-lived - token abcd's GitHub App mints for that session, limited to the repository - and the acts its work needs. An unattended run starts only where the - person's saved login cannot be reached (a cloud session, a container or a - separate user account); abcd checks the known places a login is kept - before starting one, and refuses to start, naming where it found one. + token issued through abcd's GitHub App for that session, limited to the + repository and the acts its work needs. The token reaches only the git + and forge commands abcd runs for the session, through a credential channel + of abcd's own, never the forge CLI's token variables, and the key or + refresh token that issues it is never readable by a session. An + unattended run starts only where the person's saved login cannot be + reached (a cloud session, a container or a separate user account). Before + one starts, and before its token is issued, abcd reads the signals that + carry no secret (the forge CLI's login status by exit code, whether the + token variables are set, whether a stored-credential file names the host) + and refuses to start, naming where it found a login. It never asks a + credential helper, so a login kept only in a keychain is out of its sight, + and the refusal says that rather than calling the machine clean. ## Alternatives Considered @@ -92,8 +103,10 @@ session: - Every coordination verb (land, build, drain, status, reserve, the load gate) carries a stated deadline and a named refusal at its end; a test per verb watches it end. -- Queueing and tagging gain a tip re-check; a session whose claim was taken - over cannot land under it. +- abcd's pushes and tags carry the claim's renewal atomically, and its queue + entries a tip re-check; a session whose claim was taken over cannot push + or tag under it through abcd, and can enqueue only inside the one-call + window. - The coordination records gain a version field in the release that ships this; the refusal binds from then on. - Unattended runs need a setup the person does once: the App registered in diff --git a/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md b/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md index 2fa6896634..fdab1f9616 100644 --- a/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md +++ b/.abcd/development/decisions/adrs/2610222026265719-a-session-s-checklist-names-a-credential.md @@ -27,16 +27,19 @@ The checklist is the most-read file a session has. It is re-injected into the model's context at every start and after every compaction, written by the agent, read back by any later session in that working copy, summarised into the person's return lists, and, in later work, uploaded to a private -shared copy and optionally published (the checklist decision of 2026-10-22, -K1). Anything written there is copied into every one of those places. +shared copy and optionally published (itd-2610222029228127, split out on +2026-10-22 by dcn-2610222025212338). Anything written there is copied into every one of those places. Agent-written state is also the least careful writer in the system: a model told to record "the token that expires at 03:00" will as readily paste the token. On 2026-10-18 the agent routing the draft's decomposition proposed this as a separate trust rule; on 2026-10-22 the product thinker ruled that it be -written up as one (K-split i). Through the decomposition table a trust rule -becomes an ADR and a clause of the brief's invariants. +written up as one (dcn-2610222025224993). Through the decomposition table a +trust rule becomes an ADR and a clause of the brief's invariants. The rule +itself, the first two paragraphs of the decision below, is the product +thinker's; how it is enforced, the third, is the technical facilitator's, +decided on their delegation and open to change at the plan's sign-off. ## Decision @@ -56,12 +59,13 @@ by when. **The write refuses, and the read refuses.** abcd's checklist writer checks every field it writes with abcd's native secret scanner -(`internal/adapter/scanner`), the one capture, decide and the claim writer -already run, and refuses a write that carries a secret +(`internal/adapter/scanner`), the one capture and decide already run, and refuses a write that carries a secret pattern, naming the field and the pattern kind and never echoing the match; the session's last good checklist stays in place. A checklist read from disk that fails the same check is not re-injected: the reader reports the field -and falls back to the last good copy, as it does for a file it cannot parse. +and falls back to the last good copy, as it does for a file it cannot parse; +the last good copy is only ever a file a checked write produced, and it is +checked again on read, so a file that fails both is not re-injected at all. The check is a pattern scan, so it catches the shapes it knows; the rule binds whatever it misses, and the fields that name a credential are typed (kind, scope and expiry, from fixed lists and a timestamp), never free text. diff --git a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md index 07993fed68..9bd51df8a8 100644 --- a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md +++ b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md @@ -50,17 +50,17 @@ We expect parallel headless agents to stop colliding and stop idling on each oth - Given an agent whose branch waits on an open pull request, and says so in a line abcd reads, when it asks abcd to land the branch, then abcd does not queue it while that pull request is open, rebuilds the branch on top of it once it merges, and queues the result. - Given a declared dependency that is closed without merging, cannot be resolved, or closes a cycle, when abcd reads it, then abcd refuses to land the branch and names the dependency and the reason, rather than waiting. -- Given an agent holding a claim that stops renewing it, when the claim expires and a delay has passed, then another agent can take the work, and a push or queue from the first agent under its old claim is refused because the claim's generation number has moved on. +- Given an agent holding a claim that stops renewing it, when the claim expires and a delay has passed, then another agent can take the work, and a push or tag abcd makes for the first agent under its old claim is refused because it carries a renewal that is no longer a fast-forward, and a queue entry is refused by a re-read of the claim made just before it (a window of one call, stated, not claimed closed). - Given three headless agents on one machine with a budget of two preflights, when all three reach their preflight at once, then two run and the third waits, and no wait lasts beyond its stated deadline without ending in a named refusal. - Given the session running the coordination pass is stopped mid-pass, when another session runs the pass, then it reaches the same decisions from the records on the remote alone, with nothing lost. - Given two agents running different versions of abcd's coordination records, when they read each other's records, then neither waits on the other in a cycle, and the one meeting a newer format than it knows refuses with a message naming the version. - Given several sessions on different computers, each keeping its own checklist locally, when the person or any session opens the central view, then it shows one line per session (what it holds, its one-line state, what it waits on, when it was last seen) built from the short lines the sessions published, with no program having to stay running. - Given a session that changes what it holds or waits on, when it next writes its checklist, then it publishes its short line only after the checklist is written, and publishes nothing when nothing in the line changed. - Given a session that stops without notice, when the central view is opened, then its line reads as live, then as not seen for a stated time, then as work another session may take over after a delay, then it is gone; it never reads as working once its renewals have stopped. -- Given an autonomous session started by abcd, when it pushes, opens a pull request or reads the forge, then it does so under a credential issued for it alone, limited to the repository and the acts its work needs and expiring with it, never under the person's own login; and when it tries an act outside that set (changing a repository setting or variable, deleting a branch it did not create, reaching another repository), the forge refuses it. +- Given an autonomous session started by abcd, when it pushes, opens a pull request or reads the forge, then it does so under a credential issued for it alone, limited to the repository and the acts its work needs and expiring with it, never under the person's own login; and when it tries an act outside that set (changing a repository setting or variable, deleting a branch under a protected prefix, reaching a repository outside its token's reach), the forge refuses it (narrowed per dcn-2610180720415625: a token that can push can delete an unprotected branch). - Given the person's own login holds admin rights on the repository, when an autonomous session runs, then nothing the session can reach carries those rights, so a setting only the maintainer may change (the reservation cap among them) stays out of its reach. -- Given a computer where the person's forge login is saved, when an unattended run is asked to start there, then abcd refuses to start it and names each place it found a login, without printing the login; and given a cloud session, a container or a separate user account with no login saved, the run starts with its own App token. -- Given the repository sets a sub-agent budget of eight and two sessions are running, when both start sub-agents, then together they never hold more than the budget, and a session that finds the budget spent waits for a slot under a stated deadline rather than starting one. +- Given a computer where the person's forge login is in a place abcd checks (the forge CLI's saved login, a token variable, a stored-credential file naming the host), when an unattended run is asked to start there, then abcd refuses to start it, names each place it found a login without reading or printing the login, and says what it cannot see (a keychain-only login, an SSH key); and given a cloud session, a container or a separate user account with none of them, the run starts with its own App token, which travels through abcd's own credential channel and so never trips the check. Which runs count as unattended is open below. +- Given the repository sets a sub-agent budget of eight and two sessions are running on one computer, when both start sub-agents, then together they never hold more than the budget, and a session that finds the budget spent waits for a slot under a stated deadline rather than starting one. Whether the pool also spans computers is open below. - Given an agent about to re-submit a branch after a change it overlaps with has landed, when abcd rebuilds it, then the agent is given a summary of what landed since its base. ## SOTA @@ -91,6 +91,9 @@ record) and on 2026-10-22 by the product thinker: Still open, for the product thinker's sign-off: +- **Which unattended runs the login check covers.** The away mode keeps the person's own sessions working overnight on their own computer, where their login is saved. Either every unattended run is covered, so the person's own away-mode sessions move to a place without the login, or only the agents abcd launches on its own (build and drain roles) are, and the person's own sessions keep the login they run under. +- **How a session's token is issued, and whose name it carries.** A token issued for the person keeps their name and needs a stored refresh token and the App's client secret; an installation token is narrower and shorter-lived and shows as the App. Either way the issuing secret lives where no session can read it: on the person's computer, which then stays on for a run longer than the token lasts, or in a small service they run. dcn-2610180723332655 chose the person's token; where its issuing secret lives was left open and is not answered by it. + - **How far the shared model budget reaches.** Sessions on one computer can share a pool kept on that computer, as the load gate's slots already are; sessions on several computers or accounts would need the pool kept on the shared remote, which costs a push each time a sub-agent starts or ends. The scope condition above says the model budget spans every account of one organisation; the product thinker's answer of 2026-10-22 said "shared across sessions" without naming the reach. ## Decisions diff --git a/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md b/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md index 5932b8ab6a..bec7b9afb7 100644 --- a/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md +++ b/.abcd/development/intents/drafts/itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md @@ -132,10 +132,13 @@ shared copies and the phone moved to itd-2610222029228127 on 2026-10-22 resumes from the checklist it wrote before the compaction, naming the same next step and the same open items, with nothing it had written down lost (iss-2610222026223032). -- **Given** a session that has done work since it last wrote its checklist, - **when** it ends a turn, **then** it is told its checklist is out of date - and which items it has not recorded, so the file is current before any - compaction can come. +- **Given** a session that owns a reservation, pull request or branch its + checklist does not list, or whose owned work changed after the checklist + was last written, **when** it ends a turn while the person is away, or the + person next writes to it while present, **then** it is told its checklist + is behind and which of those it does not list, so the file is current + before any compaction can come. A step that left no such trace cannot be + seen, and the scope conditions say so. - **Given** a session killed while writing its checklist, or a reboot, **when** the next session starts in that working copy, **then** it reads and is given the last complete checklist, never a truncated or empty one, and an diff --git a/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md b/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md index 89dc32d11f..57ada449e0 100644 --- a/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md +++ b/.abcd/development/intents/drafts/itd-2610222029228127-a-person-away-from-their-computer-can.md @@ -26,10 +26,9 @@ later (dcn-2610222025114805); the text below is carried over from that draft's decisions of 2026-10-17, not newly confirmed._ The checklist draft keeps each session's state on the computer it runs on, -so a session's work survives compaction and a restart there. Two things it -cannot do from there: let the person see and answer from somewhere else, and -let another computer pick up a dead session where it stopped. On 2026-10-17 -the product thinker asked for both: a private shared copy (for example +so a session's work survives compaction and a restart there. From there it +cannot let the person see and answer from somewhere else. On 2026-10-17 the +product thinker asked for that: a private shared copy (for example through abcdev.app) that shares everything by default, a public option on GitHub, and the phone, so a question that would stop a session overnight reaches the right person wherever they are. On 2026-10-22 they ruled that diff --git a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md index 53c4ba2916..f18fa2f2be 100644 --- a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md +++ b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md @@ -2498,8 +2498,10 @@ The SOTA survey was filed as research/notes/2026-10-16-model-matrix-sota.md, and ## 2026-10-22: the checklist and coordination routings, confirmed by the product thinker's answers The 2026-10-18 checklist routing (below) was graded unconfirmed until the -product thinker answered. Their answers of 2026-10-22 confirm it as proposed, -row by row: +product thinker answered. Their answers of 2026-10-22 cover two of its rows +by name (the credential rule and the waiting stance); the phone split +follows from their answer on uploads and was not asked; the compaction row +is the facilitator's routing, put to them at the plan's sign-off. As filed: | part | type | home, as filed on 2026-10-22 | |---|---|---| @@ -2513,14 +2515,15 @@ time: its trust rule as adr-2610222026262980 (proposed until the plan's sign-off, then a brief invariant) and its stance as the principle `coordination-lives-on-the-shared-remote`. -Verdict: SPLIT, confirmed for the checklist draft; the coordination split of -2026-10-17 stands. One part of the issue's remedy did not survive a check of +Verdict: SPLIT; two rows confirmed by the product thinker's named answers, +the other two facilitator-routed and unconfirmed until the plan's sign-off; +the coordination split of 2026-10-17 stands. One part of the issue's remedy did not survive a check of the host's hooks reference: no hook can make a session write before an automatic compaction, so the checklist is kept current at each turn's end instead (recorded in the checklist draft's decisions). Advisory reversal -flags: one, raised by the product thinker, not the routing: the model budget -shared across sessions reverses itd-2610100704270254's "each abcd run" -(dcn-2610222025116937). +flags: one, from the facilitator's confirming question, which the product +thinker answered by reversing: the model budget shared across sessions +reverses itd-2610100704270254's "each abcd run" (dcn-2610222025116937). ## 2026-10-17: headless agents in parallel coordinate through the shared remote (product thinker, routing confirmed) diff --git a/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md b/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md index c50bb3a7d4..2beb10c312 100644 --- a/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md +++ b/.abcd/work/decisions/dcn-2610222030477927-abcd-looks-for-the-person-s-forge-login.md @@ -3,4 +3,4 @@ id: dcn-2610222030477927 slug: abcd-looks-for-the-person-s-forge-login date: 2026-10-22 --- -2026-10-22 — abcd looks for the person's forge login before it starts an unattended run, and refuses to start where it finds one (facilitator, on state-of-the-art grounds, applying the product thinker's 2026-10-22 ruling C1 on itd-2610170634394993): before an away-mode run, build or drain starts unattended, abcd checks the places a forge login is kept on that machine for the forge host (the forge CLI's stored login, the token environment variables the forge CLI and git read, and git's credential helper asked for the host), reading only whether each answers and never the token itself, and refuses to start, naming each place it found one; it does not try to remove or hide a login, since the run belongs where none was ever saved (a cloud session, a container or a separate user account). A login the check cannot see is out of its reach, so the guarantee rests on where the run is started, which abcd states as the person's setup step (grounds: cli.github.com/manual/gh_help_environment; git-scm.com/docs/gitcredentials; adr-2610222026262980 rule 4) +2026-10-22 — abcd looks for the person's forge login before it starts an unattended run, and refuses to start where it finds one (facilitator, on state-of-the-art grounds, applying the product thinker's 2026-10-22 ruling C1 on itd-2610170634394993): before an away-mode run, build or drain starts unattended, and before abcd issues the session a token of its own, abcd checks only signals that carry no secret: the forge CLI's login status for the host by exit code alone, with its output discarded (the CLI prints no token unless asked with --show-token); whether the token variables the forge CLI and git read (GH_TOKEN, GITHUB_TOKEN and their enterprise forms) are set, never their values; and whether the stored-credential files hold an entry for the host (the forge CLI's hosts file and ~/.git-credentials), by the host line alone. It never asks a git credential helper for the host, since a helper answers with the token itself and on macOS may raise a keychain prompt that hangs an unattended start; so a login kept only in a keychain is out of the check's sight, and the refusal says so rather than claiming the machine is clean (brief invariant 16). It refuses to start, naming each place it found a login, and never removes or hides one; the guarantee rests on where the run is started (a cloud session, a container or a separate user account), which abcd states as the person's setup step. The session's own token, issued after the check, never travels in those variables (grounds: cli.github.com/manual/gh_auth_status; cli.github.com/manual/gh_help_environment; git-scm.com/docs/gitcredentials; adr-2610222026262980 rule 4) From 6d93172a6544a9f79dce0553295a4a80685a6ecd Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:47:31 +0100 Subject: [PATCH 07/10] docs: record the product thinker's sandbox answers and close step 2 of the fence spec Five answers of 2026-10-22, restated as decisions: require srt; offer to install it only on an explicit yes; the derived host list, not printed at each run; abcd signs a fenced lane's commits at hand-in; and the promise narrowed to what holds. adr-22's exception and the dependencies chapter lose their proposed marks, the sandbox ADR's two consequences name the rulings, the intent's press release is narrowed, and the spec folds each answer in. The macOS probe evidence from chore/sandbox-probes came in with the two commits before this one; the Linux half stays open. Assisted-by: Claude:claude-opus-5-5 --- .../brief/02-constraints/02-dependencies.md | 12 +++++++----- .../0022-bundled-deps-as-pluggable-adapters.md | 9 +++++---- ...8-abcd-s-command-guard-steers-agents-an-os.md | 4 ++-- ...075-a-person-who-leaves-abcd-to-run-on-its.md | 2 +- ...096-a-person-who-leaves-abcd-to-run-on-its.md | 16 ++++++++++------ ...34-anthropic-s-sandbox-srt-is-required-for.md | 6 ++++++ ...1424027-when-srt-is-missing-abcd-offers-to.md | 6 ++++++ ...27241-a-fenced-agent-may-reach-the-derived.md | 6 ++++++ ...6-the-sandbox-intent-s-promise-is-narrowed.md | 6 ++++++ ...51-abcd-signs-a-fenced-lane-s-commits-when.md | 6 ++++++ 10 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 .abcd/work/decisions/dcn-2610222041417934-anthropic-s-sandbox-srt-is-required-for.md create mode 100644 .abcd/work/decisions/dcn-2610222041424027-when-srt-is-missing-abcd-offers-to.md create mode 100644 .abcd/work/decisions/dcn-2610222041427241-a-fenced-agent-may-reach-the-derived.md create mode 100644 .abcd/work/decisions/dcn-2610222041434056-the-sandbox-intent-s-promise-is-narrowed.md create mode 100644 .abcd/work/decisions/dcn-2610222041439951-abcd-signs-a-fenced-lane-s-commits-when.md diff --git a/.abcd/development/brief/02-constraints/02-dependencies.md b/.abcd/development/brief/02-constraints/02-dependencies.md index 2ce26baa20..99ad8780b2 100644 --- a/.abcd/development/brief/02-constraints/02-dependencies.md +++ b/.abcd/development/brief/02-constraints/02-dependencies.md @@ -8,10 +8,10 @@ with the external tool available as an **opt-in adapter** over the same seam present tool is an upgrade, never a prerequisite — a missing or misbehaving adapter degrades to the native path rather than breaking abcd. -## The runner route's fence (proposed) +## The runner route's fence -**Proposed, awaiting the product thinker's sign-off.** One route is planned to -need an external program: the agents abcd's runner launches run inside a fence +**Signed off by the product thinker on 2026-10-22** (dcn-2610222041417934). One +route needs an external program: the agents abcd's runner launches run inside a fence the operating system enforces, and that fence is Anthropic's sandbox-runtime (`srt`, which needs Node, and on Linux bubblewrap and `socat`), installed by the person @@ -19,8 +19,10 @@ the person spc-2610161751056096). It is adr-22's first declared exception, scoped to the runner route: a fence has no native default to degrade to, so a machine without one refuses a runner launch, and the person's per-machine off switch -in `~/.abcd.noindex/config.json` takes the place of degradation. Nothing of it -is built yet, and every other route still needs no external tool. +in `~/.abcd.noindex/config.json` takes the place of degradation. Where `srt` is +missing, abcd offers to install it and installs only after the person's +explicit yes (dcn-2610222041424027). Nothing of it is built yet, and every +other route still needs no external tool. ## Adapters over native defaults diff --git a/.abcd/development/decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md b/.abcd/development/decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md index 0f4f8702fd..22a3576067 100644 --- a/.abcd/development/decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md +++ b/.abcd/development/decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md @@ -83,8 +83,8 @@ itd-47, the intent that rested on the same bundled cascade: rather than breaking abcd. - The doctor's job narrows from policing foreign bypass artifacts to checking abcd's own health and which adapters are wired. -- **Proposed, awaiting the product thinker's sign-off (2026-10-16): the first - declared exception.** The fence around the agents abcd's runner launches +- **The first declared exception (proposed 2026-10-16, signed off by the + product thinker on 2026-10-22, dcn-2610222041417934).** The fence around the agents abcd's runner launches ([adr-2610161621415708](2610161621415708-abcd-s-command-guard-steers-agents-an-os.md), spc-2610161751056096) is Anthropic's sandbox-runtime (`srt`), an external program abcd does not ship. On the runner route alone it is a requirement, @@ -93,5 +93,6 @@ itd-47, the intent that rested on the same bundled cascade: fence exists to remove. A machine without it refuses a runner launch, and the person's per-machine off switch (`runner.sandbox.off` in `~/.abcd.noindex/config.json`) takes the place of degradation. Every other - route still needs no external tool. Until the sign-off this consequence is - a proposal, not part of the decision. + route still needs no external tool. Where `srt` is missing, abcd offers to + install it and installs only after the person's explicit yes + (dcn-2610222041424027). diff --git a/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md b/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md index 877cf7019a..fd35ef43f0 100644 --- a/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md +++ b/.abcd/development/decisions/adrs/2610161621415708-abcd-s-command-guard-steers-agents-an-os.md @@ -59,5 +59,5 @@ We will decide limiting the network as a separate, later record. - Platform limits become the intent's scope conditions: Go tools' TLS under Seatbelt, git over SSH behind a proxy, the keychain credential helper, `sandbox-exec`'s deprecation on macOS, nested bubblewrap, and Ubuntu's restriction on unprivileged user namespaces. - A brief invariant states the rule once this record is accepted. - The boundary sits behind a seam (`internal/core/fence`): a fence-neutral policy and one interface each mechanism implements, so the mechanism the spec picks (sandbox-runtime, `spc-2610161751056096`) can be replaced by another without changing the runner or the loop. -- The mechanism is a program abcd does not ship, and a boundary has no native default to degrade to, so on the runner route it is a requirement: [adr-22](0022-bundled-deps-as-pluggable-adapters.md)'s first declared exception, proposed until the product thinker signs it off, with the person's off switch in their user layer in place of degradation. -- A signing key is not opened to a confined agent. Where the person's commits are signed, the spec proposes that a confined agent commits unsigned and abcd signs outside the boundary when it lands the work, a question for the product thinker. +- The mechanism is a program abcd does not ship, and a boundary has no native default to degrade to, so on the runner route it is a requirement: [adr-22](0022-bundled-deps-as-pluggable-adapters.md)'s first declared exception, signed off by the product thinker on 2026-10-22 (dcn-2610222041417934), with the person's off switch in their user layer in place of degradation. +- A signing key is not opened to a confined agent. Where the person's commits are signed, a confined agent commits unsigned and abcd signs outside the boundary when it lands the work (the product thinker's ruling of 2026-10-22, dcn-2610222041439951). diff --git a/.abcd/development/intents/planned/itd-2610161621364075-a-person-who-leaves-abcd-to-run-on-its.md b/.abcd/development/intents/planned/itd-2610161621364075-a-person-who-leaves-abcd-to-run-on-its.md index ffa607d1f2..87a5db58c4 100644 --- a/.abcd/development/intents/planned/itd-2610161621364075-a-person-who-leaves-abcd-to-run-on-its.md +++ b/.abcd/development/intents/planned/itd-2610161621364075-a-person-who-leaves-abcd-to-run-on-its.md @@ -21,7 +21,7 @@ Typed links: `builds_on` [itd-2609091014076309](itd-2609091014076309-session-and ## Press Release -> **When you leave abcd working through the night, the agents it starts on its own can change their own work and nothing else on your computer.** Each agent abcd launches runs inside a fence your computer's operating system enforces, not only abcd's own checks. The agent can edit the copy of the project it was given and the notes abcd keeps for that piece of work. Your other projects, your settings, and the choices you made about how abcd runs stay out of its reach. On a computer that cannot put up the fence, abcd says so before the first agent starts, so you never find out in the morning. +> **When you leave abcd working through the night, the agents it starts on its own can change their own work and nothing else on your computer.** Each agent abcd launches runs inside a fence your computer's operating system enforces, not only abcd's own checks. The agent can edit the copy of the project it was given and the notes abcd keeps for that piece of work. It cannot change your other projects, your settings, or the choices you made about how abcd runs, and it cannot read your passwords, keys and sign-ins. On a computer that cannot put up the fence, abcd says so before the first agent starts, so you never find out in the morning. > > "I used to leave a run going and wonder over breakfast what else it had touched," said Maya, an autonomous-development practitioner. "Now the agents abcd starts can only change the work they were given. The one night my laptop couldn't fence them in, abcd told me before it began, and the choice was mine." diff --git a/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md b/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md index fec70545ac..eefec1f6fd 100644 --- a/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md +++ b/.abcd/development/specs/open/spc-2610161751056096-a-person-who-leaves-abcd-to-run-on-its.md @@ -60,7 +60,7 @@ It also named two things to probe rather than assert, the macOS hard-link gap an An advisor's fit review the same day kept the pick again and changed the design in eight more places, each written in below: Go's checksum database and private modules, commit signing, automatic gc, the fence as a seam, the version policy, the approver's executable, srt as adr-22's first declared exception, and the order and split of the steps. -**The dependency.** abcd does not vendor srt and `go.mod` does not change. The person installs it once, like the harness it wraps. It is a new external program on the runner route, and on that route it is a requirement, not an adapter that degrades. [adr-22](../../decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md) makes every external tool an opt-in adapter over a native default that a missing tool degrades to. A fence has no native default to degrade to: abcd writes no fence of its own (ruling 6), and an unfenced launch is the very thing the intent removes. So srt is framed as adr-22's first declared exception, scoped to the runner route alone. Everywhere else abcd still needs no external program, and in place of degradation the person has the per-machine opt-out, `runner.sandbox.off` (ruling 7). The exception is written as a consequence on adr-22 and as one paragraph in the dependencies chapter (`02-constraints/02-dependencies.md`), both marked proposed until the product thinker signs off at step 2 (question a). +**The dependency.** abcd does not vendor srt and `go.mod` does not change. The person installs it once, like the harness it wraps. It is a new external program on the runner route, and on that route it is a requirement, not an adapter that degrades. [adr-22](../../decisions/adrs/0022-bundled-deps-as-pluggable-adapters.md) makes every external tool an opt-in adapter over a native default that a missing tool degrades to. A fence has no native default to degrade to: abcd writes no fence of its own (ruling 6), and an unfenced launch is the very thing the intent removes. So srt is framed as adr-22's first declared exception, scoped to the runner route alone. Everywhere else abcd still needs no external program, and in place of degradation the person has the per-machine opt-out, `runner.sandbox.off` (ruling 7). The exception is written as a consequence on adr-22 and as one paragraph in the dependencies chapter (`02-constraints/02-dependencies.md`), both signed off by the product thinker at step 2 on 2026-10-22 (question a, dcn-2610222041417934). **What the fence brings that part A did not ask for.** srt filters the network to a list of hosts, and it refuses `*` in that list ("Overly broad patterns like "*.com" or "*" are not allowed for security reasons", `sandbox-config.js` in 0.0.79). So a fenced launch reaches only the hosts it is given, whether or not part C has been built. The list here is derived for function, not as part C's promise: @@ -69,7 +69,7 @@ An advisor's fit review the same day kept the pick again and changed the design - with a `go.mod`, the hosts named by `GOPRIVATE` and `GONOPROXY`. Go fetches those modules straight from their version-control host, past every proxy, the file proxy below included, so without the host a private module cannot be fetched at all. The host part of each pattern is taken. A pattern whose host srt refuses as too broad is named at the check, and the person adds the concrete host in `runner.sandbox.hosts`; - the hosts the person adds in `runner.sandbox.hosts`, in their machine layer only. -A lane's agent never pushes: its brief says "Never push", and the land stage pushes from abcd, outside the fence (`landPush`, `internal/core/implement/loop/land.go`). So the forge is not on the list: only abcd's own push, outside the fence, reaches it. Whether a fenced launch may reach the network only through a host list, and whether this is the list, is question (c), answered at step 2. +A lane's agent never pushes: its brief says "Never push", and the land stage pushes from abcd, outside the fence (`landPush`, `internal/core/implement/loop/land.go`). So the forge is not on the list: only abcd's own push, outside the fence, reaches it. The product thinker chose this derived list at step 2 on 2026-10-22 (question c, dcn-2610222041427241). The run record's fence block keeps the list; the run does not print it at its start. ### The fence is a seam @@ -124,7 +124,7 @@ The agent commits in its own worktree: its brief tells it to (`internal/core/imp **Automatic gc.** After a commit git may run `git gc --auto`, through `git maintenance run --auto`, and that wants `packed-refs` and `.git/gc.log`, neither of which is on the write list. abcd's own git turns automatic maintenance off on its command line, but the agent's git is not abcd's, and the launch's environment drops every inherited `GIT_CONFIG_*` (`ScrubbedEnv`, `internal/gitutil/net.go:37`). So before each launch abcd writes `gc.auto=0` and `maintenance.auto=false` into the lane's `worktrees//config.worktree`, outside the fence. That file is on the deny list, so the agent cannot take the values out. Git reads `config.worktree` only in a repository with `extensions.worktreeConfig` on, and abcd never turns that on, since doing so rewrites the main `.git/config` and changes how the repository works. So the launch also carries both values as its own seal (`GIT_CONFIG_COUNT` with its keys and values, set over the scrubbed environment, `internal/core/runner/proc.go:168`), which holds in every repository. A gc the agent forces anyway is refused after its commit has landed, and is recorded like any other refusal. -**Commit signing.** Where the person's git signs every commit (`commit.gpgsign`), the agent's commit needs the signing key, and the read list denies `~/.gnupg` and `~/.ssh`. On Linux srt's seccomp filter also blocks the Unix socket a signing agent answers on. abcd's own commits sign as the person's commits do (`internal/core/implement/loop/pickcommit.go:19-21`), and that does not change. The key is not opened to the agent: an agent that can read it can sign anything as the person. The proposal is that fenced lanes commit unsigned, with `commit.gpgsign=false` in the launch's seal, and that abcd signs outside the fence, where it lands the work. Before its push, the land stage rewrites each lane commit with the same tree and message, in the same order, signed under the person's own configuration. Its judged-head check (`landPrepare`, `internal/core/implement/loop/land.go:198`) then compares the rewritten chain, tree for tree, with the head its validators judged. This is question (d), with its other answers, and none of it is built before the answer. +**Commit signing.** Where the person's git signs every commit (`commit.gpgsign`), the agent's commit needs the signing key, and the read list denies `~/.gnupg` and `~/.ssh`. On Linux srt's seccomp filter also blocks the Unix socket a signing agent answers on. abcd's own commits sign as the person's commits do (`internal/core/implement/loop/pickcommit.go:19-21`), and that does not change. The key is not opened to the agent: an agent that can read it can sign anything as the person. Fenced lanes commit unsigned (question d, dcn-2610222041439951), with `commit.gpgsign=false` in the launch's seal, and that abcd signs outside the fence, where it lands the work. Before its push, the land stage rewrites each lane commit with the same tree and message, in the same order, signed under the person's own configuration. Its judged-head check (`landPrepare`, `internal/core/implement/loop/land.go:198`) then compares the rewritten chain, tree for tree, with the head its validators judged. ### A machine without a fence @@ -136,6 +136,8 @@ Ruling 7: the run stops until the person acts. When a run is about to start and A failed admission or a failed trial refuses the run: exit 2, nothing created and nothing launched. The refusal names the reason (srt not found, refused, bubblewrap or user namespaces missing as srt reports, the trial failing to start, or the trial writing outside), how to fix it, and the off switch. +Where srt is not found, abcd offers to install it (question b, dcn-2610222041424027): it names the exact install it would run (srt's npm package at the tested version, and on Linux the system packages srt needs, through the system's package manager) and asks. It installs only after the person's explicit yes, given in the session or the terminal that started the run, never by an agent's answer and never by a flag or a config value. The install runs with everything the person's account can do, which the product thinker accepted. After it, the check runs again from the start, admission and trial included. Anything short of a yes installs nothing, and the run stays refused as above. + The off switch is `runner.sandbox.off: true` in `~/.abcd.noindex/config.json`. The `runner` namespace is already the machine's alone, and a repository's `.abcd/config.json` that sets it is refused, naming the machine's file (`runner.Load`). No flag sets it either, so a project's files and a command an agent can type can never turn the fence off. With it set, the run says once, at its start, that its launches are not fenced and where that was set. A wrap that fails during a run (srt removed, its settings refused) is a new runner reason, `fenced`. It is not a fallback: like a rate limit, it hands the role to no other route. The lane refuses at its runner stage with the same remedy. @@ -194,7 +196,7 @@ The `fence` lane is a Make target that joins `make preflight` beside `smoke` and - Every commit inside the fence, with git 2.52, prints `error: Unable to create '/packed-refs.lock': Operation not permitted` during its ref update, and still exits 0 with its commit, ref and reflog written (probe 2). `packed-refs` stays off the write list. `Fence.Refusals` records the line like any other refusal, and the lane does not read it as a failed commit. - On Linux, srt covers a protected path that does not yet exist with a placeholder on the host, and a SIGKILL skips its cleanup. abcd's timeout kill is the group's SIGKILL (`procgroup.Kill`), so a timeout first sends SIGTERM to srt and kills the group only after a grace period (step 8). After every launch abcd also removes, outside the fence, any empty placeholder that appeared at one of srt's protected names and that git does not track, so none is committed or left in the person's worktree (step 9). Step 1 checks whether bubblewrap's own session takes descendants out of the group abcd kills. - srt quotes the argument vector into a shell string on macOS and Linux, which amends `proc.go`'s rule that the argv is never a shell line. abcd's wrapped vector is fixed words, a role name from a closed list, and paths abcd made. A path carrying a quote or a control character is refused before the launch, and a test sends a prompt through with every shell metacharacter (step 7). -- The intent's press release says the person's other projects and settings "stay out of its reach". With this design that holds for changes. It holds for reads only where the read denies above reach. The wording is question (e). +- A fenced agent can still read most of what the person can read. The intent's press release was narrowed to what holds at step 2 (question e, dcn-2610222041434056): the agent cannot change anything outside its own work, and cannot read the person's passwords, keys and sign-ins (the read denies above, the macOS keychain included). ### Coordination @@ -213,7 +215,9 @@ The `fence` lane is a Make target that joins `make preflight` beside `smoke` and ## Questions for the product thinker -Put at step 2, after step 1's probes. Each question is written for the product thinker, in plain words, and each answer says what it leads to. The mechanism behind each one is in the sections above. Nothing is built before all five are answered. +Put at step 2, after step 1's probes. Each question is written for the product thinker, in plain words, and each answer says what it leads to. The mechanism behind each one is in the sections above. + +**Answered by the product thinker on 2026-10-22**, each recorded as a decision and folded into the sections above: (a) require srt (dcn-2610222041417934); (b) offer to install it, only on an explicit yes (dcn-2610222041424027); (c) the derived list, not announced per run (dcn-2610222041427241); (d) abcd signs at hand-in (dcn-2610222041439951); (e) narrow the words to what holds (dcn-2610222041434056). The questions as they were put stay below for the record. - **(a) One extra program to install.** Fencing the agents abcd starts needs one program abcd does not ship: Anthropic's sandbox. The person installs it once, with the small runtime it needs. Everything else in abcd keeps needing nothing extra. - Require it for the agents abcd starts. Leads to: on a machine without it, abcd will not start such an agent until the person installs it or switches the fence off for that machine. @@ -246,7 +250,7 @@ Put at step 2, after step 1's probes. Each question is written for the product t - packages: .abcd/development/research/notes, .abcd/development/intents - tests: an abcd lab on macOS and on Linux with bubblewrap, each probe named in the research note: `claude --print --bare` and `opencode run` wrapped by srt 0.0.79 with the step 3 policy written by hand; `CLAUDE_CONFIG_DIR` per launch with the person's `~/.claude` and `~/.claude.json` read-only; opencode with fresh XDG folders, a per-run cache seeded by clone, a read-only credential link, and a refreshing sign-in; `go build` and `go test ./...` of abcd itself with the per-run `GOPATH` and caches, a module missing from `go.sum` verified through the checksum database, a `GOPRIVATE` module fetched from its host, and whether module downloads pass TLS under Seatbelt; whether a keychain credential helper answers git inside the fence; a commit in a linked worktree under the narrowed main `.git`, with and without `gc.auto=0`, with `packed-refs` unwritable, and with `commit.gpgsign` on; a hard link from the worktree to a file outside it on macOS; SIGTERM cleanup, and whether bubblewrap's session leaves the process group abcd kills; what opencode fetches at start with a seeded cache; a prompt with shell metacharacters. Probing builds nothing, and its findings are what step 2's questions read. Each limit found becomes a scope condition on the intent or a capture of its own, and a failure on the macOS or Linux half goes back to the person at step 2, with nono as the named alternative - evidence: the macOS half's first probes ran on 2026-10-17 and are recorded under Step 1 evidence, after the steps. The Linux half, the agent sessions and the probes listed there as not yet run are still to do -2. Sign-off: the person answers the five questions for the product thinker (waits for the person; nothing is built before it) +2. Sign-off: the person answers the five questions for the product thinker (done 2026-10-22; see the answers under Questions for the product thinker) - packages: .abcd/development/brief, .abcd/development/decisions/adrs, .abcd/development/specs - tests: each answer recorded as a decision and folded into this spec; on a yes to question (a), the proposed marks come off adr-22's consequence and the dependencies chapter's paragraph (02-constraints/02-dependencies.md), and on a no both are withdrawn; record-lint and docs-lint pass over both and over the ADR's acceptance 3. Declare the fence seam and derive a launch's fence-neutral policy (itd-2610161621364075 criteria 1, 2, 3) diff --git a/.abcd/work/decisions/dcn-2610222041417934-anthropic-s-sandbox-srt-is-required-for.md b/.abcd/work/decisions/dcn-2610222041417934-anthropic-s-sandbox-srt-is-required-for.md new file mode 100644 index 0000000000..997fb3eba5 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222041417934-anthropic-s-sandbox-srt-is-required-for.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222041417934 +slug: anthropic-s-sandbox-srt-is-required-for +date: 2026-10-22 +--- +2026-10-22 — Anthropic's sandbox (srt) is required for the agents abcd starts on its own (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question a, itd-2610161621364075): every agent the runner launches runs inside srt, on macOS and Linux, and on a machine without it no such agent starts; this is adr-22's first declared exception, scoped to the runner route alone, and the per-machine off switch runner.sandbox.off stays the person's way out. The proposed marks on adr-22's consequence and on the dependencies chapter's paragraph come off diff --git a/.abcd/work/decisions/dcn-2610222041424027-when-srt-is-missing-abcd-offers-to.md b/.abcd/work/decisions/dcn-2610222041424027-when-srt-is-missing-abcd-offers-to.md new file mode 100644 index 0000000000..28965621c8 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222041424027-when-srt-is-missing-abcd-offers-to.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222041424027 +slug: when-srt-is-missing-abcd-offers-to +date: 2026-10-22 +--- +2026-10-22 — When srt is missing, abcd offers to install it and installs only after the person's explicit yes (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question b): the refusal before a run names the install it would make and asks; a yes runs the install with everything the person's account can reach, which the product thinker accepted; anything short of a yes installs nothing, and the run stays refused with the install command and the off switch named diff --git a/.abcd/work/decisions/dcn-2610222041427241-a-fenced-agent-may-reach-the-derived.md b/.abcd/work/decisions/dcn-2610222041427241-a-fenced-agent-may-reach-the-derived.md new file mode 100644 index 0000000000..144b95c739 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222041427241-a-fenced-agent-may-reach-the-derived.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222041427241 +slug: a-fenced-agent-may-reach-the-derived +date: 2026-10-22 +--- +2026-10-22 — A fenced agent may reach the derived host list, and the list is not announced at every run (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question c): the runner's model endpoint; with a go.mod, the GOPROXY and GOSUMDB hosts and the hosts GOPRIVATE and GONOPROXY name; and the hosts the person adds in runner.sandbox.hosts on that machine; the run record keeps the list, but the run does not print it at its start diff --git a/.abcd/work/decisions/dcn-2610222041434056-the-sandbox-intent-s-promise-is-narrowed.md b/.abcd/work/decisions/dcn-2610222041434056-the-sandbox-intent-s-promise-is-narrowed.md new file mode 100644 index 0000000000..0eafd5cc46 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222041434056-the-sandbox-intent-s-promise-is-narrowed.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222041434056 +slug: the-sandbox-intent-s-promise-is-narrowed +date: 2026-10-22 +--- +2026-10-22 — The sandbox intent's promise is narrowed to what holds (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question e, itd-2610161621364075): a fenced agent changes nothing outside its own work, and cannot read the person's passwords, keys and sign-ins (the credential stores the spec denies, the macOS keychain included); the press release is rewritten to say that, and no longer says the person's other projects and settings stay out of its reach, since a fenced agent can still read most of what the person can diff --git a/.abcd/work/decisions/dcn-2610222041439951-abcd-signs-a-fenced-lane-s-commits-when.md b/.abcd/work/decisions/dcn-2610222041439951-abcd-signs-a-fenced-lane-s-commits-when.md new file mode 100644 index 0000000000..ed8b91758b --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222041439951-abcd-signs-a-fenced-lane-s-commits-when.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222041439951 +slug: abcd-signs-a-fenced-lane-s-commits-when +date: 2026-10-22 +--- +2026-10-22 — abcd signs a fenced lane's commits when it hands the work in (product thinker's ruling of 2026-10-22, spc-2610161751056096 step 2 question d): lane commits are made unsigned inside the fence, with commit.gpgsign=false in the launch's seal; at land, outside the fence, abcd rewrites each lane commit with the same tree, message and order, signed under the person's own configuration, and the judged-head check compares the rewritten chain tree for tree with the head its validators judged, so the agent never holds the signing key From b077530d05c64707bc4805d20e50accc2ad8d999 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:48:28 +0100 Subject: [PATCH 08/10] chore: settle how an autonomous session's token is issued, on delegation A device-flow user access token in the person's name, with the refresh token kept on the person's computer outside every session; recorded as dcn-2610222048069284 from GitHub's current documentation, and folded into the coordination draft's open questions. Assisted-by: Claude:claude-opus-5-5 --- ...itd-2610170634394993-several-headless-agents-work-one.md | 2 +- ...10222048069284-a-session-s-own-token-is-a-user-access.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .abcd/work/decisions/dcn-2610222048069284-a-session-s-own-token-is-a-user-access.md diff --git a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md index 9bd51df8a8..5c004e93a8 100644 --- a/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md +++ b/.abcd/development/intents/drafts/itd-2610170634394993-several-headless-agents-work-one.md @@ -92,7 +92,7 @@ record) and on 2026-10-22 by the product thinker: Still open, for the product thinker's sign-off: - **Which unattended runs the login check covers.** The away mode keeps the person's own sessions working overnight on their own computer, where their login is saved. Either every unattended run is covered, so the person's own away-mode sessions move to a place without the login, or only the agents abcd launches on its own (build and drain roles) are, and the person's own sessions keep the login they run under. -- **How a session's token is issued, and whose name it carries.** A token issued for the person keeps their name and needs a stored refresh token and the App's client secret; an installation token is narrower and shorter-lived and shows as the App. Either way the issuing secret lives where no session can read it: on the person's computer, which then stays on for a run longer than the token lasts, or in a small service they run. dcn-2610180723332655 chose the person's token; where its issuing secret lives was left open and is not answered by it. +- **How a session's token is issued: settled 2026-10-22, on delegation** (dcn-2610222048069284). A user access token from the device flow, so no client secret ships anywhere; the refresh token stays on the person's computer, outside every session; each launch gets an eight-hour token, and a cloud session's pushes stop when it lapses, queued for the person's return. - **How far the shared model budget reaches.** Sessions on one computer can share a pool kept on that computer, as the load gate's slots already are; sessions on several computers or accounts would need the pool kept on the shared remote, which costs a push each time a sub-agent starts or ends. The scope condition above says the model budget spans every account of one organisation; the product thinker's answer of 2026-10-22 said "shared across sessions" without naming the reach. diff --git a/.abcd/work/decisions/dcn-2610222048069284-a-session-s-own-token-is-a-user-access.md b/.abcd/work/decisions/dcn-2610222048069284-a-session-s-own-token-is-a-user-access.md new file mode 100644 index 0000000000..2b45270adb --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222048069284-a-session-s-own-token-is-a-user-access.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222048069284 +slug: a-session-s-own-token-is-a-user-access +date: 2026-10-22 +--- +2026-10-22 — A session's own token is a user access token from the device flow, and its refresh token stays on the person's computer (facilitator on the product thinker's delegation of 2026-10-14 for technical choices grounded in the state of the art, itd-2610170634394993; keeps dcn-2610180723332655, the person's name marked as made through abcd, which the product thinker accepted in the 2026-10-22 C3 question): the person authorises abcd's GitHub App once through the device flow (a code entered in the browser), which needs only the App's client id, so no client secret is shipped to any machine, and a device-flow token is refreshed without the secret; the refresh token, which lasts six months and is replaced at each refresh, is the only issuing secret, and it is kept on the person's computer in the system's credential store (the macOS keychain, or a file only the person's account can read under ~/.abcd.noindex on Linux), outside every session and on the fence's read-deny list; at each unattended launch abcd on that computer refreshes and hands the session only the eight-hour access token, through abcd's own credential channel; a container or a separate user account on the same computer is handed a fresh token before the old one lapses, and a cloud session cannot be reached after launch, so its pushes stop when its token lapses and its remaining pushes are queued as owed work for the person's return or a relaunch, which the walk states; the token reaches only the repositories the App is installed on, so abcd asks for the App to be installed on the one repository; the installation token stays the fallback behind the forgecred seam, with the commit author still the person, should the device flow be withdrawn (grounds: docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app; docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens; docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app) From 8f5a948a6f8aac8a4c3c884bea36e69cad8af492 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:17:07 +0100 Subject: [PATCH 09/10] docs: promote the plain-terminal drain to an intent and record its decisions iss-2610222000271964, which the product thinker asked to plan now, becomes itd-2610222215485265, refining the shipped drain bundle: typing abcd drain in a plain terminal works through the ledger on its own. The draft carries a facilitator-proposed press release, mechanism, scope and criteria for the product thinker's walk, and five technical decisions on delegation (one intent, a loop inside the verb, host detection, one consent per drain, runner defaults while driving). The calibration note grades the routing FILE-AS-IS, proposed. Not planned: the walk comes first. Refs: iss-2610222000271964 Assisted-by: Claude:claude-opus-5-5 --- .../brief/06-delivery/03-out-of-scope.md | 1 + ...65-abcd-drain-run-from-a-plain-terminal.md | 74 +++++++++++++++++++ .../2026-08-15-decomposition-calibration.md | 13 ++++ ...-the-plain-terminal-drain-is-one-intent.md | 6 ++ ...-the-plain-terminal-drain-s-driver-is-a.md | 6 ++ ...a-plain-terminal-drain-detects-its-host.md | 6 ++ ...a-plain-terminal-drain-asks-once-at-its.md | 6 ++ ...while-a-plain-terminal-drain-drives-the.md | 6 ++ ...64-abcd-drain-run-from-a-plain-terminal.md | 5 ++ 9 files changed, 123 insertions(+) create mode 100644 .abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md create mode 100644 .abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md create mode 100644 .abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md create mode 100644 .abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md create mode 100644 .abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md create mode 100644 .abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md diff --git a/.abcd/development/brief/06-delivery/03-out-of-scope.md b/.abcd/development/brief/06-delivery/03-out-of-scope.md index a80fb6c901..ff3d3afbb9 100644 --- a/.abcd/development/brief/06-delivery/03-out-of-scope.md +++ b/.abcd/development/brief/06-delivery/03-out-of-scope.md @@ -150,6 +150,7 @@ That is what keeps "not hand-counted" true after the day it was written. - `itd-2610170634394993` — Headless agents in parallel coordinate through the shared remote, not conversation - `itd-2610170638184486` — Every session keeps a checklist on disk, so no run stalls and no stall spreads - `itd-2610222029228127` — Every session's checklist reaches a shared copy and the phone +- `itd-2610222215485265` — Typing abcd drain in a plain terminal works through the ledger on its own **Later-phase items with no intent id.** These four were written into the brief diff --git a/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md b/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md new file mode 100644 index 0000000000..645894f879 --- /dev/null +++ b/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md @@ -0,0 +1,74 @@ +--- +id: itd-2610222215485265 +slug: abcd-drain-run-from-a-plain-terminal +spec_id: null +kind: null +suggested_kind: null +reclassification_history: [] +builds_on: [] +severity: minor +related_issues: [iss-2610222000271964] +related_intents: [itd-2610161621364075, itd-2610170634394993, itd-2610100704270254] +refines: [itd-2610161016401115] +origin: extracted-from-record +production_mode: hand-written +--- + +# Typing abcd drain in a plain terminal works through the ledger on its own + +## Press Release + +> **Type `abcd drain` in a plain terminal and it works through the ledger on its own.** Frank opens a terminal on a spare machine and types `abcd drain`. abcd asks him once whether it should start Claude sessions itself to judge each fix and work each lane, using his model budget. He says yes. The terminal then shows one line per step (an issue judged, a lane opened, its review passed, its pull request queued) and moves on to the next. It stops at its cap, when nothing eligible is left, or when Frank presses Ctrl-C, and it hands back cleanly whatever it was holding. Frank never answers anything by hand, never keeps a second agent session open, and never runs the command again and again. +> +> _Facilitator-proposed on 2026-10-22 from iss-2610222000271964, for the product thinker's sign-off._ + +## Why This Matters + +On 2026-10-22, after the crash, the product thinker typed `abcd drain` in a plain terminal on a second machine. It reserved the first of 213 eligible issues, wrote a judgement request and waited for an agent session that never came. It opened no lanes and left the issue reserved for two hours (iss-2610222000271964). + +The shipped drain bundle (itd-2610161016401115) promised a drain that "runs unattended on a hosted session or a plain terminal for a day". It delivered the pieces: a judge routable to a runner, and lane roles that `implement step` starts through the runner. But nothing loops. Each `abcd drain` is one move, by design, for a host that calls it again, and a plain terminal has no such host. This intent closes that gap, so it `refines` the shipped one rather than reopening it. + +## Mechanism + +_Facilitator-proposed._ + +We expect a terminal drain to work through issues with no person and no host session. Every step that today waits for a host is either a call abcd already knows how to make itself (the routed judge, the runner-started roles) or a wait that resumes by itself, so the driver only has to repeat what a host repeats. This is shown wrong if a terminal drain, started on a machine with the runner set up and left for eight hours, opens fewer lanes than the same drain driven by a host session in the same time, for a reason other than its cap. + +## Scope Conditions + +_Facilitator-proposed._ + +- Holds on a machine where the claude runner is set up and admitted (`runner.claude`), within the model budget abcd's sub-agent setting allows. +- Holds for one drain per working copy, which the drain's lock already enforces. +- Holds while the terminal stays open, or under a tool the person chooses to keep it open (`tmux`, `nohup`); abcd starts no background service. +- Once the sandbox (itd-2610161621364075) ships, it holds only where its fence check passes; once the coordination intent (itd-2610170634394993) ships, only where its login check allows the run (that intent's open choice 1). + +## Acceptance Criteria + +_Facilitator-proposed on 2026-10-22, for the product thinker's walk; none confirmed._ + +- **Given** Frank types `abcd drain` in a plain terminal with no host session, **when** the drain would need a judgement or a lane step, **then** abcd asks him once whether to start Claude sessions itself, naming that they use his model budget, and starts none without his yes; `--drive` gives the yes in advance for a scripted start, and a non-interactive start without it does what it does today, one move and exit. +- **Given** Frank said yes, **when** the drain runs, **then** it judges each eligible issue through the runner, opens its lane, steps the lane until it lands or is handed back, and moves to the next, with no further input, printing one line per step and logging the detail under the local tier. +- **Given** a terminal drain, **when** it reaches its cap, finds nothing eligible, or Frank presses Ctrl-C, **then** it stops, releases every reservation it holds that no open lane needs, and says what it did; a second Ctrl-C stops it at once, as today. +- **Given** a judgement or a lane step whose runner fails, times out or answers out of shape, **when** the driver meets it, **then** that issue is handed back with the reason and the drain moves on; it never waits for a person to answer by hand. +- **Given** the machine's load gate or the sub-agent budget is full, **when** the driver would start a role, **then** it waits under the existing deadline and pacing and says so, never starting beyond them. +- **Given** a host session is driving the drain, **when** `abcd drain` runs, **then** it behaves exactly as today, one move per call. +- **Given** the routed roles, **when** the terminal drain starts them, **then** each launch goes through the runner's one launch point, so the guard approver, and once shipped the sandbox's fence, apply as they do for `abcd build`. + +## Open Questions + +_None for the product thinker; each technical point is recorded as a decision below. The criteria are walked at the sign-off._ + +## Decisions + +Each is the facilitator's, on the product thinker's delegation of 2026-10-14 and 2026-10-16 for technical choices, recorded with `abcd decide` on 2026-10-22: + +1. One intent, refining the shipped drain bundle (itd-2610161016401115) rather than reopening it, and FILE-AS-IS through the decomposition table: the fence, the routed judge's isolation and consent are existing rules, cited. +2. The driver is a loop inside `abcd drain`, not a new verb and not a background service. +3. A host is detected from the environment Claude Code sets for its shell (`CLAUDECODE=1`), and `--drive` and `--one-move` override the detection. +4. Consent to start agents is asked once per drain at its first launch and never remembered across drains; `--drive` is the scripted yes. +5. While driving, the judge and every lane role default to the claude runner wherever the person's config routes no role; a role routed elsewhere keeps its route. + +## Audit Notes + +_Empty. Populated by intent-auditor when intent moves to shipped/._ diff --git a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md index f18fa2f2be..31e9bfddc5 100644 --- a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md +++ b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md @@ -2495,6 +2495,19 @@ After filing, two adversarial reviews (design and record discipline, each an Opu The SOTA survey was filed as research/notes/2026-10-16-model-matrix-sota.md, and itd-166 was widened to the asked and served sides it was routed. The routing survived as a five-record split; its plumbing row was the part the initial routing got wrong. +## 2026-10-22: abcd drain from a plain terminal (facilitator-on-delegation, routing not yet put to the product thinker) + +| part | type | home | +|---|---|---| +| abcd drain in a plain terminal drives itself: repeats its moves, steps each lane, starts every role through the runner, stops at its cap, an empty ledger or Ctrl-C | capability | intent itd-2610222215485265, promoted from iss-2610222000271964, refining the shipped itd-2610161016401115 | +| the agents it starts are fenced by the operating system | trust rule | cited: itd-2610161621364075 part A covers every runner launch | +| a routed judge's answer steers an unattended lane, so it runs alone in its own directory | trust rule | cited: the shipped judge's directory and grant rules | +| starting agents spends the person's model budget, so none starts without their yes | stance | cited: the consent rules already in force | +| where an unattended terminal drain may run, given the product thinker's C1 answer | cited | the coordination intent's open choice 1; not decided here | +| the loop, its log, its lock and its signals | plumbing | the brief's drain chapter, at planning | + +Verdict: FILE-AS-IS, proposed. Typed links: `refines` itd-2610161016401115, whose press release promised a plain-terminal drain and whose delivery stopped at one move per call. Advisory reversal flags: none. + ## 2026-10-22: the checklist and coordination routings, confirmed by the product thinker's answers The 2026-10-18 checklist routing (below) was graded unconfirmed until the diff --git a/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md b/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md new file mode 100644 index 0000000000..b8c03119af --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222216346448 +slug: the-plain-terminal-drain-is-one-intent +date: 2026-10-22 +--- +2026-10-22 — The plain-terminal drain is one intent that refines the shipped drain bundle (facilitator, on the product thinker's delegation of 2026-10-14 and 2026-10-16, itd-2610222215485265): the decomposition table routes it FILE-AS-IS, since its fence (itd-2610161621364075), its routed judge's isolation (commands/drain.md) and the consent to start agents are rules already in force, cited rather than restated; it refines itd-2610161016401115, whose press release promised a plain-terminal drain and which delivered the routed judge but no loop, rather than reopening a shipped record (grounds: the decomposition-calibration note under 2026-10-22; itd-84) diff --git a/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md b/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md new file mode 100644 index 0000000000..147f6cf296 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222216350228 +slug: the-plain-terminal-drain-s-driver-is-a +date: 2026-10-22 +--- +2026-10-22 — The plain-terminal drain's driver is a loop inside abcd drain, not a new verb and not a background service (facilitator, on delegation, itd-2610222215485265): the loop repeats the moves a host repeats, so the move logic stays one code path, and the terminal session is its lifetime, so nothing runs that the person did not start and cannot see (grounds: dcn-2610180720366282, no process that must stay alive; commands/drain.md, one move per call) diff --git a/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md b/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md new file mode 100644 index 0000000000..adfa18db09 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222216362392 +slug: a-plain-terminal-drain-detects-its-host +date: 2026-10-22 +--- +2026-10-22 — A plain-terminal drain detects its host from the environment Claude Code sets for its shell (facilitator, on delegation, itd-2610222215485265): CLAUDECODE=1, observed live in a Claude Code shell on 2026-10-22, marks a host session and keeps today's one move per call; its absence with a terminal on standard input marks a plain terminal; --drive and --one-move override the detection, so a host abcd cannot detect still has a way in; the variable is checked against Claude Code's settings documentation when the spec is written (grounds: the live observation; the principle that a hint is never the only route) diff --git a/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md b/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md new file mode 100644 index 0000000000..3329391d97 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222216363556 +slug: a-plain-terminal-drain-asks-once-at-its +date: 2026-10-22 +--- +2026-10-22 — A plain-terminal drain asks once, at its first launch, before starting any agent, and never remembers the answer across drains (facilitator, on delegation, itd-2610222215485265): starting agents spends the person's model budget, and the consent rules already in force ask a yes for an act like that; one question per drain keeps the product thinker's no-manual-steps ruling, and --drive is the scripted yes for a start with no terminal (grounds: the setup-consent rule that nothing changes without a yes; the no-manual-steps ruling of 2026-10-15) diff --git a/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md b/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md new file mode 100644 index 0000000000..a031d39813 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222216373604 +slug: while-a-plain-terminal-drain-drives-the +date: 2026-10-22 +--- +2026-10-22 — While a plain-terminal drain drives, the judge and every lane role default to the claude runner wherever the person's config routes no role (facilitator, on delegation, itd-2610222215485265): a role the person routed elsewhere keeps its route, an opencode route for the judge stays skipped as today, and a judge that cannot run hands the issue back rather than waiting for an answer by hand (grounds: commands/drain.md on the routed judge; commands/build.md on runner roles) diff --git a/.abcd/work/issues/open/iss-2610222000271964-abcd-drain-run-from-a-plain-terminal.md b/.abcd/work/issues/open/iss-2610222000271964-abcd-drain-run-from-a-plain-terminal.md index 7dc95915ef..414d0b9f13 100644 --- a/.abcd/work/issues/open/iss-2610222000271964-abcd-drain-run-from-a-plain-terminal.md +++ b/.abcd/work/issues/open/iss-2610222000271964-abcd-drain-run-from-a-plain-terminal.md @@ -10,6 +10,11 @@ origin: researcher-authored production_mode: hand-written found_at: "commands/drain.md" remedy: "when no host session drives the drain, abcd drain launches claude sessions itself through the runner abcd already uses to start agents, one per judgement and lane step, and loops until its cap or the ledger is done" +related_intents: [itd-2610222215485265] --- abcd drain run from a plain terminal stops at its first move: it reserves the first eligible issue, writes a host-judgement request, and waits for an agent session to answer it, so a person who types abcd drain in a terminal gets no fixes (seen 2026-10-22 20:47 BST on a second machine: 213 eligible, 0 lanes opened, iss-2610120138028442 reserved for 2h and left). The person wants abcd drain to work from the plain terminal by launching claude sessions itself to judge each remedy and drive each lane. + +## Grounds + +- pursued: typing abcd drain in a plain terminal should work through the ledger with no host session; shown wrong if a terminal drain left for eight hours with the runner set up opens fewer lanes than a host-driven drain in the same time, for a reason other than its cap From edca0506d0a675be4ea05fefb646768b70ef6ef1 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Thu, 22 Oct 2026 23:25:05 +0100 Subject: [PATCH 10/10] docs: revise the plain-terminal drain after its Opus and Sonnet review round 1 The first routing graded it FILE-AS-IS with no reversal flag; the code already names this path as a reversal of the host-delegated boundary, so the record it owes is filed as adr-2610222222326104 (proposed): a drive with no host starts only on the person's own invocation, on their paid key, bounded, and hands back what it cannot finish. The intent now enters driving only by --drive, asks before its first move naming the cap, the estimate and the key, runs a lane's preflight itself, turns a runner fallback into a stuck hand-back, waits out runner outages without handing anything back, sleeps on the move's own waits, and leaves a lane resumable on Ctrl-C. Three superseded decisions are replaced and three added; two real choices (the paid key, and where a drive may run under C1) go to the product thinker's walk. Assisted-by: Claude:claude-opus-5-5 --- .../brief/06-delivery/03-out-of-scope.md | 2 +- ...abcd-drives-agents-with-no-host-session.md | 57 ++++++++++++++++ .abcd/development/decisions/adrs/README.md | 1 + ...65-abcd-drain-run-from-a-plain-terminal.md | 65 ++++++++++++------- .../2026-08-15-decomposition-calibration.md | 6 +- ...-the-plain-terminal-drain-is-one-intent.md | 2 +- ...-the-plain-terminal-drain-s-driver-is-a.md | 2 +- ...a-plain-terminal-drain-detects-its-host.md | 6 -- ...a-plain-terminal-drain-asks-once-at-its.md | 6 -- ...while-a-plain-terminal-drain-drives-the.md | 6 -- ...0756-a-drive-asks-before-its-first-move.md | 6 ++ ...67-while-driving-a-role-the-person-left.md | 6 ++ ...-drain-drives-itself-only-when-drive-is.md | 6 ++ ...666-ctrl-c-or-a-hang-up-stops-a-drive-s.md | 6 ++ ...-a-drive-runs-a-lane-s-preflight-itself.md | 6 ++ ...0-a-runner-fallback-while-driving-hands.md | 6 ++ 16 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 .abcd/development/decisions/adrs/2610222222326104-abcd-drives-agents-with-no-host-session.md delete mode 100644 .abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md delete mode 100644 .abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md delete mode 100644 .abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md create mode 100644 .abcd/work/decisions/dcn-2610222224040756-a-drive-asks-before-its-first-move.md create mode 100644 .abcd/work/decisions/dcn-2610222224040967-while-driving-a-role-the-person-left.md create mode 100644 .abcd/work/decisions/dcn-2610222224049799-a-drain-drives-itself-only-when-drive-is.md create mode 100644 .abcd/work/decisions/dcn-2610222224054666-ctrl-c-or-a-hang-up-stops-a-drive-s.md create mode 100644 .abcd/work/decisions/dcn-2610222224055876-a-drive-runs-a-lane-s-preflight-itself.md create mode 100644 .abcd/work/decisions/dcn-2610222224056700-a-runner-fallback-while-driving-hands.md diff --git a/.abcd/development/brief/06-delivery/03-out-of-scope.md b/.abcd/development/brief/06-delivery/03-out-of-scope.md index ff3d3afbb9..b6c327b6d9 100644 --- a/.abcd/development/brief/06-delivery/03-out-of-scope.md +++ b/.abcd/development/brief/06-delivery/03-out-of-scope.md @@ -150,7 +150,7 @@ That is what keeps "not hand-counted" true after the day it was written. - `itd-2610170634394993` — Headless agents in parallel coordinate through the shared remote, not conversation - `itd-2610170638184486` — Every session keeps a checklist on disk, so no run stalls and no stall spreads - `itd-2610222029228127` — Every session's checklist reaches a shared copy and the phone -- `itd-2610222215485265` — Typing abcd drain in a plain terminal works through the ledger on its own +- `itd-2610222215485265` — abcd drain --drive works through the ledger from a plain terminal **Later-phase items with no intent id.** These four were written into the brief diff --git a/.abcd/development/decisions/adrs/2610222222326104-abcd-drives-agents-with-no-host-session.md b/.abcd/development/decisions/adrs/2610222222326104-abcd-drives-agents-with-no-host-session.md new file mode 100644 index 0000000000..16b5549d7c --- /dev/null +++ b/.abcd/development/decisions/adrs/2610222222326104-abcd-drives-agents-with-no-host-session.md @@ -0,0 +1,57 @@ +--- +id: adr-2610222222326104 +slug: abcd-drives-agents-with-no-host-session +status: proposed +date: 2026-10-22 +supersedes: null +superseded_by: null +related_intents: [itd-2610222215485265, itd-2609201916151817, itd-2609201916056194, itd-2610161016401115] +related_rfcs: [] +related_adrs: [adr-25] +--- + +# ADR-2610222222326104: abcd drives agents with no host session only on the person's own invocation, on a paid key, and hands back what it cannot finish + +## Context + +[adr-25](0025-host-delegated-llm-default.md) makes the language model host-delegated by default: abcd prepares the work, and the person's host session starts the agents. Runners (itd-2609201916056194) are opt-in adapters a role is routed to. A host session still drives every step today. `abcd implement step` and each `abcd drain` move return to the host between steps. + +itd-2609201916151817's decision 5 flagged a process driver as a reversal of that boundary. In that driver, abcd runs alone and starts every agent itself. The product thinker confirmed it for the opt-in path only, and the decision asked for a record before any such path ships. `drive.go`'s header says the same: the no-host path owes this record. + +On 2026-10-22 the product thinker asked for exactly that path: typing `abcd drain` in a plain terminal should work through the ledger by starting Claude sessions itself (iss-2610222000271964, itd-2610222215485265). Two earlier rulings bound it: +- **RN2 (2026-10-02):** the claude runner runs in print mode with the bare flag on an API key, so a role reaches a runner only on the person's own route, and a subscription account runs roles in its own session. Its grounds are Claude Code's CLI reference and legal and compliance pages. +- **Coordination C1 (2026-10-22):** unattended runs happen only where the person's forge login is absent. Which runs that covers is still open in that intent's walk. + +## Decision + +abcd drives agents with no host session only under four conditions. + +1. **On the person's own invocation.** + - A no-host drive starts only from a command the person typed in a terminal, after a yes given at that terminal, or with an explicit flag that is the scripted yes. + - The yes covers that one drive and is never remembered. + - A repository's configuration can never start one or widen one. +2. **On a paid key, through the person's own route.** + - Every agent a no-host drive starts runs through a runner the person enabled on their own machine: the role's own route, or `runner.fallback_host` for a role they left unrouted. So per RN2 it spends their API key, never a subscription session. + - The yes names that key, the cap on the work, and an estimate of the agent runs it allows. + - A role a repository pins to the host has nowhere to run without a host, so the drive refuses to start and names that role. +3. **Bounded.** + - A no-host drive has a cap, a default one when none is given. + - It ends at the cap, at an empty queue, or on the person's signal. + - Every wait inside it has a deadline (adr-2610222026262980, rule 1). +4. **What it cannot finish goes back.** + - A role that falls back with no host to take it is a step the drive cannot finish, so its work is handed back with the reason. + - A run of runner failures in a row is treated as an outage, which the drive waits through and then stops. It never hands work back permanently for a failure that may be passing. + +## Alternatives Considered + +- **A no-host drive on these four conditions (chosen).** It gives the person what they asked for. It keeps the spend theirs and visible. It keeps a repository from starting one. And it never leaves work held for a host that is not coming. +- **Keep every agent host-started, and tell a terminal user to open a Claude session.** Rejected: this is what iss-2610222000271964 found, a drain that reserved an issue and waited for two hours. +- **Start agents on the person's subscription login rather than an API key.** Rejected by RN2, on its compliance grounds. +- **A background service that keeps drives running.** Rejected: no process must stay alive (dcn-2610180720366282). The terminal session is the drive's lifetime. + +## Consequences + +- `abcd drain` gains a driving mode. `abcd build` can adopt the same four conditions later, under this record. +- AGENTS.md's host-delegated boundary gains its first written exception. The boundary's text names this record once the record is accepted, which happens at the product thinker's sign-off of itd-2610222215485265. +- A person on a subscription alone cannot run a no-host drive. They run the drain from a Claude session, as today. The walk says so. +- The sandbox's fence (itd-2610161621364075) applies to every agent such a drive starts, since each one goes through the runner's one launch point. So does the coordination intent's login check, as far as its open choice 1 reaches. diff --git a/.abcd/development/decisions/adrs/README.md b/.abcd/development/decisions/adrs/README.md index ad16a63fb8..d940df7c85 100644 --- a/.abcd/development/decisions/adrs/README.md +++ b/.abcd/development/decisions/adrs/README.md @@ -218,4 +218,5 @@ The intent lint (a Go implementation) extends to verify these reciprocally. | [adr-2610181003541997](2610181003541997-the-away-mode-s-stop-check-reads-the.md) | The away mode's stop check reads the forge once, as its documented meaning | proposed | 2026-10-18 | | [adr-2610222026262980](2610222026262980-coordination-between-headless-agents-is.md) | Coordination between headless agents is bounded by deadlines, fenced by the claim's tip, version-checked, and done under each session's own credential | proposed | 2026-10-22 | | [adr-2610222026265719](2610222026265719-a-session-s-checklist-names-a-credential.md) | A session's checklist names a credential by its kind, scope and expiry, never its value or anything that retrieves it | accepted | 2026-10-22 | +| [adr-2610222222326104](2610222222326104-abcd-drives-agents-with-no-host-session.md) | abcd drives agents with no host session only on the person's own invocation, on a paid key, and hands back what it cannot finish | proposed | 2026-10-22 | diff --git a/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md b/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md index 645894f879..98ca49a009 100644 --- a/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md +++ b/.abcd/development/intents/drafts/itd-2610222215485265-abcd-drain-run-from-a-plain-terminal.md @@ -14,60 +14,75 @@ origin: extracted-from-record production_mode: hand-written --- -# Typing abcd drain in a plain terminal works through the ledger on its own +# abcd drain --drive works through the ledger from a plain terminal ## Press Release -> **Type `abcd drain` in a plain terminal and it works through the ledger on its own.** Frank opens a terminal on a spare machine and types `abcd drain`. abcd asks him once whether it should start Claude sessions itself to judge each fix and work each lane, using his model budget. He says yes. The terminal then shows one line per step (an issue judged, a lane opened, its review passed, its pull request queued) and moves on to the next. It stops at its cap, when nothing eligible is left, or when Frank presses Ctrl-C, and it hands back cleanly whatever it was holding. Frank never answers anything by hand, never keeps a second agent session open, and never runs the command again and again. +> **Type `abcd drain --drive` in a plain terminal and it works through the ledger on its own.** Frank opens a terminal on a spare machine and types `abcd drain --drive`. Before it touches anything, abcd tells him what it will do: work through up to eight issues, start Claude sessions on his API key, about forty agent runs at most. It asks him once. He says yes. The terminal then shows one line per step: an issue judged, a lane opened, its checks run, its pull request queued. Then it moves to the next. It stops at the cap, when nothing eligible is left, or when Frank presses Ctrl-C, and it tells him what it left and how to pick it up again. Whatever it cannot finish, it hands back with the reason. It never leaves an issue held while it waits for someone who isn't coming. > -> _Facilitator-proposed on 2026-10-22 from iss-2610222000271964, for the product thinker's sign-off._ +> _Facilitator-proposed on 2026-10-22 from iss-2610222000271964, for the product thinker's sign-off; revised after the Opus and Sonnet review round 1 the same night._ ## Why This Matters -On 2026-10-22, after the crash, the product thinker typed `abcd drain` in a plain terminal on a second machine. It reserved the first of 213 eligible issues, wrote a judgement request and waited for an agent session that never came. It opened no lanes and left the issue reserved for two hours (iss-2610222000271964). +On 2026-10-22, after the crash, the product thinker typed `abcd drain` in a plain terminal on a second machine. The drain reserved the first of 213 eligible issues, wrote a judgement request, and waited for an agent session that never came. It opened no lanes, and the issue stayed reserved for two hours (iss-2610222000271964). -The shipped drain bundle (itd-2610161016401115) promised a drain that "runs unattended on a hosted session or a plain terminal for a day". It delivered the pieces: a judge routable to a runner, and lane roles that `implement step` starts through the runner. But nothing loops. Each `abcd drain` is one move, by design, for a host that calls it again, and a plain terminal has no such host. This intent closes that gap, so it `refines` the shipped one rather than reopening it. +The shipped drain bundle (itd-2610161016401115) promised a drain that "runs unattended on a hosted session or a plain terminal for a day". It delivered a judge routable to a runner, and lane roles that `implement step` starts through a runner. But a host session still drives every step: each `abcd drain` is one move, a lane's preflight is run by the host, and a runner that fails hands its role to the host. In a plain terminal there is no host, so the first step that needs one waits for good. This intent closes that gap, so it `refines` the shipped one rather than reopening it. + +Doing so reverses the host-delegated boundary for an opt-in path. itd-2609201916151817's decision 5 flagged that reversal and the product thinker confirmed it for the opt-in path. The record it owes is adr-2610222222326104, proposed until this intent's sign-off. ## Mechanism _Facilitator-proposed._ -We expect a terminal drain to work through issues with no person and no host session. Every step that today waits for a host is either a call abcd already knows how to make itself (the routed judge, the runner-started roles) or a wait that resumes by itself, so the driver only has to repeat what a host repeats. This is shown wrong if a terminal drain, started on a machine with the runner set up and left for eight hours, opens fewer lanes than the same drain driven by a host session in the same time, for a reason other than its cap. +We expect a terminal drive to land work with no person and no host session, because each thing a host does between steps becomes an act the driver does itself, or a hand-back: +- it calls the next move; +- it runs a lane's preflight behind the load gate; +- it starts each role through the person's runner; +- it hands back what fails. + +The claim is shown wrong if a terminal drive on a scratch repository seeded with three minor issues, on a machine with the runner set up, needs any input after its one yes before it reaches its cap, or ends with an issue still reserved and no lane working on it. ## Scope Conditions _Facilitator-proposed._ -- Holds on a machine where the claude runner is set up and admitted (`runner.claude`), within the model budget abcd's sub-agent setting allows. -- Holds for one drain per working copy, which the drain's lock already enforces. -- Holds while the terminal stays open, or under a tool the person chooses to keep it open (`tmux`, `nohup`); abcd starts no background service. -- Once the sandbox (itd-2610161621364075) ships, it holds only where its fence check passes; once the coordination intent (itd-2610170634394993) ships, only where its login check allows the run (that intent's open choice 1). +- Holds on a machine where the person has enabled the claude runner with an API key (`runner.claude`, ruling RN2). A person with only a subscription runs the drain from a Claude session, as today. +- Holds for one drive per working copy, one lane at a time, as the drain's lock and its one-lane rule already enforce. +- Holds while the terminal stays open, or under a tool the person chooses to keep it running (`tmux`, `nohup`). abcd starts no background service. +- Once the sandbox (itd-2610161621364075) ships, it holds only where its fence check passes. Once the coordination intent ships, it holds only where that intent's login check lets an unattended run start (its open choice 1, and Open Questions below). ## Acceptance Criteria -_Facilitator-proposed on 2026-10-22, for the product thinker's walk; none confirmed._ +_Facilitator-proposed on 2026-10-22, revised after review round 1, for the product thinker's walk. None is confirmed._ -- **Given** Frank types `abcd drain` in a plain terminal with no host session, **when** the drain would need a judgement or a lane step, **then** abcd asks him once whether to start Claude sessions itself, naming that they use his model budget, and starts none without his yes; `--drive` gives the yes in advance for a scripted start, and a non-interactive start without it does what it does today, one move and exit. -- **Given** Frank said yes, **when** the drain runs, **then** it judges each eligible issue through the runner, opens its lane, steps the lane until it lands or is handed back, and moves to the next, with no further input, printing one line per step and logging the detail under the local tier. -- **Given** a terminal drain, **when** it reaches its cap, finds nothing eligible, or Frank presses Ctrl-C, **then** it stops, releases every reservation it holds that no open lane needs, and says what it did; a second Ctrl-C stops it at once, as today. -- **Given** a judgement or a lane step whose runner fails, times out or answers out of shape, **when** the driver meets it, **then** that issue is handed back with the reason and the drain moves on; it never waits for a person to answer by hand. -- **Given** the machine's load gate or the sub-agent budget is full, **when** the driver would start a role, **then** it waits under the existing deadline and pacing and says so, never starting beyond them. -- **Given** a host session is driving the drain, **when** `abcd drain` runs, **then** it behaves exactly as today, one move per call. -- **Given** the routed roles, **when** the terminal drain starts them, **then** each launch goes through the runner's one launch point, so the guard approver, and once shipped the sandbox's fence, apply as they do for `abcd build`. +- **Given** Frank types `abcd drain --drive` in a plain terminal, **when** it starts, **then** before its first move it shows the cap (eight unless `--max` says otherwise), an estimate of the agent runs that cap allows, and the API key and runner the agents will use, and starts nothing without his yes. Without a terminal, `--drive` alone is not enough; the scripted yes is `--drive --yes`. +- **Given** a machine where the runner is not enabled, the API key is missing, the judge is routed to opencode, or the repository pins a role to the host, **when** `abcd drain --drive` starts, **then** it refuses before its first move and names what is missing. It reserves nothing. +- **Given** Frank said yes, **when** the drive runs, **then** it judges each eligible issue through the runner, opens its lane, starts each role through the runner, runs the lane's preflight itself behind the load gate, lands it, and moves to the next, with no further input. It prints one line per step and logs the detail under the local tier. +- **Given** a role whose runner fails or answers out of shape during a lane, **when** the driver meets that fallback, **then** the lane is handed back as stuck with the runner's reason, its reservation is released, and the drive moves on. No lane is left waiting for a host. +- **Given** three runner failures in a row, or a judgement whose runner fails, **when** the driver meets them, **then** it treats them as an outage: it waits 1, 5 and 10 minutes, then hourly, saying so each time. It stops after eight hours of failures, and hands no issue back for an outage. +- **Given** the drain's own waits (its pace window, its cap wait, a wait for origin), **when** a move returns one, **then** the driver sleeps until the earliest time the move named, never less than thirty seconds, and holds the drain's lock only during a move. +- **Given** a drive, **when** Frank presses Ctrl-C or the terminal closes, **then** a running agent is stopped, the open lane is left where it stands with its reservation, and the drive prints the run, the lane and the time its reservation lapses, so the next `abcd drain --drive` or any host picks it up. A second Ctrl-C exits at once. +- **Given** `abcd drain` without `--drive`, **when** it runs, in a terminal or a host session, **then** it does what it does today, one move per call. The host's page always calls it that way. ## Open Questions -_None for the product thinker; each technical point is recorded as a decision below. The criteria are walked at the sign-off._ +Two choices for the product thinker. Both are in the walk. + +- **The drive spends an API key, not a subscription.** Under ruling RN2, an agent abcd starts runs on the person's own API key, billed per use. A person with only a subscription can't drive a drain from a terminal, and keeps running it from a Claude session. Is a pay-per-use key acceptable for terminal drives, or should drains stay in Claude sessions? +- **Where a drive may run, given the forge-login ruling.** The coordination intent's C1 answer keeps unattended runs off a computer where the person's GitHub login is saved, and a drive needs that login to reserve and push. This is the same question as that intent's open choice 1. A drive started and watched at the terminal may count as attended, so the reading chosen there decides where a drive may run. ## Decisions -Each is the facilitator's, on the product thinker's delegation of 2026-10-14 and 2026-10-16 for technical choices, recorded with `abcd decide` on 2026-10-22: +Each is the facilitator's, on the product thinker's delegation of 2026-10-14 and 2026-10-16 for technical choices, recorded with `abcd decide` on 2026-10-22 and revised after review round 1: -1. One intent, refining the shipped drain bundle (itd-2610161016401115) rather than reopening it, and FILE-AS-IS through the decomposition table: the fence, the routed judge's isolation and consent are existing rules, cited. -2. The driver is a loop inside `abcd drain`, not a new verb and not a background service. -3. A host is detected from the environment Claude Code sets for its shell (`CLAUDECODE=1`), and `--drive` and `--one-move` override the detection. -4. Consent to start agents is asked once per drain at its first launch and never remembered across drains; `--drive` is the scripted yes. -5. While driving, the judge and every lane role default to the claude runner wherever the person's config routes no role; a role routed elsewhere keeps its route. +1. One intent, refining the shipped drain bundle (itd-2610161016401115). The reversal of the host-delegated boundary it carries is recorded as adr-2610222222326104, proposed. +2. The driver is a loop inside `abcd drain`, entered only by `--drive`, not a new verb and not a background service. +3. Driving is never switched on by detecting the absence of a host. `--drive` is the only way in, so a host session or a terminal inherited from one never starts a drive by accident. +4. Consent comes before the first move, names the cap, the estimated agent runs, the runner and its key, and is never remembered. `--drive --yes` is the scripted yes. The default cap while driving is eight, the cap the product thinker's own drains use. +5. Roles the person left unrouted run on `runner.fallback_host`, the existing key for a run with no host session, or on the claude runner for this drive alone when the person's yes names it. A role the repository pins to the host refuses the start. +6. The driver runs the lane's preflight itself, through `abcd implement load --wait`, when the landing waits for a receipt. +7. A runner fallback while driving hands the lane back as stuck. Runner failures in a row are an outage the driver waits through, never a hand-back. +8. Ctrl-C or a hang-up stops the running agent and leaves the open lane resumable with its reservation, naming when the reservation lapses. Nothing is handed back for an interruption. ## Audit Notes diff --git a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md index 31e9bfddc5..2e0b9a7084 100644 --- a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md +++ b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md @@ -2499,14 +2499,14 @@ The SOTA survey was filed as research/notes/2026-10-16-model-matrix-sota.md, and | part | type | home | |---|---|---| -| abcd drain in a plain terminal drives itself: repeats its moves, steps each lane, starts every role through the runner, stops at its cap, an empty ledger or Ctrl-C | capability | intent itd-2610222215485265, promoted from iss-2610222000271964, refining the shipped itd-2610161016401115 | +| abcd drain --drive in a plain terminal drives itself: repeats its moves, steps each lane, runs its preflight, starts every role through the person's runner, stops at its cap, an empty ledger or Ctrl-C | capability | intent itd-2610222215485265, promoted from iss-2610222000271964, refining the shipped itd-2610161016401115 | +| abcd drives agents with no host session only on the person's own invocation, on a paid key, bounded, handing back what it cannot finish | trust rule | adr-2610222222326104 (proposed): the record itd-2609201916151817's decision 5 owed for the opt-in reversal of the host-delegated boundary | | the agents it starts are fenced by the operating system | trust rule | cited: itd-2610161621364075 part A covers every runner launch | | a routed judge's answer steers an unattended lane, so it runs alone in its own directory | trust rule | cited: the shipped judge's directory and grant rules | -| starting agents spends the person's model budget, so none starts without their yes | stance | cited: the consent rules already in force | | where an unattended terminal drain may run, given the product thinker's C1 answer | cited | the coordination intent's open choice 1; not decided here | | the loop, its log, its lock and its signals | plumbing | the brief's drain chapter, at planning | -Verdict: FILE-AS-IS, proposed. Typed links: `refines` itd-2610161016401115, whose press release promised a plain-terminal drain and whose delivery stopped at one move per call. Advisory reversal flags: none. +Verdict: SPLIT, proposed: the capability is the intent, the trust rule an ADR. Typed links: `refines` itd-2610161016401115, whose press release promised a plain-terminal drain and whose delivery stopped at one move per call. Advisory reversal flags: one, the host-delegated boundary in AGENTS.md (adr-25), reversed for this opt-in path as itd-2609201916151817's decision 5 anticipated; the first routing graded it FILE-AS-IS with no flag, and the Opus and Sonnet review round 1 found the flag the code already named (drive.go), so the initial routing did not survive. ## 2026-10-22: the checklist and coordination routings, confirmed by the product thinker's answers diff --git a/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md b/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md index b8c03119af..5921d551c9 100644 --- a/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md +++ b/.abcd/work/decisions/dcn-2610222216346448-the-plain-terminal-drain-is-one-intent.md @@ -3,4 +3,4 @@ id: dcn-2610222216346448 slug: the-plain-terminal-drain-is-one-intent date: 2026-10-22 --- -2026-10-22 — The plain-terminal drain is one intent that refines the shipped drain bundle (facilitator, on the product thinker's delegation of 2026-10-14 and 2026-10-16, itd-2610222215485265): the decomposition table routes it FILE-AS-IS, since its fence (itd-2610161621364075), its routed judge's isolation (commands/drain.md) and the consent to start agents are rules already in force, cited rather than restated; it refines itd-2610161016401115, whose press release promised a plain-terminal drain and which delivered the routed judge but no loop, rather than reopening a shipped record (grounds: the decomposition-calibration note under 2026-10-22; itd-84) +2026-10-22 — The plain-terminal drain is one intent that refines the shipped drain bundle (facilitator, on the product thinker's delegation of 2026-10-14 and 2026-10-16, itd-2610222215485265): the decomposition table routes it FILE-AS-IS, since its fence (itd-2610161621364075), its routed judge's isolation (commands/drain.md) and the consent to start agents are rules already in force, cited rather than restated; it refines itd-2610161016401115, whose press release promised a plain-terminal drain and which delivered the routed judge but no loop, rather than reopening a shipped record; revised after review round 1: the path reverses the host-delegated boundary for an opt-in use, as itd-2609201916151817's decision 5 flagged, so that reversal is recorded as adr-2610222222326104, proposed until the sign-off (grounds: the decomposition-calibration note under 2026-10-22; itd-84) diff --git a/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md b/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md index 147f6cf296..d9014967f6 100644 --- a/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md +++ b/.abcd/work/decisions/dcn-2610222216350228-the-plain-terminal-drain-s-driver-is-a.md @@ -3,4 +3,4 @@ id: dcn-2610222216350228 slug: the-plain-terminal-drain-s-driver-is-a date: 2026-10-22 --- -2026-10-22 — The plain-terminal drain's driver is a loop inside abcd drain, not a new verb and not a background service (facilitator, on delegation, itd-2610222215485265): the loop repeats the moves a host repeats, so the move logic stays one code path, and the terminal session is its lifetime, so nothing runs that the person did not start and cannot see (grounds: dcn-2610180720366282, no process that must stay alive; commands/drain.md, one move per call) +2026-10-22 — The plain-terminal drain's driver is a loop inside abcd drain, entered only by --drive, not a new verb and not a background service (facilitator, on delegation, itd-2610222215485265): the loop repeats the moves a host repeats, so the move logic stays one code path, and the terminal session is its lifetime, so nothing runs that the person did not start and cannot see (grounds: dcn-2610180720366282, no process that must stay alive; commands/drain.md, one move per call) diff --git a/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md b/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md deleted file mode 100644 index adfa18db09..0000000000 --- a/.abcd/work/decisions/dcn-2610222216362392-a-plain-terminal-drain-detects-its-host.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: dcn-2610222216362392 -slug: a-plain-terminal-drain-detects-its-host -date: 2026-10-22 ---- -2026-10-22 — A plain-terminal drain detects its host from the environment Claude Code sets for its shell (facilitator, on delegation, itd-2610222215485265): CLAUDECODE=1, observed live in a Claude Code shell on 2026-10-22, marks a host session and keeps today's one move per call; its absence with a terminal on standard input marks a plain terminal; --drive and --one-move override the detection, so a host abcd cannot detect still has a way in; the variable is checked against Claude Code's settings documentation when the spec is written (grounds: the live observation; the principle that a hint is never the only route) diff --git a/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md b/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md deleted file mode 100644 index 3329391d97..0000000000 --- a/.abcd/work/decisions/dcn-2610222216363556-a-plain-terminal-drain-asks-once-at-its.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: dcn-2610222216363556 -slug: a-plain-terminal-drain-asks-once-at-its -date: 2026-10-22 ---- -2026-10-22 — A plain-terminal drain asks once, at its first launch, before starting any agent, and never remembers the answer across drains (facilitator, on delegation, itd-2610222215485265): starting agents spends the person's model budget, and the consent rules already in force ask a yes for an act like that; one question per drain keeps the product thinker's no-manual-steps ruling, and --drive is the scripted yes for a start with no terminal (grounds: the setup-consent rule that nothing changes without a yes; the no-manual-steps ruling of 2026-10-15) diff --git a/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md b/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md deleted file mode 100644 index a031d39813..0000000000 --- a/.abcd/work/decisions/dcn-2610222216373604-while-a-plain-terminal-drain-drives-the.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -id: dcn-2610222216373604 -slug: while-a-plain-terminal-drain-drives-the -date: 2026-10-22 ---- -2026-10-22 — While a plain-terminal drain drives, the judge and every lane role default to the claude runner wherever the person's config routes no role (facilitator, on delegation, itd-2610222215485265): a role the person routed elsewhere keeps its route, an opencode route for the judge stays skipped as today, and a judge that cannot run hands the issue back rather than waiting for an answer by hand (grounds: commands/drain.md on the routed judge; commands/build.md on runner roles) diff --git a/.abcd/work/decisions/dcn-2610222224040756-a-drive-asks-before-its-first-move.md b/.abcd/work/decisions/dcn-2610222224040756-a-drive-asks-before-its-first-move.md new file mode 100644 index 0000000000..19da75c8d0 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224040756-a-drive-asks-before-its-first-move.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224040756 +slug: a-drive-asks-before-its-first-move +date: 2026-10-22 +--- +2026-10-22 — A drive asks before its first move, naming the cap, the estimated agent runs, the runner and its API key, and the answer is never remembered (facilitator, on delegation, itd-2610222215485265; revised after review round 1): asked at the first launch the question came after a reservation, which is the incident iss-2610222000271964 recorded; the default cap while driving is eight, the cap the product thinker's own drains use, and --drive --yes is the scripted yes where there is no terminal (grounds: RN2, a runner spends the person's paid key; the setup-consent rule; the 2026-10-22 recovery plan's drain at --max 8) diff --git a/.abcd/work/decisions/dcn-2610222224040967-while-driving-a-role-the-person-left.md b/.abcd/work/decisions/dcn-2610222224040967-while-driving-a-role-the-person-left.md new file mode 100644 index 0000000000..ccac021202 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224040967-while-driving-a-role-the-person-left.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224040967 +slug: while-driving-a-role-the-person-left +date: 2026-10-22 +--- +2026-10-22 — While driving, a role the person left unrouted runs on runner.fallback_host, and a role a repository pins to the host refuses the start (facilitator, on delegation, itd-2610222215485265; revised after review round 1): runner.fallback_host is the existing key for the runner that runs a role when abcd has no host session, so no second default is added; when it is unset the person's yes may name the claude runner for that one drive, which is the person's own invocation and so a personal route under RN2; a repository's pin to the host has nowhere to run without one, so the drive refuses before its first move and names the role (grounds: internal/core/runner/config.go; RN2; commands/build.md on host pins) diff --git a/.abcd/work/decisions/dcn-2610222224049799-a-drain-drives-itself-only-when-drive-is.md b/.abcd/work/decisions/dcn-2610222224049799-a-drain-drives-itself-only-when-drive-is.md new file mode 100644 index 0000000000..9d03acd676 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224049799-a-drain-drives-itself-only-when-drive-is.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224049799 +slug: a-drain-drives-itself-only-when-drive-is +date: 2026-10-22 +--- +2026-10-22 — A drain drives itself only when --drive is given, never because no host was detected (facilitator, on delegation, itd-2610222215485265; revised after review round 1): a terminal inherited from a Claude Code shell carries its environment and a host that runs commands in a terminal sets none, so detection would start a drive by accident or block a host's call on a prompt; --drive is the one way in, abcd drain without it does one move per call as today, and the host's command page always calls it that way (grounds: the Opus and Sonnet reviews of 2026-10-22; brief invariant 16, a control asserts only what it can prove) diff --git a/.abcd/work/decisions/dcn-2610222224054666-ctrl-c-or-a-hang-up-stops-a-drive-s.md b/.abcd/work/decisions/dcn-2610222224054666-ctrl-c-or-a-hang-up-stops-a-drive-s.md new file mode 100644 index 0000000000..aa55c3e7d6 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224054666-ctrl-c-or-a-hang-up-stops-a-drive-s.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224054666 +slug: ctrl-c-or-a-hang-up-stops-a-drive-s +date: 2026-10-22 +--- +2026-10-22 — Ctrl-C or a hang-up stops a drive's running agent and leaves the open lane resumable (facilitator, on delegation, itd-2610222215485265): the runner's process group is stopped, the open lane keeps its worktree, branch and reservation, and the drive prints the run, the lane and when the reservation lapses, so the next drive or any host resumes it; nothing is handed back for an interruption, and a second signal exits at once as today (grounds: commands/drain.md on signals; the stuck hand-back's meaning) diff --git a/.abcd/work/decisions/dcn-2610222224055876-a-drive-runs-a-lane-s-preflight-itself.md b/.abcd/work/decisions/dcn-2610222224055876-a-drive-runs-a-lane-s-preflight-itself.md new file mode 100644 index 0000000000..4e25d99588 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224055876-a-drive-runs-a-lane-s-preflight-itself.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224055876 +slug: a-drive-runs-a-lane-s-preflight-itself +date: 2026-10-22 +--- +2026-10-22 — A drive runs a lane's preflight itself when its landing waits for a receipt (facilitator, on delegation, itd-2610222215485265): the landing pushes only on a preflight receipt naming the lane's head, which only make preflight mints and which a host runs today; the driver runs it in the lane's worktree through abcd implement load --wait, one at a time behind the machine's load gate, inside the person's yes (grounds: internal/core/implement/loop/land.go; the load gate) diff --git a/.abcd/work/decisions/dcn-2610222224056700-a-runner-fallback-while-driving-hands.md b/.abcd/work/decisions/dcn-2610222224056700-a-runner-fallback-while-driving-hands.md new file mode 100644 index 0000000000..ac075a5985 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610222224056700-a-runner-fallback-while-driving-hands.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610222224056700 +slug: a-runner-fallback-while-driving-hands +date: 2026-10-22 +--- +2026-10-22 — A runner fallback while driving hands the lane back as stuck, and runner failures in a row are an outage the drive waits through (facilitator, on delegation, itd-2610222215485265): a fallback hands its role to a host that does not exist, so the driver turns it into the existing stuck hand-back with the runner's reason and releases the reservation; three failures in a row, or a judge that fails, are treated as an outage, waited through at 1, 5 and 10 minutes then hourly and ended after eight hours, handing nothing back, since a hand-back makes an issue a person's for every account and a passing outage must not do that (grounds: drive.go; commands/drain.md on hand-backs and origin_wait)