diff --git a/.abcd/development/brief/02-constraints/03-invariants.md b/.abcd/development/brief/02-constraints/03-invariants.md index 60c19439e5..5e13616426 100644 --- a/.abcd/development/brief/02-constraints/03-invariants.md +++ b/.abcd/development/brief/02-constraints/03-invariants.md @@ -117,6 +117,25 @@ The following are non-negotiable invariants — any architectural choice that vi status branch holding a standing announcement it has not asked about. The prompt hook shows the announcements, the doubts and the release holds from the last completed fetch, from disk alone, and asks the network nothing. + The away mode (itd-2610180912189125) is the person's word that sessions + work on while they are away: setting it (`abcd mode autonomous`) invokes, + for as long as it holds, the stop check's one read of the forge at each + turn's end, at most one listing of the open pull requests through the + forge client (`gh pr list`: head and base branch, author, body, merge + state, review decision and check rollup), every open one when the + person's forge login is known, so another person's pull request can be + told from the session's own, and the person's own alone when it is not, + under a ten-second deadline, with + no cache, so a run of eight blocked stops costs up to eight bounded + listings. A listing that fails or is late is named as not read, never + hidden, and the check judges from disk alone: every pushed branch of the + session's own is compared with the last-fetched default branch, naming + when it was fetched, and a reservation is never reported as carried by no + pull request. Whenever the away mode does not hold, the stop check reads disk + only, and no turn's end touches the network, held by + `TestPresentReadsDiskOnly` + ([adr-2610181003541997](../../decisions/adrs/2610181003541997-the-away-mode-s-stop-check-reads-the.md), + which refines adr-38 for this verb's meaning alone). Inbound, abcd listens on a network only through the dashboard, only while someone has started it on purpose, and only for the people let in: it listens on this computer's own Tailscale addresses alone, never every diff --git a/.abcd/development/brief/04-surfaces/01-ahoy.md b/.abcd/development/brief/04-surfaces/01-ahoy.md index 361c799e3a..d5574cb84a 100644 --- a/.abcd/development/brief/04-surfaces/01-ahoy.md +++ b/.abcd/development/brief/04-surfaces/01-ahoy.md @@ -659,7 +659,7 @@ carries the prompt-router entries it expects. Neither install nor uninstall ever mutates it: the manifest is plugin-static. A missing or malformed manifest surfaces as a non-resolvable diagnostic. -The shipped manifest wires seven event types, and every event command is a +The shipped manifest wires nine event types, and every event command is a resolving shim rather than a plain binary call. Five of them self-provision. `UserPromptSubmit`, `PreToolUse`, `PostToolUse` and `PreCompact` each attempt `hooks/bootstrap.sh` only when the plugin-root binary is missing, recording the @@ -668,8 +668,10 @@ window. `UserPromptSubmit`, the hook that runs on every message, declares a 120-second `timeout`, the time a salvage on a slow link has before the host cancels it; `PreToolUse`, `PostToolUse` and `PreCompact` declare none and take the host's ten-minute default, so a slow first download finishes there. `SessionStart` -declares 240 seconds, the script's worst case with room to spare, and -`SessionEnd` and `SubagentStop` declare none. Every event that runs the script, +declares 240 seconds, the script's worst case with room to spare, +`SessionEnd`, `SubagentStop` and `StopFailure` declare none, and `Stop` declares +30 seconds, room for the away mode's stop check to make its one forge listing +under its own ten-second deadline and its git reads. Every event that runs the script, `SessionStart` included, names a `statusMessage` the host shows as its spinner text while the hook runs, so the wait is never a silent stall: the salvage itself sends the script's output nowhere. The message states no duration, @@ -689,6 +691,12 @@ capture (iss-2608210934566223). Each resolves the plugin root, then `PATH`, then says in one line that the transcript was not captured. `SubagentStop` never returns a non-zero code of its own beyond that refusal, because exit 2 is the host's BLOCKING status on that event and would stop the sub-agent finishing. +`Stop` and `StopFailure`, which carry the away mode's stop check +(spc-2610180914469489), download nothing either: both fire at a turn's end, where +a fetch would hold the session, and each resolves the plugin root, then `PATH`, +then says in one line what did not happen, with exit 1, never the `Stop` +event's blocking 2: `Stop` that the stop check did not run, `StopFailure` that +a turn ended on an API error was not recorded. **The `PATH` rung is owned-only** (GHSA-gx3m-3224-qqcv, CWE-426). It accepts only an absolute resolution out of a directory that is neither under the shim's diff --git a/.abcd/development/brief/04-surfaces/08-abcd.md b/.abcd/development/brief/04-surfaces/08-abcd.md index e3d24a553b..b1ee3efb4d 100644 --- a/.abcd/development/brief/04-surfaces/08-abcd.md +++ b/.abcd/development/brief/04-surfaces/08-abcd.md @@ -22,14 +22,15 @@ answer. | Verb | Bucket | Status | |---|---|---| -| `mode` | — | shipped | | `peers` | — | shipped | This is the bare command's chapter, so the rule compares its table with the command tree in neither direction: the surfaces index is what covers the -top-level verbs. The rows are the two top-level verbs this chapter documents -beneath the board, and neither registers a sub-verb of its own. The record-id -form is a positional on the root, not a sub-verb. +top-level verbs. The row is the top-level verb this chapter documents beneath +the board, and it registers no sub-verb of its own. `abcd mode`, which stores +the badge's state and keeps the away mode, has its own chapter, +[`26-mode.md`](26-mode.md). The record-id form is a positional on the root, +not a sub-verb. ## What ships today @@ -621,12 +622,6 @@ _Generated from the command tree; a drift test fails `go test` when this appendi | `--version` | bool | | `--view` | string | -### `abcd mode` - -Sub-verbs: none. - -Flags: none. - ### `abcd peers` Sub-verbs: none. diff --git a/.abcd/development/brief/04-surfaces/17-guard.md b/.abcd/development/brief/04-surfaces/17-guard.md index 420fef1656..2e61ae0b47 100644 --- a/.abcd/development/brief/04-surfaces/17-guard.md +++ b/.abcd/development/brief/04-surfaces/17-guard.md @@ -3,8 +3,10 @@ A destructive shell command typed by accident costs a person hours: a force push that erases a colleague's commits, an `rm -rf` with an unlucky glob, a `git clean` over uncommitted work. `/abcd:guard` catches those before they run -and says what to run instead. It writes nothing, and it costs nothing to have -on: an unrecognised command passes through untouched. +and says what to run instead. It writes nothing but the record of a branch an +agent creates and, when it admits abcd's own question where the badge shows, +the waiting-on mode and the marker saying a question is open (both below), and +it costs nothing to have on: an unrecognised command passes through untouched. Two things use it. A person or a script asks it about one command line. A compatible agent harness asks it about every command the agent is about to run, @@ -206,6 +208,64 @@ them, is not a decision: the question runs and the hook says so on exit 1, the same fail-open-loud contract below. The manifest's pre-tool-use matcher names the shell tool and the question tool, and nothing else. +**While the person is away** (spc-2610180914469489, "The question reroute"; +dcn-2610180937177502, dcn-2610181003477194), the gate reads the away record +(`internal/core/autonomy`, the person's word that they are away, set through +`abcd mode`) before anything above. While it holds, every call to the question +tool is refused with the host's deny and one reason, whatever the chip: abcd's +question or another +tool's, chipped or not, and a questions field the check cannot read alike, +from any working copy of the repository. Nothing past the away check runs, so +no mode is set from a chip and no question is marked open; the badge's +chip-set path is never reached while away. The reason names none of the asking +rules, so the agent reroutes the question rather than redrafting it. It routes +the question by the act it asks about (`autonomy.QuestionRoute`, which carries +the list of acts only the person may approve): such an act is queued with +the queue `abcd mode` keeps for their return, and the session carries on with +other work. The acts are an embargo close's approval digest, the embargo +triage verdict, the drain's hand-back, the confirmed apply verb for remote +configuration, a release sign-off (a cut, or a step of one, that asks for the +person by name or that no standing instruction of theirs allows; a standing +permission for automatic cuts stands, and the away mode adds none), pushing, +opening or merging a pull request without a standing instruction of the +person's (the away mode is never that instruction), publishing, a visibility +change or an outward comment or message, a new dependency, a secret, a +credential or an App, a step only a website or a browser can do, a +destructive act, anything that spends money, and any other act a command page +or a brief invariant names as the person's. Every other question, product or +technical, is decided and recorded with `abcd decide`, with its grounds and +how to reverse it. **Nothing detects the act mechanically:** the agent +classifies its own question from the reason's list, and the guard's registry +entries enforce only what they name, a force push and a skipped hook among +them. An away record or lock the store refuses reads as not away: the question +is checked as if the person were present, and the hook says so on stderr, +with exit 1 where it would otherwise admit the question silently. + +## The created-branch recorder + +The pre-tool-use hook also records each branch a shell command creates, for +the away mode's reading of the session's own branches (itd-2610180912189125, +spc-2610180914469489 step 3). The stacked drain branches of 2026-10-17 were cut +with a plain `git worktree add` and left no upstream, no entry in the +coordinating copy's reflog, no run state and no record id, so nothing else could +tell they were the session's. Before the guard judges the command, the hook +reads it for `git worktree add -b|-B `, `git switch -c|-C `, `git checkout +-b|-B ` and `git branch ` (each recorded only when no branch of that name +exists yet), a bare `git worktree add ` (the branch git names after the +folder) and `git worktree add `, and `git branch -m|-M +`, which carries a record from the old name to the new. Branch names match +as whole names, so `feat/x` never stands for `feat/x2`. A word a substitution +or a variable prints is read as no branch. + +The record is kept whether or not the person is away, under the working copy's +key (the reservations' copy key) in the away store, `~/.abcd.noindex/away//`, +written under the store's lock through its atomic writer, in a list capped at +the hundred most recent, the oldest dropped. The recorder records and **refuses +nothing**: a store it cannot write leaves one stderr line and the command goes +on to the guard's own judgement, and a checkout abcd does not manage (no +`.abcd/` at its root or at its main worktree's) records nothing. A record made for a command that then fails names a branch that does +not exist, and the away mode's reading ignores it. + ## Fail-open-loud A broken guard never bricks a session and never silently stops protecting one. diff --git a/.abcd/development/brief/04-surfaces/26-mode.md b/.abcd/development/brief/04-surfaces/26-mode.md new file mode 100644 index 0000000000..fcc9de183d --- /dev/null +++ b/.abcd/development/brief/04-surfaces/26-mode.md @@ -0,0 +1,121 @@ +# `/abcd:mode` — Whose Answer the Loop Waits On, and the Away Mode + +`/abcd:mode` holds two facts the agent loop is judged by. The first is the +waiting-on state behind the status line's badge: `managed`, `facilitator` or +`product-thinker`, stored per checkout at `.abcd/.work.local/mode`, printed by +bare `abcd mode` and set by `abcd mode ` (itd-200, spc-70). The badge, +who sets it and when it resets are described with the board that shows it, in +[`08-abcd.md`](08-abcd.md) ("The presence line" and "The badge is true at +every stop"). The second is the away mode, below: the person's word that they +are away and sessions should work on (itd-2610180912189125, +spc-2610180914469489). + +## Sub-verbs + +> _Machine-checked (`surface_coverage`, spc-27): each row records the verb's +> adr-40 bucket (`lint` / `review` / `audit` / `gate`, or `—` for a +> non-assessment verb) and its existence (`shipped` / `staged`). The existence +> fact is verified against the committed command-tree snapshot in both +> directions. The bucket cell is checked for membership of the closed adr-40 +> vocabulary only: the snapshot carries no bucket field, so a bucket that is +> wrong but legal passes, and that cell stays a review-grain claim._ + +| Verb | Bucket | Status | +|---|---|---| +| `autonomous` | — | shipped | +| `back` | — | shipped | +| `queue` | — | shipped | + +## The away mode + +Beside the badge, and never a fourth word, the away mode records the person's +word that they are away and sessions should work on. Setting it records, once +for every working copy of the repository on the machine, who set it (the +holder name reservations resolve without a network read, else git's +`user.name`), from which working copy, when, and when it ends. The record +lives in `~/.abcd.noindex/away//`, keyed on the root commit as the worktree +store is. + +**When it ends.** The end is a clock time (its next occurrence, local time) +or a timestamp, or nothing, which leaves the record open-ended. Every record +ends seven days after it was set at most, so an end past that, or one already +past, is refused with nothing written. The person may also say that pushes +wait for their return; that word is kept in the record, narrows what sessions +do, and grants nothing. A prompt never ends the mode; its time does, or the +person's word that they are back. + +**The queue and the return.** While the record holds, a session keeps a step +for the person's return instead of asking them: an act only the person may +approve. On their return the record is ended by word, which prints one plain +line per queued step, per item handed back and per recorded stop (a stop at +the host's cap labelled "possibly forced by the cap"), records who ended it and +from which working copy, and closes the record, on a record whose time has +passed as on one that holds. + +**It grants no rights.** The person's own standing instruction stays what +decides whether a session commits, pushes, or opens or merges a pull request. + +**The lock and the checks.** Every change to the record holds a lock file +beside it and re-reads the record inside the lock, so two writers never lose +an update and a check that read the record before the person came back never +writes it back; only ending the mode goes around a lock it cannot have +(below). The record and the lock are used only when each is a regular file +this account owns that no one else can write, within 64 KiB: one that fails +those checks, or does not parse, reads as not away, and queueing a step +refuses it, naming the file. Setting the mode refuses the same, except that it +carries a record merely past 64 KiB, letting its oldest lines go. + +**Ending never refuses on the record.** The person's word that they are back +ends the mode whatever state the record or its lock is in. A record past +64 KiB is printed whole and kept with its oldest lines let go; one that does +not parse, or is not safely this account's, is replaced by a closed record, +printing nothing from it; and a lock not had within two seconds, or not safe +to take, is gone around, closing without it. Each is named on its own line. + +**The badge while away.** While the record holds, parking the badge on the +product thinker or the technical facilitator is refused, naming +`abcd decide`, since a stop parked on a person who is away is a question +nobody answers. + +Every refusal of the away verbs exits 2 with nothing written: an unreadable +or past end, one past seven days, a checkout without the local tier (checked +only when setting the mode), no checkout, a queued step with no record holding +or with no words, and, when setting the mode or queueing a step, a lock not +had within two seconds and a record or lock that fails the checks. Ending the +mode refuses only where there is no checkout, no home or no root commit to key +the record on. None makes a network request. + + + +## Appendix: the shipped surface + +_Generated from the command tree; a drift test fails `go test` when this appendix and the tree disagree. It lists flags and sub-verbs only. What each flag means is in the [CLI reference](../../../../docs/reference/cli/commands.md), and exit codes, output fields and behaviour are the prose's to state._ + +### `abcd mode` + +Sub-verbs: `abcd mode autonomous`, `abcd mode back`, `abcd mode queue`. + +Flags: none. + +### `abcd mode autonomous` + +Sub-verbs: none. + +| Flag | Type | +|---|---| +| `--hold-pushes` | bool | +| `--until` | string | + +### `abcd mode back` + +Sub-verbs: none. + +Flags: none. + +### `abcd mode queue` + +Sub-verbs: none. + +Flags: none. + + diff --git a/.abcd/development/brief/04-surfaces/README.md b/.abcd/development/brief/04-surfaces/README.md index 7ae7677dbc..1c00d4274a 100644 --- a/.abcd/development/brief/04-surfaces/README.md +++ b/.abcd/development/brief/04-surfaces/README.md @@ -34,7 +34,7 @@ are wiring rather than user-facing surface are listed separately under | 23 | `/abcd:reading` | shipped | Assemble what a cold reading may see, prove it, and validate what comes back | [`23-reading.md`](23-reading.md) | | 24 | `/abcd:decide` | shipped | Record a decision as one file that merges cleanly, list every decision newest first, or mint an ADR with its id, date and skeleton | [`24-decide.md`](24-decide.md) | | 25 | `/abcd:worktree` | staged | Keep session and agent worktrees in a machine-scoped store rather than beside the checkout (design target — [itd-2609091014076309](../../intents/planned/itd-2609091014076309-session-and-agent-worktrees-live-in-a.md)) | [`../05-internals/03-configuration.md` § The worktree store](../05-internals/03-configuration.md#the-worktree-store) | -| 26 | `/abcd:mode` | shipped | Say whose answer the agent loop is waiting on, so the status line and the board show it | [`08-abcd.md`](08-abcd.md) | +| 26 | `/abcd:mode` | shipped | Say whose answer the agent loop is waiting on, so the status line and the board show it, or that you are away and sessions should work on | [`26-mode.md`](26-mode.md) | | 27 | `/abcd:implement` | shipped | Share one autonomous run between two sessions: claim a record before its lane, keep the second session inside its bounds, and compare the ways of dividing the work from the run log | [`27-implement.md`](27-implement.md) | | 28 | `/abcd:peers` | shipped | See what the sibling worktrees and local branches hold before capturing, fixing or filing anything | [`08-abcd.md`](08-abcd.md) | | 29 | `/abcd:report` | shipped | Tell abcd about a defect or propose an enhancement from a repository it manages, into an inbox in your own account | [`29-report.md`](29-report.md) | diff --git a/.abcd/development/brief/05-internals/03-configuration.md b/.abcd/development/brief/05-internals/03-configuration.md index 6ae7592fd4..22f20b95e4 100644 --- a/.abcd/development/brief/05-internals/03-configuration.md +++ b/.abcd/development/brief/05-internals/03-configuration.md @@ -1326,14 +1326,20 @@ names a `statusMessage`, the host's spinner text while the hook runs, because th salvage sends the script's output nowhere; the text states no duration, since those limits differ. A test pins every event's timeout and the message (`internal/surface/cli/hooks_timeout_test.go`). `SessionEnd` and -`SubagentStop` are the two +`SubagentStop` are among the four exceptions and download nothing at all: each fires where the harness cancels a slow hook rather than wait — one as the session exits, the other inside a live session as a sub-agent finishes — so a fetch there races that cancellation and loses the transcript the hook exists to capture (iss-2608210934566223). Both resolve the plugin root, then PATH, then say the transcript was not captured. `SubagentStop` additionally never exits non-zero beyond that refusal, because exit 2 is the host's -BLOCKING code on that event. +BLOCKING code on that event. `Stop` and `StopFailure` carry the away +mode's stop check (`hook stop`, `hook stop-failure`, spc-2610180914469489) and fire +at a turn's end, where a fetch would hold the session; each resolves the plugin +root, then PATH, then says what did not happen, at exit 1, never `Stop`'s +blocking 2: `Stop` that the stop check did not run, `StopFailure` that a turn +ended on an API error was not recorded. `Stop` declares a 30-second `timeout`, room for the check's one forge +listing under its own ten-second deadline; `StopFailure` declares none. `SessionEnd` carries `hook session-end`, which stages the session's own transcript; `SubagentStop` carries `hook subagent-stop`, which stages a finished sub-agent's diff --git a/.abcd/development/decisions/adrs/2610181003541997-the-away-mode-s-stop-check-reads-the.md b/.abcd/development/decisions/adrs/2610181003541997-the-away-mode-s-stop-check-reads-the.md new file mode 100644 index 0000000000..af25e1fb3b --- /dev/null +++ b/.abcd/development/decisions/adrs/2610181003541997-the-away-mode-s-stop-check-reads-the.md @@ -0,0 +1,87 @@ +--- +id: adr-2610181003541997 +slug: the-away-mode-s-stop-check-reads-the +status: proposed +date: 2026-10-18 +supersedes: null +superseded_by: null +related_intents: [itd-2610180912189125] +related_rfcs: [] +related_adrs: [adr-38] +--- + +# ADR-2610181003541997: The away mode's stop check reads the forge once, as its documented meaning + +## Context + +Brief invariant 7 says abcd asks the network nothing on its own: implicit +checks read disk only, and a fetch happens only when the user invokes a verb +or flag whose documented meaning is that fetch +([adr-38](0038-implicit-checks-are-disk-only.md)). The stop check of +itd-2610180912189125 runs from the host's Stop hook at every turn's end, which +no one invokes by name. + +Two of the four kinds of owed work live only on the forge: whether a +session's own pull request is behind its base or failing its checks, and +whether one carries a reservation's work. Plan review round 2 showed that the +disk cannot answer the first in the night: the last-fetched default branch is +stale, so a pull request that fell behind after the last fetch never reads as +behind (blocker 2, dcn-2610181003464379). The drain's own reads of the remote +were written into invariant 7 the same way, as the documented meaning of its +verb. + +## Decision + +We will treat setting the away mode (`abcd mode autonomous`, which the +person runs or asks a session to run, and which grants no push rights, +dcn-2610181016510153) as invoking the stop check's forge read while the mode holds: +at most one pull-request listing per check (head and base branch, author, +body, merge state, review decision and check rollup, at most 200 pull +requests), under a ten-second deadline. When the person's forge login is +known (the holder name reservations resolve without a network read), the +listing covers every open pull request in the repository, not only the +person's: the reader needs other people's pull requests to tell a branch +whose open pull request another person authored from the session's own, and +to leave it out. When the login is unknown, the listing asks for the +person's own pull requests alone (`--author @me`) and that rule is not +applied. There is no cache, so a run of +eight blocks costs up to eight listings, each bounded by the deadline; a +reuse cache is remaining work of itd-2610170638184486 (dcn-2610181017056307). + +We will keep the check disk-only whenever the away mode does not hold: no +turn's end while the person is present touches the network. + +We will name a failed or late read, never hide it: the check fails open to +the disk kinds alone, with the pull requests named as not read. A branch +with a pull request is then compared on disk with the last-fetched default +branch, naming the fetch time, and a reservation is never reported as +uncarried, so a failed read cannot lead a session to open a duplicate pull +request (plan review round 3). + +We will add this listing to invariant 7 as the away mode's documented +meaning, beside the drain's. + +## Alternatives Considered + +- **Disk only, always.** Keeps invariant 7 untouched. Rejected: "behind" is + then measured against a default branch nothing refreshes overnight, and a + reservation's carrying pull request cannot be seen, so the stall the slice + fixes (iss-2610170619247185) recurs on exactly the pull requests that fell + behind in the night. +- **A fetch of the default branch at each check.** Answers "behind" from + disk after a fetch. Rejected: it is a network read all the same, it still + cannot see checks, reviews or links, and it would run while the person is + present unless also confined to the away mode. +- **One listing per check while away, chosen.** One call, bounded in time, + and only inside a window the person opened. + +## Consequences + +- Invariant 7 gains a third verb-scoped meaning; the zero-network test + harness must show the present-mode check makes no forge call + (TestPresentReadsDiskOnly). +- The forge client must be signed in for the away check to read pull + requests; when it is not, the check says so and judges from disk, which the + intent's scope conditions already name. +- A later intent that wants the forge read while the person is present needs + its own amendment; this record does not cover it. diff --git a/.abcd/development/decisions/adrs/README.md b/.abcd/development/decisions/adrs/README.md index d372cbac8a..084dce4990 100644 --- a/.abcd/development/decisions/adrs/README.md +++ b/.abcd/development/decisions/adrs/README.md @@ -215,4 +215,5 @@ The intent lint (a Go implementation) extends to verify these reciprocally. | [adr-2610150729333337](2610150729333337-a-status-branch-moves-without-a.md) | A status branch moves without a preflight receipt | proposed | 2026-10-15 | | [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 | 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 d03d8ab38c..52b5691114 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 @@ -81,7 +81,71 @@ unowned pull request. ## 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._ +_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._ + +- **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 + working copy's local tier holds the design decisions signed off, whether + the technical facilitator is set to auto, the kinds of choice still raised + with a person, each item waiting and on whom, its trigger promises, and the + work it owns, read from its reservations, its pull requests and its + branches. +- **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** 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 + unreadable file falls back to the last good copy and says so. +- **Given** a checklist promise "when pull request 42 merges, add the + wrapper", **when** the session next looks (at a start, a wake or a stop + check) after 42 has merged, **then** the promise is named as its next item, + with no watcher process kept running. +- **Given** a session whose items all wait on something outside, **when** it + 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** 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. +- **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 + with its grounds and how to reverse it. +- **Given** Nia, the technical facilitator, is set to auto, **when** a session + 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. +- **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 + answer from the product thinker. +- **Given** a session's state shown to a person, **when** what the session + 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 @@ -103,6 +167,61 @@ session's interview on 2026-10-17: one line, what it waits on, when it was last seen), published only when it changes and only after the session has written its checklist; and each autonomous session's own credential. +- **The first slice (itd-2610180912189125, which builds on this draft):** the + away mode held by abcd and repeated at start, compaction and prompt, the + stop check, and the question reroute, carved out on 2026-10-18 as the fix + for iss-2610170619247185 so the security and bug-fix release waits on it + alone (dcn-2610180913379049, dcn-2610180913388445). Its away mode grants + no rights; the person's standing instruction decides what is pushed + (dcn-2610181016510153). This draft keeps the rest and reuses the slice's + away record and hook path. +- **Moved back from the first slice by its review round 1 (2026-10-18, + dcn-2610180937181929), named remaining work of this draft:** + - the report shown once on the first prompt after the away mode ends: the + queued steps, the decisions recorded in the window and the stops the + host forced, with who ended the mode and from which working copy; + - the window's decisions gathered from every working copy, by `abcd + decide` appending each decision's id to the away record under its lock + while the window is open, since sibling worktrees hold their own + decision records; + - the status line naming the away mode and its end; + - a bare `abcd mode` printing the away record, the queue and the owed + items with no plugin host; + - the brief's surface text for these. + + The slice keeps `abcd mode back` printing the queue and the recorded stops, + one plain line each, so nothing queued goes unread meanwhile. +- **Moved back from the first slice by its trim after review round 2 + (2026-10-18, dcn-2610181016510153 and dcn-2610181017056307, on the release + orchestrator's ruling), named remaining work of this draft:** + - making the away mode the person's act alone, should a later design give + it rights worth guarding: the guard entry refusing an agent's `abcd mode + autonomous` (the binary on PATH and the plugin root's), and its routes + through `sh -c` and a script file a shell runs; the shell-write refusal + on the away store under `~/.abcd.noindex/away/`; the file-tool refusal, + with the PreToolUse matcher gaining `Edit`, `Write`, `MultiEdit` and + `NotebookEdit`; the refused attempts recorded in the store, shown on the + person's next prompt and listed by `abcd mode back`; and the live checks + 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. The slice's away mode grants no push rights, and the + person's standing instruction decides what is sent, so none of this + holds up the stall fix; and since the OS fence (spc-2610161751056096) + covers only runner-launched agents, it stays a mistake filter in the + person's own sessions; + - the guard reading `go run ./cmd/abcd` (go's flags stepped over, an + `@` allowed) and a built `abcd--` as `abcd`, for + every entry that names the program, `abcd-source-ledger-flip` included, + which commands/guard.md today names as unseen. It widens what every + entry matches, so it gets its own security review here rather than + riding on the cut; + - a reuse cache for the away stop check's forge listing (a listing younger + than ten minutes reused, and on a failed read a cached listing younger + than half the default lease used with its age named), so a run of blocks + costs one call; the slice makes one uncached listing per check under a + ten-second deadline; + - anything `abcd mode back` prints beyond one plain line per queued step + and recorded stop: grouping, formatting or a summary of the window. ## Decisions diff --git a/.abcd/development/intents/shipped/itd-2610180912189125-a-session-told-to-work-on-while-its.md b/.abcd/development/intents/shipped/itd-2610180912189125-a-session-told-to-work-on-while-its.md new file mode 100644 index 0000000000..52348bbb0d --- /dev/null +++ b/.abcd/development/intents/shipped/itd-2610180912189125-a-session-told-to-work-on-while-its.md @@ -0,0 +1,394 @@ +--- +id: itd-2610180912189125 +slug: a-session-told-to-work-on-while-its +spec_id: spc-2610180914469489 +kind: standalone +suggested_kind: null +reclassification_history: [] +builds_on: [itd-2610170638184486] +severity: critical +impact: additive +origin: researcher-authored +production_mode: hand-written +related_issues: [iss-2610170619247185] +--- + +# A session left to work on never stops while it has work + +## Press Release + +> A session told to work on while its person is away keeps working until they are back. Alice, a solo founder who is also the product thinker, leaves for the evening and says the sessions should work on until 09:00, running one command or asking a session to. abcd records that once for the repository, with who set it and when it ends, and repeats it to every session at each start, after each compaction and with every prompt, so no summary and no relay between sessions can drop it. A session that tries to end its turn while it still has work it can do alone (a reservation whose work is not yet handed in, a pull request of its own that has fallen behind or is failing, a branch it has not pushed, a branch stacked on another change that is still open) is handed the next of those items instead. One with nothing left to do takes the next ready item, and may stop only with its next look scheduled before its reservations need renewing. A question it would have put to Alice is decided and recorded instead, and an act only Alice may approve (an advisory's approval, a publication, a push her standing instructions do not allow, a new dependency, a secret, a browser step) is queued for their return. The mode itself grants nothing: what a session may push stays whatever Alice's own instructions say. When Alice is back and says so, the mode ends and they see the queued steps and any stop the host forced; from then on nothing is refused, and a stop with work left is only pointed out. + +## Why This Matters + +On the night of 2026-10-16 every session had been told to work on until the +morning, and one sat idle for about ten of eleven and a half hours while it +held work it could do alone (iss-2610170619247185). The instruction lived only +in chat: relayed by a peer, overridden in the session's reading when the +product thinker came back for an hour, then dropped by a compaction summary. +The session ended its turn on a question nobody was there to answer, and a +Claude Code session runs only when something sends it input, so nothing ran +until a peer's message six hours later. The same night a coordinating session +held three finished steps behind a merge order when only their landing order +was fixed (dcn-2610180905291297). + +This is the smallest slice of the checklist draft (itd-2610170638184486) that +removes the stall's core cause, carved out so the security and bug-fix release +waits on it alone (dcn-2610180913379049, dcn-2610180913388445). It needs +nothing on the shared remote, so it does not wait on the coordination intent (itd-2610170634394993). + +## Mechanism + +We expect an autonomous run to stop stalling because the three ways the +session of 2026-10-16 went quiet are each closed where the host gives abcd a +hook: the instruction to work on becomes a record abcd repeats at every start, +compaction and prompt rather than context a summary can drop; ending a turn +with owed work is refused at the Stop hook, the one point every quiet spell +passes through; and a question to an absent person is refused before it can +leave the session waiting. Shown wrong if, in an away window of at least six +hours with owed work seeded at its start, a session following the slice is +idle (no tool call) for more than one hour while owed work remains, or ends +its turn on a question to the person. + +## Scope Conditions + +- A repository abcd manages, with the local tier and abcd's plugin hooks + installed in the host that runs its sessions. +- A host that runs a Stop hook and honours its block decision, a session-start + hook on compaction and a prompt hook, as Claude Code documents them; on a + host without them the mode is still recorded and shown, and nothing is + enforced. +- Sessions on one computer: the away record is held per repository on that + machine, so a session on another computer or in the cloud does not see it. +- The stop check's pull-request reading, made only while the person is away, + needs the forge client signed in; when it cannot read the forge in time it + judges from disk alone, and says so. +- A session that keeps working through the host: the check refuses a stop, it + cannot start a session that is not running. A turn the host ends on an API + error, such as a rate limit, fires no Stop hook and cannot be continued: it + is recorded, and only a wake the session had already scheduled resumes it. +- Known residual: a sibling working copy's branch that no worktree holds any + more still reads as this session's own when both copies commit with the same + email, because nothing on disk tells them apart. + +## What's In Scope + +- **The away mode.** `abcd mode autonomous [--until