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 10185df1aa..420ec83203 100644 --- a/.abcd/development/brief/06-delivery/03-out-of-scope.md +++ b/.abcd/development/brief/06-delivery/03-out-of-scope.md @@ -151,8 +151,10 @@ That is what keeps "not hand-counted" true after the day it was written. - `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` — abcd drain --drive works through the ledger from a plain terminal +- `itd-2610240850336695` — The person grants their sessions what they may do alone, until a time - `itd-2610240913343804` — No surprises: abcd says up front what it will do on its own and what it will ask - `itd-2610240933025076` — abcd plans unattended and asks only what a person owns +- `itd-2610241015120503` — The person and the repository agree when abcd may merge, and what a cloud session may do **Later-phase items with no intent id.** These four were written into the brief diff --git a/.abcd/development/decisions/adrs/2610240850193281-a-human-set-autonomy-grant-permits.md b/.abcd/development/decisions/adrs/2610240850193281-a-human-set-autonomy-grant-permits.md new file mode 100644 index 0000000000..de98956c3a --- /dev/null +++ b/.abcd/development/decisions/adrs/2610240850193281-a-human-set-autonomy-grant-permits.md @@ -0,0 +1,133 @@ +--- +id: adr-2610240850193281 +slug: a-human-set-autonomy-grant-permits +status: proposed +date: 2026-10-24 +supersedes: null +superseded_by: null +related_intents: [itd-2610240850336695, itd-2610241015120503, itd-2610180912189125, itd-2610170638184486, itd-2610161621364075, itd-97, itd-2610150602547271] +related_rfcs: [] +related_adrs: [adr-2610150603484802, adr-42] +--- + +# ADR-2610240850193281: A human-set autonomy grant permits, separately from the agent-written waiting-on badge + +## Context + +`abcd mode` carries two things. The badge (`managed`, `facilitator`, +`product-thinker`) reports whose answer the loop is waiting on; a question's +chip sets it and the answer resets it, so in practice the agent writes it. The +away mode (`abcd mode autonomous`, `queue`, `back`, itd-2610180912189125) +records that the person is away and grants no rights: no reader in `build`, +`implement`, `drain` or `landing` consults it, and `--hold-pushes` changes only +the stop hook's wording. The one setting meant to grant something, +`facilitator.mode: auto|human` (adr-2610150603484802), lives in the +repository's config and is not built. + +The away mode first granted a narrow right, a session pushing its own branches +and opening their pull requests (`dcn-2610181003456444`). +`dcn-2610181016510153` reversed it: the OS sandbox fences only the agents abcd's +runner launches, never the person's own interactive sessions, so guard entries +"would never become a boundary" and would buy "a trust claim the plan cannot +make". + +The consequence surfaced on 2026-10-24: a session refused to push a branch and +open a pull request without a standing permission, and the person found no +setting that gives one. The permission lived only in chat. + +State of the art, read on 2026-10-24: Claude Code's permission modes, Codex's +approval and sandbox presets, Cline's auto-approve toggles and LangChain's +per-tool interrupts are all grants the harness enforces, none of them an OS +boundary for the person's own session. Each is kept apart from the session's +"needs input" status (Claude Code's agent view states the separation), each is +built from per-capability grants with named presets on top, and each keeps a +short list of irreversible acts gated at every level (force-push, merging an +unapproved pull request, production deploys; GitHub's coding agent cannot +merge at all). None of them expires a grant on a clock. + +## Decision + +We will treat a grant and a fence as different things. A grant says what a +session may do without asking; it needs no sandbox, because it widens nothing +the session could not already do and only removes the stop. A fence restricts, +and stays a mistake filter where abcd cannot enforce it (adr-42), as +`--hold-pushes` already is. + +We will therefore give the person one autonomy grant, kept apart from the +badge: + +- The person alone sets it, by an explicit command (`/abcd:mode autonomous`, + or `abcd mode autonomous` at a terminal), never from a sentence in chat, and + it lives in their home (`~/.abcd.noindex/`), keyed on the repository's root + commit. A repository's committed `.abcd/config.json` may remove a capability + from it and never add one. +- It is built from `push-branch`, `open-pr` and `decide-as `, with three + presets on top (`nothing`, `ship-work`, `everything`); a grant that names + none carries `ship-work` (`push-branch`, `open-pr`, `decide-as facilitator`). +- On the grant alone, abcd's own commands refuse merging to the default + branch and cutting a release, and the guard filters the shell routes to them + where its hooks run. A release or merge the person asks for in the session is + the person's act, not the grant's, and is not refused. A session never + force-pushes, even on the person's word: the person runs it in their own + terminal. +- It ends at a time the person names, or seven days after it was set; an ended + or unreadable grant reads as no grant. +- It holds whether the person is present or away: being away stays its own + record, and ending the absence does not end the grant. +- It reaches a session only through abcd's hook output, and is checked again + at each push and pull request, so where abcd's hooks do not run there is no + grant. +- Only the person sets it: the guard refuses a session that sets or writes it, + by every route abcd can be invoked through. +- The badge stays the agent's report of whose answer is owed; the status line + shows the grant beside it. + +Whether abcd may merge on its own (`queue-merge`, and the repository's planned +`facilitator.mode: auto`) and what a cloud session may do are not decided +here; they are itd-2610241015120503's, because each would reverse an accepted +rule (ruling AM1 in the landing's merge gate, adr-2610150603484802, invariant +23). + +This reverses dcn-2610181016510153 in whole, on rights and on who sets the +mode (it let a session set it "on their word"), and reinstates +dcn-2610181003456444's person-only guard with a wider grant; +dcn-2610241016208164 records the reversal from the decisions' side. + +The grant and the fence are two axes, as Codex keeps its approval policy apart +from its sandbox mode. Where abcd puts up a fence, the grant is embedded in it: +the agents abcd's runner launches run inside the operating-system sandbox of +itd-2610161621364075, and the grant says what they may do there without asking. +Where abcd cannot fence, the person's own interactive sessions (the part B that +intent names for a draft of its own), the grant stands alone, abcd claims no +boundary, and the person's host settings stay the outer fence (product thinker, +planning interview, 2026-10-24). + +## Alternatives Considered + +- **Keep the away mode as a record that grants nothing** (the standing + decision). Rejected: the person's permission then lives in chat, one session + hears it and its peers do not, and compaction can drop it. Every tool + examined ties the human control to what the agent may do. +- **Grant rights only where abcd can fence them** (the runner's sandboxed + agents). Rejected: it leaves the person's interactive sessions, the ones + that paused on 2026-10-24, with no grant at all. +- **A single autonomy level** (off, low, full). Rejected: every tool examined + builds its levels from per-capability grants, and a single level cannot say + "push and open pull requests, but do not decide product questions". +- **Per-capability grants with named presets, irreversible acts outside every + preset** (chosen). It matches the state of the art and keeps the acts the + person cannot undo with the person. + +## Consequences + +- A session may push its own branch and open a pull request under a grant + without asking, and the morning list must name what it did. +- abcd takes on a refusal it does not have today: on the grant alone, its own + commands refuse merging to the default branch, a release cut and a + force-push, with the guard as the shell-side mistake filter. +- The shipped away mode's "or asking a session to" route is withdrawn: a + breaking change for anyone who sets it in prose. +- The badge loses its human writer on the `/abcd:mode` page; the page leads + with the grant. +- The grant is a permission, not a sandbox: a session that ignores it is not + stopped by it, and the documentation says so. diff --git a/.abcd/development/decisions/adrs/README.md b/.abcd/development/decisions/adrs/README.md index daaf828ef5..3620f342c5 100644 --- a/.abcd/development/decisions/adrs/README.md +++ b/.abcd/development/decisions/adrs/README.md @@ -220,6 +220,7 @@ The intent lint (a Go implementation) extends to verify these reciprocally. | [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 | +| [adr-2610240850193281](2610240850193281-a-human-set-autonomy-grant-permits.md) | A human-set autonomy grant permits, separately from the agent-written waiting-on badge | proposed | 2026-10-24 | | [adr-2610241022330872](2610241022330872-setup-edits-the-shell-settings-to-put.md) | Setup edits the shell settings to put abcd's folder on the PATH, on a yes | accepted | 2026-10-24 | | [adr-2610241022541063](2610241022541063-each-person-s-commit-identity-lives-on.md) | Each person's commit identity lives on their own machine, not in the repository | accepted | 2026-10-24 | | [adr-2610241057124151](2610241057124151-abcd-installs-the-tools-it-needs-from.md) | abcd installs the tools it needs from downloads pinned in its own release | accepted | 2026-10-24 | 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 5c004e93a8..dd88ccf422 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 @@ -75,6 +75,20 @@ Surveyed 2026-10-17 against primary sources ([research note](../../research/note Path taken: 2. The forge and the shared remote are the native floor (the queue, compare-and-swap pushes, branches cloud sessions can push), and the pass, the dependency line, the generation number and the budgets are bespoke to abcd's records, behind a seam. + +**Carried in from itd-2610240850336695, its Decision 19 (product thinker, +2026-10-24).** When sessions push under a credential of their own, the +repository gains a ruleset refusing non-fast-forward pushes on every branch, +with a bypass for the person's admin role: a session and abcd then cannot +force-push anywhere, and the person still can, through `gh` or the forge's web +interface. Until then the person's grant ships with the force-push stop on +feature branches resting on this machine's pre-push hook and the guard. + +**The bundle gains a third member (product thinker, 2026-10-24).** The +person's grant, itd-2610240850336695, joins the autonomous-agents bundle with +this intent and the checklist, so its force-push stop lands with this intent's +credential split and the all-branch ruleset above. + ## Open Questions Answered on 2026-10-18 by the planning prep (facilitator, each a decision 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 bec7b9afb7..0133ac55cb 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 @@ -263,6 +263,14 @@ session's interview on 2026-10-17: 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). +- **Moved out to itd-2610240850336695 (the person's grant, 2026-10-24).** The + parked person-only work above (the guard refusing a session's own `abcd mode + autonomous` by every route, the shell and file-tool refusals on the away + store, the wider PreToolUse matcher, the recorded attempts and the two live + checks) is that intent's Decision 5, because it gives the away mode the rights + worth guarding. That intent `builds_on` this one: when this draft's pre-run + blocker sweep ships, it runs before an away grant takes effect, so the person + is asked every undecided design decision first (its Decision 16). ## Decisions @@ -301,6 +309,11 @@ five bullets now bind itd-2610222029228127, where they are carried. - **The release:** this intent and the coordination intent form the autonomous-agents bundle, built as the next big release once current work settles; dashboard work joins later (product thinker). +- **The bundle gains a third member (product thinker, 2026-10-24).** The + person's grant, itd-2610240850336695, joins the autonomous-agents bundle, so + the grant, the checklist and the coordination intent are planned together. + Its Decision 14 narrows "they say so" above: the person sets the away mode + and the grant by the explicit command, never by a sentence in chat. - **Credentials:** autonomous sessions never use the product thinker's own forge login; each runs under its own weaker credential, owned by the coordination intent. The checklist records the credential's kind, scope and diff --git a/.abcd/development/intents/drafts/itd-2610240850336695-before-stepping-away-the-person-grants.md b/.abcd/development/intents/drafts/itd-2610240850336695-before-stepping-away-the-person-grants.md new file mode 100644 index 0000000000..3714940d84 --- /dev/null +++ b/.abcd/development/intents/drafts/itd-2610240850336695-before-stepping-away-the-person-grants.md @@ -0,0 +1,357 @@ +--- +id: itd-2610240850336695 +slug: before-stepping-away-the-person-grants +spec_id: null +kind: null +suggested_kind: null +reclassification_history: [] +builds_on: [itd-2610180912189125, itd-2610170638184486] +related_adrs: [adr-2610240850193281] +severity: minor +impact: breaking +origin: researcher-authored +production_mode: hand-written +--- + +# The person grants their sessions what they may do alone, until a time + +Typed links: `builds_on` +[itd-2610180912189125](../shipped/itd-2610180912189125-a-session-told-to-work-on-while-its.md) +(the shipped away mode, whose record and hooks this grant sits beside, and +whose "or asking a session to" route Decision 14 **reverses**) and +[itd-2610170638184486](itd-2610170638184486-every-abcd-session-keeps-a-checklist-on.md) +(the checklist, whose pre-run blocker sweep asks the person for every undecided +design decision before they leave, and whose parked person-only guard work this +intent takes over, Decision 5); `related_adrs` +[adr-2610240850193281](../../decisions/adrs/2610240850193281-a-human-set-autonomy-grant-permits.md) +(the decision this intent delivers). Merging and cloud sessions are split out +to their own draft (Decision 13). + +## Press Release + +> **Before stepping away, a person grants their sessions exactly what they may do alone, and abcd holds them to it instead of leaving it to a sentence in chat.** Alice, a solo founder who is also the product thinker, types `/abcd:mode autonomous --away --until 09:00` before the evening. That grants the default "ship work" preset: every session of this repository on Alice's computer may push the branches it made, open their pull requests and answer its own technical questions, without stopping to ask. None of them merges to `main` or cuts a release on the grant alone, and none ever force-pushes, because no preset grants those and abcd's own commands refuse them. The status line shows the grant and its end beside the badge that says whose answer is owed. At 09:00 the grant lapses by itself and sessions go back to asking, and Alice's morning list shows each push and pull request a session made under it. Alice can grant while still at the keyboard too, to stop being asked whether a session may push; and a session told in chat "I'm away, carry on" does not grant itself anything, it names the command. +> +> "I used to come back to sessions that had waited all night to ask whether they could push," said Alice, a solo founder. "Now I say once what they may do, and in the morning I see exactly what they did with it." + +## Why This Matters + +_Written by the agent on 2026-10-24 from the session in which the product +thinker found the gap; confirmed by the planning interview the same day._ + +On 2026-10-24 the product thinker asked a session to build an issue. Getting +it started meant committing the issue record on a branch, running the +preflight, pushing and opening a pull request, and the session stopped at the +push on its own rule that a push or pull request needs a standing permission. +The product thinker asked why it paused when "haven't we set the TF role to +autonomous?", and found that no such setting exists: + +- The badge (`managed`, `facilitator`, `product-thinker`) is the only thing the + mode page offers, and in practice the agent writes it: a question's chip sets + it and the answer resets it. +- The away mode (`abcd mode autonomous`) is the human-set control, but it + "grants no rights": nothing in `build`, `implement`, `drain` or `landing` + reads it, and `--hold-pushes` changes only the stop hook's wording. +- The one setting meant to grant something, `facilitator.mode: auto|human`, + lives in the repository's config and is not built. + +So the person's permission has nowhere to live but a sentence in chat, which +one session hears and its peers do not, and which compaction can drop. The +shipped away mode also promises it can be set by "asking a session to", which +lets a sentence in chat stand for the person's act; this intent withdraws that +route (Decision 14), which is why its impact is `breaking`. + +The away mode was designed with a narrow push grant and then reversed +(dcn-2610181003456444, then dcn-2610181016510153), because the guard cannot +fence the person's own sessions. That reasoning treats a grant as a fence; the +state of the art keeps them apart (see SOTA). + +## Mechanism + +_Confirmed by the product thinker in the planning interview, 2026-10-24._ + +We expect sessions to stop pausing on the acts a grant covers, because the +grant is a record each session reads at its start, after each compaction and +again at each push or pull request it makes, not a sentence one session heard +in chat; and we expect no rise in harm, because the acts a person cannot undo +are in no preset, abcd's own commands refuse them on the grant alone, and the +guard filters the shell routes to them where its hooks run. As one night: the +person runs `/abcd:mode autonomous --away --until 09:00` and leaves. A session +fixing an issue on its own branch pushes it and opens its pull request without +asking; a second session started an hour later, which never heard the person, +reads the grant and does the same; a third, compacted at 23:00, reads it again +and carries on. In the morning there are pull requests waiting, nothing on the +default branch has changed, and a force-push or release a session attempted +through abcd was refused. + +The claim is wrong if a session under a grant stops to ask about an act the +grant covers (the record did not reach it), or if a session acting on the grant +alone does something the person cannot easily undo through abcd (the refusals +did not hold). + +## Scope Conditions + +_Confirmed by the product thinker in the planning interview, 2026-10-24._ + +- One person on one machine grants their own local sessions of one repository, + keyed on its root commit. Cloud sessions are outside this intent (Decision + 13): here a cloud session reads no grant. +- The grant holds only where abcd can enforce it: a host whose hooks run abcd's + guard. Where they do not, the grant does not apply and the session asks as it + would with no grant (Decision 12). +- The grant is a permission, not a lock: it removes the stop for the acts it + covers, and does not claim to stop a session that acts without asking, as a + session can today. The guard is a mistake filter (adr-42); the refusals that + hold are the ones abcd's own commands make. + +## Acceptance Criteria + +_Confirmed by the product thinker in the planning interview, 2026-10-24; +rewritten to the adversarial reviews' findings the same day._ + +- **Given** a grant carrying `push-branch` until 09:00, **when** a session + pushes a branch this working copy created, before 09:00, **then** it pushes + without asking for permission, and the morning list names the push, the + session and the capability. +- **Given** a grant carrying `open-pr`, **when** a session opens a pull request + from a branch this working copy created, **then** it opens without asking and + the morning list names it. +- **Given** any grant, **when** a session acting on the grant alone asks abcd to + merge to the default branch or cut a release, **then** abcd's command refuses + it and names the act as outside every preset; **and given** the same act + through a raw shell where abcd's hooks run, **then** the guard refuses it as a + mistake filter. +- **Given** any grant or none, **when** a session force-pushes, even on the + person's word in the session, **then** the push is refused (by the forge on + the default branch, release tags and abcd's own branches; by the pre-push + hook and the guard on every other branch) and the refusal names the person's + own `gh` or the forge's web interface as the place to do it. +- **Given** a grant set or revoked while sessions are running, **when** a + running session next pushes or opens a pull request, **then** it acts on the + grant as it now stands, not as it stood when the session started. +- **Given** a grant whose end has passed, or a grant record that cannot be + read, **when** a session reads it, **then** it reads as no grant and the + session asks as it would without one. +- **Given** a grant set with no `--until`, **when** it is recorded, **then** it + ends seven days later and the status line names that end. +- **Given** a host where abcd's hooks do not run, **when** a session there would + act on a grant, **then** the grant reaches it as no grant and it asks. +- **Given** a grant naming `decide-as facilitator` only, **when** a session meets + a product question, **then** it waits for the person and records no answer of + its own; **and given** a technical question, **then** it answers, recording + the answer as a decision. +- **Given** a grant naming the `nothing` preset, **when** a session pushes, + **then** it asks; **given** `everything`, **then** it pushes, opens pull + requests and answers both roles' questions; **given** a list of capabilities, + **then** only those apply. +- **Given** the repository's committed `.abcd/config.json` withholds a + capability, **when** the grant is read, **then** the effective grant omits it, + and a capability the repository names that the person did not grant is + ignored; an uncommitted change to that file widens nothing. +- **Given** a grant in this repository, **when** a session in a sibling + worktree of it reads the grant, **then** the grant applies there, and a + session in another repository reads none. +- **Given** a grant set while the person is present, **when** they later end + the absence with `abcd mode back`, **then** the grant still holds until its + own end or until it is revoked. +- **Given** a grant is in force, **when** the status line renders, **then** it + shows the grant's preset and end beside the waiting-on badge. +- **Given** a session, **when** it runs `abcd mode autonomous` itself by any + route (the binary on PATH, the plugin root's, `go run ./cmd/abcd`, a built + `bin/abcd--`, through `sh -c` or a script), or writes the grant + record or the away store through a shell or a file tool, **then** the guard + refuses it, records the attempt, and the person sees it on their next prompt. +- **Given** the person writes "I'm away, carry on" in chat, **when** a session + reads it, **then** it records no grant and no absence and names the command. +- **Given** the person reads the morning list, **when** it prints, **then** it + lists each act a session took under the grant, with the session, the + capability and the time. + +## Prior Art + +- itd-2610180912189125 (shipped): the away mode, record and hooks; this intent + sits beside it and withdraws its "asking a session to" route. +- itd-2610170638184486 (draft): the checklist and its pre-run blocker sweep, + which asks the person for every undecided design decision before they leave; + its parked person-only guard work moves here. +- itd-2610161621364075 (planned): the operating-system fence for the agents + abcd's runner launches; the grant is embedded in that fence where it exists. +- itd-148 (planned) and iss-2610240859590333: a session working in its own + worktree, which the grant's "a branch this working copy created" relies on. +- adr-42: the guard is a mistake filter, not a boundary. + +## SOTA + +- Claude Code permission modes (default, acceptEdits, plan, auto, + bypassPermissions) with allow, ask and deny rules: mature; the mode is kept + apart from the session's "needs input" state, and a short list stays + prompted in every mode. +- OpenAI Codex CLI: a sandbox axis and an approval axis, with named presets; + mature. +- Cline auto-approve: one toggle per capability; mature. +- GitHub Copilot coding agent: may push only to its own branch and can never + approve or merge; mature. +- None of them expires a grant on a clock. + +**Path:** 3, bespoke, because the grant is abcd's own record read by abcd's own +hooks and commands across the person's sessions of one repository, which none +of the tools above holds; the shape follows them (capabilities with presets, a +status kept apart from the grant, irreversible acts outside every level). + +## Decisions + +1. **Grant and fence are two axes (product thinker, 2026-10-24).** The grant + is adopted as a permission with no boundary claimed. Where abcd fences, the + runner's agents under itd-2610161621364075, the grant is embedded in that + fence; the person's own sessions get the grant alone. Recorded in + adr-2610240850193281. +2. **The capability set (product thinker, 2026-10-24).** `push-branch` (push a + branch this working copy created), `open-pr` (open a pull request from it) + and `decide-as` (answer its own questions as a named role). A fourth, + `queue-merge`, moved to the merging draft by Decision 13. Merging to the + default branch, cutting a release and force-pushing are in no preset. +3. **`decide-as` takes either role or both (product thinker, 2026-10-24).** + The person names `facilitator`, `product-thinker` or both on each grant; a + session answers only the roles named, recording each answer as a decision, + and a question for a role not named waits for the person. It never widens + what invariant 19 lets a machine take alone. +4. **The default preset is "ship work" (product thinker, 2026-10-24).** A grant + that names no preset carries `push-branch`, `open-pr` and `decide-as + facilitator`. Its first wording made it wait on the checklist's blocker + sweep; Decision 16 amends that. +5. **The person-only work moves into this intent (product thinker, + 2026-10-24).** The grant ships with what protects it: the guard refuses a + session's own `abcd mode autonomous` by every route (the binary on PATH, the + plugin root's, `go run ./cmd/abcd`, a built `bin/abcd--`, and + routes through `sh -c` and a script a shell runs); shell and file-tool writes + to the grant record and the away store are refused; the PreToolUse matcher + gains `Edit`, `Write`, `MultiEdit` and `NotebookEdit`; refused attempts are + recorded and shown to the person. It also takes the two live checks + dcn-2610181003456444 required: that the person's `!` command reaches no + pre-tool hook, and that a message relayed from another session starting with + `!` is not run as a shell command. itd-2610170638184486 drops this from its + remaining work. +6. **The facilitator-setting fold (product thinker, 2026-10-24).** Moved to the + merging draft by Decision 13. +7. **A grant covers one repository (product thinker, 2026-10-24).** It is + keyed on the repository's root commit, as the away record is, so every + worktree and clone of it on this machine shares it; another repository + needs its own grant. +8. **A grant holds whether the person is present or away (product thinker, + 2026-10-24).** Being away stays its own record, with its question reroute + and stop check; the grant is a separate record beside it. Ending the + absence does not end the grant; the grant ends at its own time or when the + person revokes it. +9. **Three presets ship (product thinker, 2026-10-24).** `nothing` (sessions + keep asking), `ship-work` (the default) and `everything` (every capability + this intent carries, `decide-as` both roles). A grant may instead list + capabilities one by one. +10. **`abcd mode autonomous` becomes the grant; being away is a flag on it + (technical facilitator, 2026-10-24).** `abcd mode autonomous` with no + `--grant` grants `ship-work`; `--grant ` names + another; `--until` ends it; `--away` also records the absence. `abcd mode + back` ends the absence only. +11. **Cloud sessions are autonomous by default (product thinker, + 2026-10-24).** Moved to the merging draft by Decision 13, where it meets + invariant 23 and Decision 12; here a cloud session reads no grant. +12. **No enforcement, no grant (product thinker, 2026-10-24).** Where abcd + cannot enforce the grant (no hooks, so no guard), the grant does not apply: + the session reads it as absent and asks. The grant reaches a session only + through abcd's hook output, never as plain text a session reads for + itself, so a host without hooks never sees it as permission. +13. **The split (product thinker, 2026-10-24, after the two adversarial + reviews).** This intent keeps the grant, its protection and the refusals. + `queue-merge`, the facilitator-setting fold and the cloud default move to + a draft of their own, with the findings they carry: the inversion of + ruling AM1 (abcd arms a merge only where the ruleset requires a person's + approval), the reversal of adr-2610150603484802, and invariant 23. +14. **The grant is set only by the person's explicit command, never from a + sentence in chat (product thinker, 2026-10-24).** The person runs + `/abcd:mode autonomous` (or `abcd mode autonomous` at a terminal); a + session asked in prose ("I'm away, carry on") refuses and names the + command. This withdraws the shipped away mode's "or asking a session to" + route, so the impact is `breaking`. How the guard tells the person's slash + command from a session's own call is the spec's to settle, starting from + the live checks of Decision 5. +15. **A grant with no end falls back to a cap (product thinker, 2026-10-24).** + `--until` is optional; a grant that names no end lasts seven days at most, + the away mode's existing cap, and the status line shows the end either way. +16. **The grant works without the blocker sweep (product thinker, + 2026-10-24).** The default preset takes effect when the person sets it; it + does not wait for itd-2610170638184486's sweep. When the sweep ships, it + runs before an away grant takes effect, so the person is asked every + undecided design decision first; until then the person decides that for + themselves. +17. **The refusals apply to a session acting on the grant alone (recorded + without a question: one defensible answer).** A release the person asks + for in the session, or a cut the release orchestrator runs on the person's + instruction, is the person's act, not the grant's, and is not refused. The + record settles it: AGENTS.md has sessions cut releases on the person's + word, and refusing that would refuse the person. Decision 18 narrows this + for force-pushes. +18. **A session never force-pushes, even on the person's word (product + thinker, 2026-10-24).** Force-pushing is refused whatever the grant and + whatever the person says in the session; the person runs it themselves in + their own terminal. Merging to the default branch and cutting a release + stay as Decision 17 says. +19. **The force-push stop is deterministic where the forge holds it, and the + gap until then is recorded (product thinker, 2026-10-24).** A session and + abcd must never force-push; the person may, through `gh` or the forge's + web interface. The deterministic form is a forge rule: a ruleset refusing + non-fast-forward pushes on every branch, with a bypass for the person's + admin role. It holds only once sessions push under a credential of their + own, without that role (itd-2610170634394993, its Decision 5), because + while a session pushes under the person's own login the bypass lets it + through and the login can lift the rule. Today the forge already refuses a + force-push to the default branch, release tags and abcd's own reservation + and status branches, for everyone. On every other branch this intent ships + with the stop that runs on this machine (the committed pre-push hook + refusing a non-fast-forward push, and the guard refusing `--no-verify` and + a changed hooks path), which is a mistake filter, not a guarantee. The gap + closes when the credential split lands; the all-branch ruleset is carried + in itd-2610170634394993. +20. **The grant joins the autonomous-agents bundle (product thinker, + 2026-10-24).** It is planned and shipped as one bundle with + itd-2610170634394993 (sessions' own weaker credential, and coordination + through the remote) and itd-2610170638184486 (the checklist), the bundle + the product thinker named on 2026-10-17 as the next big release. So the + force-push gap of Decision 19 never ships: the all-branch ruleset lands + with the credential split, in the same release as the grant. This + supersedes Decision 19's "ships with the stop that runs on this machine". + The checklist's 2026-10-17 ruling that the person says they are away "or + set it in the app" is narrowed by Decision 14, the same person's later + ruling: a sentence in chat sets nothing. +21. **A grant holds only in a fenced session (product thinker, 2026-10-24, + after the bundle's design review).** The person's own sessions run as the + person's operating-system user, so they can read the person's forge login + and any key that mints a session's own credential; a weaker credential + alone stops nothing there. A grant therefore takes effect only in a + session that runs inside an operating-system fence keeping the person's + login and the issuing key out of reach: the fence of itd-2610161621364075 + for the agents abcd's runner launches, and, for the person's own + interactive sessions, the host's own sandbox, which abcd never switches on + (it does not edit host settings) but detects and reports, as that intent's + part B names. A session abcd cannot confirm is fenced reads no grant and + asks, as Decision 12 has it for a host without hooks. +22. **The bundle ships as one release (product thinker, 2026-10-24).** The + bundle review proposed two releases; the product thinker kept one, so + every member, the dependency lines and the model budget included, lands + in the same cut. The phone page and the shared and public options are not + members: they were split out of the checklist on 2026-10-22 into + itd-2610222029228127, which the question put to the product thinker + wrongly counted in. + +## Open Questions + +- The spelling of revoking the grant without ending the absence (the spec's to + name). +- How the guard tells the person's `/abcd:mode autonomous` from a session's own + call (Decision 14), once the live checks of Decision 5 have run. +- Planning is by the bundle form, all three members at once (Decision 20), so + it waits until itd-2610170634394993 and itd-2610170638184486 are plannable; + both were reviewed on 2026-10-24 and their findings are not yet applied. +- Decision 21 depends on the fence for the person's own sessions, which + itd-2610161621364075 names as its part B, a draft not yet filed. + +## Audit Notes + +_Empty. Populated by intent-auditor when intent moves to shipped/._ diff --git a/.abcd/development/intents/drafts/itd-2610241015120503-a-person-lets-abcd-merge-its-own-pull.md b/.abcd/development/intents/drafts/itd-2610241015120503-a-person-lets-abcd-merge-its-own-pull.md new file mode 100644 index 0000000000..7eded8e474 --- /dev/null +++ b/.abcd/development/intents/drafts/itd-2610241015120503-a-person-lets-abcd-merge-its-own-pull.md @@ -0,0 +1,89 @@ +--- +id: itd-2610241015120503 +slug: a-person-lets-abcd-merge-its-own-pull +spec_id: null +kind: null +suggested_kind: null +reclassification_history: [] +builds_on: [itd-2610240850336695] +severity: minor +origin: researcher-authored +production_mode: hand-written +--- + +# The person and the repository agree when abcd may merge, and what a cloud session may do + +Typed links: `builds_on` +[itd-2610240850336695](itd-2610240850336695-before-stepping-away-the-person-grants.md) +(the person's grant, from which this draft was split by its Decision 13). This +draft carries the reversal flags that split moved here, each for the product +thinker to confirm, never auto-classified: it may **reverse** ruling AM1 and +[adr-2610150603484802](../../decisions/adrs/2610150603484802-a-fresh-install-s-facilitator-is-a.md), +and it may **supersede** itd-2610150602547271 (the planned facilitator +setting, whose spec is open) and refine itd-97. + +## Press Release + +> A person lets abcd merge its own pull requests through the merge queue, and lets cloud sessions work without waiting, under rules the repository's owners and the person both agree to. + +## Why This Matters + +_Written by the agent on 2026-10-24 from the planning interview and the two +adversarial reviews of itd-2610240850336695; the planning interview confirms +or rewrites it._ + +The interview for the person's grant ruled two things the reviews found it +could not hold without decisions of their own, so the product thinker split +them out (itd-2610240850336695, Decision 13): + +- **`queue-merge`, and the planned facilitator setting folded into it.** As + drafted, `queue-merge` let a session queue "a pull request whose ruleset + needs no approval". The shipped landing does the opposite under ruling AM1: + it arms auto-merge only where the ruleset requires a person's approval, and + otherwise leaves the merge to a person, so the draft would open the one case + AM1 closes, a merge with no person anywhere in it. Folding + `facilitator.mode: auto` into a person's home grant also reverses + adr-2610150603484802, under which the repository's owners consent to abcd + merging by a deliberate committed setting; a contributor's grant alone + would bypass that. +- **Cloud sessions autonomous by default.** A cloud session cannot read the + person's home, so its default would come from the binary or the repository, + and the person's grant forbids the repository to grant. It also meets + invariant 23 (cloud work reaches the default branch only through the + person's own collect) and the grant's own rule that no enforcement means no + grant, where a cloud host abcd's hooks cannot reach is exactly that case. + +## 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 + +> _Required (the itd-1 discipline): add at least one Given-When-Then bullet describing the verifiable bar for "shipped" before this draft can be planned._ + +## Prior Art + +- itd-2610240850336695 (draft): the person's grant, split from here. +- adr-2610150603484802 and itd-2610150602547271: the repository's + `facilitator.mode`, the standing answer on who consents to abcd merging. +- `internal/core/landing/land.go` (`mergeGate`, ruling AM1): the shipped rule + on when abcd arms a merge. +- Invariant 23 and itd-2610120738075547: cloud work's route through the + person's own collect. + +## Open Questions + +- Does a merge abcd queues need both the repository's consent and the person's + grant, or does one supersede the other? +- Does `queue-merge` arm only what `mergeGate` arms today, or widen it, and if + it widens, what ruling amends AM1? +- Which preset does a cloud session get by default, where does it read it, and + how does it hold under invariant 23 and "no enforcement, no grant"? + +## Audit Notes + +_Empty. Populated by intent-auditor when intent moves to shipped/._ diff --git a/.abcd/work/decisions/dcn-2610241016208164-dcn-2610181016510153-is-reversed-in.md b/.abcd/work/decisions/dcn-2610241016208164-dcn-2610181016510153-is-reversed-in.md new file mode 100644 index 0000000000..757682f5d4 --- /dev/null +++ b/.abcd/work/decisions/dcn-2610241016208164-dcn-2610181016510153-is-reversed-in.md @@ -0,0 +1,6 @@ +--- +id: dcn-2610241016208164 +slug: dcn-2610181016510153-is-reversed-in +date: 2026-10-24 +--- +2026-10-24 — dcn-2610181016510153 is reversed in whole by adr-2610240850193281: the away mode no longer grants nothing, and a session no longer sets it on the person's word. A person-set autonomy grant now permits pushing a session's own branches, opening their pull requests and answering its own questions as a named role, set only by the person's explicit command; dcn-2610181003456444's person-only guard is reinstated with that wider grant (itd-2610240850336695). The reversal's grounds: a grant only removes a stop and needs no fence, which the reversed decision's boundary argument did not separate. diff --git a/.abcd/work/issues/open/iss-2610240859590333-a-session-wrote-records-into-the-main.md b/.abcd/work/issues/open/iss-2610240859590333-a-session-wrote-records-into-the-main.md new file mode 100644 index 0000000000..181a799c15 --- /dev/null +++ b/.abcd/work/issues/open/iss-2610240859590333-a-session-wrote-records-into-the-main.md @@ -0,0 +1,17 @@ +--- +schema_version: 1 +id: "iss-2610240859590333" +slug: "a-session-wrote-records-into-the-main" +severity: "major" +category: "bug" +source: "user-observation" +found_during: "/abcd:capture of the autonomy-grant design" +origin: researcher-authored +production_mode: hand-written +found_at: "internal/surface/cli" +remedy: "Make every record-writing verb (capture, intent, decide, spec, and the index generator) and the guard's write path refuse in the default branch's working tree of a checkout another live session also holds, naming the machine-scoped worktree store and the git worktree add command to use, with an explicit override for a person working alone." +--- + +A session wrote records into the main checkout's working tree on main while another live session (abcd-0d) was working in the same checkout, because nothing enforces the rule that a concurrent session works in its own worktree. On 2026-10-24 this session ran capture, intent, decide --adr and go generate from the main checkout: an issue record, an intent draft, an ADR and two regenerated index regions landed in the shared tree, the regenerated indexes folding in a peer's untracked draft. Nothing refused or warned. capture, intent and decide write into whichever checkout they run in; the guard judges shell commands and questions, not where records are written; and the CONCURRENCY rules domain, which carries the rule, recalls only on prompts about worktrees, peers or sessions, so a prompt saying 'file the decision record' never injected it. The agent also skipped the required scan (session listing and abcd peers) before mutating. The rule held only as prose. + +Related: iss-2608230847432285 records the same failure arriving by a different route, a session whose shell cwd reverts from its worktree to the shared checkout. Here the session never entered a worktree at all, so the two want one enforcement: a write is judged by the checkout it lands in, whatever route put it there. diff --git a/.abcd/work/issues/open/iss-2610241133284600-on-2026-10-24-the-main-checkout-of-abcd.md b/.abcd/work/issues/open/iss-2610241133284600-on-2026-10-24-the-main-checkout-of-abcd.md new file mode 100644 index 0000000000..f367ef3ffa --- /dev/null +++ b/.abcd/work/issues/open/iss-2610241133284600-on-2026-10-24-the-main-checkout-of-abcd.md @@ -0,0 +1,17 @@ +--- +schema_version: 1 +id: "iss-2610241133284600" +slug: "on-2026-10-24-the-main-checkout-of-abcd" +severity: "major" +category: "bug" +source: "user-observation" +found_during: "pushing design/autonomy-grant" +origin: researcher-authored +production_mode: hand-written +found_at: ".githooks/pre-push" +remedy: "Have every abcd verb that commits or pushes (capture, decide, build, implement, land, drain) and the SessionStart hook refuse, or at least say loudly, when the checkout's core.hooksPath does not resolve to the committed .githooks, naming abcd ahoy install as the fix; and add the check to abcd ahoy's gap list." +--- + +On 2026-10-24 the main checkout of abcd had no git hooks installed: core.hooksPath was unset in every config scope and the common .git/hooks held only samples, so the committed .githooks (commit-msg, pre-commit, pre-push) ran for no commit and no push made from it or from its linked worktrees. Several sessions were committing and pushing that day. Nothing said so: commits went through silently, a commit message naming an issue without a Refs: line (RS004) was accepted at commit time, and a push to origin was made with no preflight receipt, which the pre-push hook exists to refuse. The gap surfaced only because make preflight's lint-issues gate failed afterwards. How the setting was lost is not known; the setup redesign under way the same day touches the hooks. abcd ahoy reports install state but did not flag it, and no commit verb checks it before writing. + +Related: the setup redesign under way the same day (branch feat/setup-redesign, not yet on origin) captured the ahoy side of this as its own issue: ahoy reported clean while the hooks were unarmed, so setup never offered to arm them, and the redesign makes arming them a question the person approves. The session behind it found the hooks already unarmed at its first ahoy run that morning, about 08:12 UTC, before any setup run of its own. diff --git a/.abcd/work/issues/open/iss-2610241256347199-no-verb-that-adds-or-moves-a-record-in.md b/.abcd/work/issues/open/iss-2610241256347199-no-verb-that-adds-or-moves-a-record-in.md index 3ff9e8bb3d..da3a35b6b5 100644 --- a/.abcd/work/issues/open/iss-2610241256347199-no-verb-that-adds-or-moves-a-record-in.md +++ b/.abcd/work/issues/open/iss-2610241256347199-no-verb-that-adds-or-moves-a-record-in.md @@ -13,3 +13,8 @@ remedy: "Have each verb that writes or moves a record in a directory an index_dr --- No verb that adds or moves a record in an indexed directory regenerates the index that lists it. Only go generate ./internal/surface/cli (cmd/abcd-gen-surface, through lint.RegenerateIndexes) rewrites the generated regions, such as the drafts list in .abcd/development/brief/06-delivery/03-out-of-scope.md and the ADR index. So filing a draft intent, planning one out of drafts/, or minting an ADR with decide --adr leaves a region stale, and record-lint's index_drift blocker refuses the next make preflight. On 2026-10-24 a hand-filed draft (itd-2610240933025076) failed preflight this way. The region then conflicted on a rebase over main, because every branch that files a draft edits the same list. + +## Further evidence 2026-10-26 + +- A records-only pull request conflicted with main on the forge twice in one day, each time only in these generated regions (the drafts list in the out-of-scope page and the ADR index), because another branch that filed a draft or an ADR merged first. Each conflict turned auto-merge off and needed a local merge, and the merge commit needed a fresh `make preflight`, which on this machine waited three to five hours at the load gate. A union merge driver would not help: as the root `.gitattributes` says, the forge computes mergeability and the queue's merge without it. +- So the cost of committing a generated list that every record-filing branch edits is a full preflight per collision, not a small rebase. A remedy that removes it: keep the regions out of the branch's diff. Either generate them on the merge queue's tree and check them there instead of committing them on each branch, or render the lists at read time from the folders and drop the committed regions. diff --git a/.abcd/work/issues/open/iss-2610241414124940-the-preflight-load-gate-can-starve-every.md b/.abcd/work/issues/open/iss-2610241414124940-the-preflight-load-gate-can-starve-every.md new file mode 100644 index 0000000000..1a4ad5b7de --- /dev/null +++ b/.abcd/work/issues/open/iss-2610241414124940-the-preflight-load-gate-can-starve-every.md @@ -0,0 +1,27 @@ +--- +schema_version: 1 +id: "iss-2610241414124940" +slug: "the-preflight-load-gate-can-starve-every" +severity: "major" +category: "bug" +source: "user-observation" +found_during: "concurrent preflights, 2026-10-24" +origin: researcher-authored +production_mode: hand-written +found_at: "internal/core/implement" +remedy: "Gate every heavy preflight stage, not only the tests, behind one admission so queued runs stop feeding the load they wait on; judge capacity by the slot count rather than a raw load threshold once a run holds a slot; release a slot whose holder has used little CPU for a set time; and re-read load.preflights on every wait so a changed setting applies to waiters already queued." +--- + +The preflight load gate can starve every waiter on a busy machine and holds its only slot for a hung command. It opens only while the one-minute load is below twice the online cores (20 on 10 cores), but the preflight stages it does not gate (lint, record gates, evals, build, vet) run before it, so seven sessions' preflights on 2026-10-24 kept the load near 25 and the gate never opened: one drain lane's preflight ended 'load gate still shut after 2h0m0s: load 20.9 is not below 20 (2 x 10 online cores); 3 make preflights are running'. Later the same day, at 14:04 UTC, the load was 8 on 10 cores and about nine preflights were still queued, their logs unchanged for 45 minutes, because the one open slot (~/.abcd.noindex/load-wait.json, form command, opened 13:19 UTC, held to 14:19) belonged to a package test run that had used about 11% CPU for 45 minutes; load.preflights was 2, yet only one slot was open, possibly because waiters started before the setting was written read one slot when they began. Nothing reclaims a slot whose holder is idle, and nothing lets the gate open when the load it waits on comes from the ungated stages of its own queued runs. Adjacent records on an unmerged branch cover the ungated stages running unbounded and the unmeasured wait, which this issue does not restate. + +## Further evidence 2026-10-24 + +- A records-only preflight timed out at two hours with the load at 19, below the threshold, so load was not the cause: the gate refused because another make preflight was running and six waiters were queued ahead. Under this load the test stages take 25 to 75 minutes, and only one preflight's test stages pass at a time even with load.preflights at 2, so with six or more waiters no run can meet a two-hour timeout. +- The test stage's `go test -p 4 -timeout 20m ./...` bounds each test binary, not the stage, so one stage ran 73 minutes and more with no overall bound. +- With several preflights' test stages admitted together at a load near 20, six packages hit their 20-minute per-binary timeouts in one run: admitting more than one at a time made all of them fail. The sessions worked around it by running whole preflights one at a time through the gate's command form (`abcd implement load --wait --timeout 8h -- make preflight`). + +## Further evidence 2026-10-25 + +- A whole preflight run alone through the command form, admitted at a load of 8.5 on 10 cores after a 2h12m wait, still timed out three packages at 20 minutes: `internal/core/claim` (43 s in an earlier passing run), `internal/core/implement/loop` and `internal/surface/cli` (16 s earlier). The goroutine dumps show the tests waiting on child processes (git), not on a lock or a deadlock. +- In the same window, the machine's endpoint security scanner used 100 to 210 per cent CPU, sampled every five seconds, while every test binary together used 9 to 38 per cent. The tests make many throwaway repositories under the temporary directory, and an on-access scanner inspects each write and exec. Load average does not show this cost, so the gate opens on a machine whose test throughput is a small fraction of normal. +- So the gate's signal (the one-minute load) is the wrong measure on a machine with on-access scanning. A remedy grounded in that: measure throughput, not load, for example by timing a fixed probe (one throwaway `git init` and commit) before admitting a run, and say so when it is many times slower than the baseline; and ask the machine's administrator to exclude the Go build cache, the temporary directory and `~/.abcd.noindex/` from on-access scanning. diff --git a/.abcd/work/issues/open/iss-2610241415434937-building-an-issue-the-person-has-just.md b/.abcd/work/issues/open/iss-2610241415434937-building-an-issue-the-person-has-just.md new file mode 100644 index 0000000000..f069a857bc --- /dev/null +++ b/.abcd/work/issues/open/iss-2610241415434937-building-an-issue-the-person-has-just.md @@ -0,0 +1,15 @@ +--- +schema_version: 1 +id: "iss-2610241415434937" +slug: "building-an-issue-the-person-has-just" +severity: "minor" +category: "ux" +source: "user-observation" +found_during: "capture then build of the install one-liner issue, 2026-10-24" +origin: researcher-authored +production_mode: hand-written +found_at: "internal/core/implement" +remedy: "Make capture say at filing time whether the drain rule would take the record (and why not, naming the field), and let build iss-N preflight every refusal at once, naming each field and the record-only pull request the issue lane needs, instead of refusing on the first ground found." +--- + +Building an issue the person has just filed takes a pull request round trip first, with nothing in the capture or build output saying so up front. On 2026-10-24 a person asked a session to capture an issue and then build it. abcd build refused three times in a row, each on a different ground found only by running it. First, the default category the capture gave the issue (observation) is outside the drain rule's fixable set. Second, the issue lane fetches origin's default branch, so a record that exists only in the working tree cannot be built at all, and the record had to go through its own branch, preflight, pull request and merge queue (a queue run that failed once on an unrelated flaky test), about three hours. Third, once it landed, the severity the capture gave it (major) is above the drain's nitpick and minor, so the issue was a person's all along. None of the three was visible when the issue was filed, and the person's intent, to file and then build, needed four round trips to learn that it could not be built.