Development record
Session and agent worktrees live in a machine-scoped store that abcd lists and reclaims, never beside the user's own projects
Typed links: related_adrs adr-2609091248200336 (the rule this store enacts), adr-2609091248201071 (the root-SHA-keyed sibling store whose shape this copies); refines itd-2609201916151817 (the build loop, whose lane primitive becomes this store's one primitive: its spec's piece 6 takes the store's verb once this ships); related_intents itd-118 (the post-merge tidy that will call prune) and itd-148 (every change in its own worktree, which uses this store's verbs and is blocked_by this draft).
Press Release
abcd keeps every session and agent worktree in one place on your machine, and can tell you what is there.
abcd ahoy worktree add <name>creates a worktree at~/.abcd.noindex/worktrees/<root-sha>/<name>/, keyed on the repository's root commit the way the history and transcript stores already are, and writes nothing beside your checkout. Bareabcd ahoy worktreelists what the store holds for this repository — name, branch, path, whether the tree is clean, whether its branch has merged — and names any worktree of the repository that sits outside the store;abcd ahoy worktree list --allwalks every lane on the machine.abcd ahoy worktree prunereclaims a worktree whose branch has merged and whose tree is clean, moves the worktree's private notes into a dated archive rather than deleting them, reports each removal by name, and leaves everything else where it is, with the reason; a worktree that has gone quiet without merging is pointed out with a summary and goes only when you name it. The/abcdstatus board carries one line: how many worktrees the store holds for this repository and how many are reclaimable. Your project directory holds what you put there."I came back from a run to find twenty-two new folders beside my projects, none of which I had made, and no way to tell which were still in use," said Maya, autonomous-development practitioner. "The isolation was right — parallel agents need separate checkouts. The location was not. Now the checkouts live where abcd keeps the rest of its machine state, one command lists them, one command clears the ones whose work has merged, and my folder is mine again."
Why This Matters
Parallel agents need separate checkouts: AGENTS.md's concurrent-sessions convention makes the checkout the unit of isolation, and the record's gates read the whole tree, so two sessions in one checkout fail each other's gates in both directions. Nothing says where a checkout goes, so each agent picks, and what an agent picks is git's default, a sibling directory. On 2026-09-01 twenty-one spent worktrees (1.4 GB) were removed by hand from the product thinker's project directory (iss-2609020721142452); on 2026-09-06 one session created twenty-two more, beside four unrelated projects, and git worktree list on that checkout named twenty-seven. The product thinker's objection, verbatim: "I don't want a user to be surprised that a folder is all of a sudden full of stuff."
That objection is the rule adr-2609091248200336 records: a tool never creates directories in space that belongs to the user, and agent and session scratch is machine-scoped. This intent is the capability that makes the rule followable — an agent that has somewhere to put a worktree, and a user who can see and reclaim what was put there.
The shape is not new. ~/.abcd.noindex/history/<root-sha>/, ~/.abcd.noindex/transcripts/<root-sha>/ and ~/.abcd.noindex/voyage/<root-sha>/ are each keyed on the repository's root-commit SHA, and internal/core/history/location.go states why: a checkout moves, is renamed, and is cloned twice on one machine, while its root commit changes under none of that. index.json is the sole user-scope registry of every repository abcd knows on the machine, so a cross-repository listing is a walk that already exists. There is a de facto precedent on disk as well: ~/.abcd.noindex/worktrees/488a0aa9/phase-9 and .../park-gates, laid by hand by an autonomous run under abcd's own root commit before the verb exists (dcn-2609260000000007). That is corroboration that the location is the natural one, not authority for the shape — the precedent abbreviates the key, and this store uses the full SHA the sibling stores use.
Two things separate the store from the same pile somewhere less visible, and the intent owns both.
Discoverability. A worktree under ~/.abcd.noindex/ is invisible to ls where the user works, which is the point, and so it has to be visible somewhere else. Git already knows every worktree of a checkout wherever it sits (git worktree list --porcelain), so the store adds no registry of its own. What stands in for one is a proof git can give: a worktree belongs to the store when git lists it as a worktree of this repository, its real path lies inside this repository's lane, and its own common directory is this checkout's. It adds a verb that reads git's answer and says which entries are in the store, which are outside it, and what state each is in; and a line on the /abcd status board, so a user who never types the verb still learns the count.
Lifecycle. A worktree whose branch has merged is garbage, and nothing today notices. Reclaiming is abcd ahoy worktree prune: it removes a worktree only when its branch is merged into the default branch and its tree is clean, moves the worktree's local tier into the notes archive first, and names what it declined and why. It runs when the user runs it; the status board's reclaimable count is what prompts them. Reclaiming automatically on merge is itd-118's post-merge tidy; when that ships it calls this verb rather than inventing a second reclaim.
What's In Scope
abcd ahoy worktree add <name> [--branch <branch>]creates a worktree at~/.abcd.noindex/worktrees/<root-sha>/<name>/for the repository the caller stands in, on a new branch named after the worktree unless--branchnames an existing one, and prints the path. The store is created on first use the way the transcript store is: each level made individually and re-verified as a real directory, never through a symlink, never with a single recursive make, and each level owned by the caller with no group or other write bit. A name that is not one plain path segment (../x,-x, an empty or dotted name) refuses, a name already in the lane refuses, and a repository with no root commit refuses. The build loop's lane code (laneWorktree,ensureStore,adoptWorktreeininternal/core/implement/loop/lane.go) already does this for its own lanes; it moves into the store's package and the loop calls it, so there is one primitive and not two. Nothing is written beside the checkout.- Bare
abcd ahoy worktreeandabcd ahoy worktree listrender the lane for this repository: name, branch, path, clean or dirty, merged or unmerged. On a machine stream a store row's path is home-redacted and an outside row's path uses the display form that stays recognisable wherever it lives. Every worktree git reports for the checkout is a row; one outside the store is marked as outside and never touched.--allwalks every lane under~/.abcd.noindex/worktrees/, labelling each through~/.abcd.noindex/history/index.json(read through the loader that already scrubs it) where the root commit is registered and by SHA where it is not; the worktree's own.gitfile, not the registry's path label, says which checkout a lane belongs to. A lane whose checkout has moved, so that its.gitpointer is dead, renders as "checkout not found; rungit worktree repairfrom the checkout" under its SHA, with no clean or merged column. A directory under~/.abcd.noindex/worktrees/whose name is not a full root commit (the hand-laid eight-digit form) renders as a "not a lane" row rather than being skipped, naming this repository's full-key lane where the abbreviation matches it and saying its worktrees are retired by hand.--jsonemits the same rows. abcd ahoy worktree prune [--dry-run]removes each worktree that belongs to the store (by the proof above) whose branch is merged into the repository's default branch and whose tree is clean, moves its local tier (below) into the notes archive, runs git's own metadata prune for it, and reports each removal by name. A dirty tree, an unmerged branch, a worktree outside the store, a directory in the lane that fails the proof, a worktree git holds locked (with git's lock reason), the worktree prune is run from, and a lane the build loop's state file still holds are each left in place and named with the reason. Prune never forces a removal, so git's own re-check at the moment of removal closes the gap between judging a worktree clean and removing it.--dry-runremoves nothing and reports the same rows;--jsonemits them. The branch is left standing. "Merged" means the forge records the branch's pull request as merged, or every commit of the branch is already on the default branch by content (patch-equivalence); with no forge reachable, patch-equivalence alone decides. Exit codes: 0 when at least one worktree was reclaimed, 1 when the run completed and reclaimed nothing (an empty lane included), 2 on a refusal or fault.- The notes archive. A worktree's
.abcd/.work.local/is ignored by git, so a tree git calls clean can still hold a handover note, a per-machine banlist, logs and scratch. Prune never deletes it: before removing the worktree it moves the tier into a sibling store,~/.abcd.noindex/notes/<root-sha>/<UTC timestamp>-<worktree name>/, created through the same canonical directory primitive as the transcript store (each level0o700, never through a symlink), redacted on write the way transcripts are, and accompanied by a manifest recording the worktree name, branch, source path (home-redacted), time and file list. The report names where the notes went. If the move fails, the worktree is kept and named with the reason. - Quiet worktrees. An unmerged worktree in the lane with no activity for 14 days (repo-configurable) and no open pull request (where a forge is reachable; without one, the 14 days alone) is listed by
pruneas a candidate with a dossier: dirty state first, then branch subjects, diff summary, referenced records and last activity, and a recommendation. It is removed only when named on the command line (prune --yes <name>, repeatable); the core never prompts. A named removal takes the same notes-archive step and the same refusals as a merged one, a dirty tree included: prune never forces, so uncommitted work is committed or discarded by the person first, and the dossier shows it so they know. - One line on the
/abcdstatus board: the count of worktrees in the lane and the count reclaimable, absent when the lane is empty. Both counts come from the worktree and merged-branch scan the bare board already runs forpeers, never from a status read per lane, and the line is the CLI board's alone: it is not part of the status block the site renders. - The concurrent-sessions convention in
AGENTS.mdpoints at the verb, and the plugin page carries it as a host-run step: an agent that needs an isolated checkout (a lane, a subagent, a peer session) runsabcd ahoy worktree add <name>and works in the printed path, never a host's default worktree location. The prose stays host-agnostic. - The worktree directory is
0o700, like the store levels above it:addsets the mode after git creates the directory.
What's Out of Scope
- Deleting branches, tracking refs or remote branches. That is the rest of itd-118's post-merge tidy; prune reclaims the directory and git's record of it, and nothing else.
- Moving a worktree that already sits beside a checkout. The store never moves what it did not create. A sibling worktree is listed as outside and left alone; retiring it is the user's own
git worktree remove, and the sibling worktrees already on the product thinker's machine are a hand cleanup, not a migration. - Worktrees other tools create, in the store or out of it. The precedent lanes stay as they are.
- Retention of the notes archive. Nothing reclaims archived notes; a later tidy of
~/.abcd.noindex/notes/is its own decision. - Any change to the isolation rule itself. The checkout stays the unit of isolation; this intent changes where a checkout lands.
Mechanism
We expect a machine-scoped, root-SHA-keyed store with a list verb and a reclaim verb to end the surprise because the surprise is location, not existence: the same isolation, in a directory abcd owns, found by asking abcd. It fails if agents keep creating worktrees by hand outside the store, and that failure is visible rather than silent — the list verb marks every such worktree as outside, and the status board counts them.
Scope Conditions
- The verbs act on worktrees created through abcd or by an agent following its conventions. A worktree the user placed by hand is theirs: listed as outside, never moved, never reclaimed.
- The store is under the caller's own home and needs no authority the caller lacks. Where the home is unwritable, or a level of the chain is a symlink,
addrefuses and creates nothing. - The lane is keyed on the root commit, so a re-founded repository gets a new lane, as the history store gives it a new entry.
- "Merged" is judged against the repository's default branch, by the forge's recorded pull-request state or by patch-equivalence, so a squash or rebase merge counts; with no forge reachable, patch-equivalence alone decides, and a branch rewritten after its merge so that its content no longer matches stays unmerged.
- One machine. The store is never synced, and its paths never enter a committed file (the privacy-hygiene rule already refuses
/Users/<name>/).
Acceptance Criteria
- Given a checkout of a repository with commits, when
abcd ahoy worktree add <name>runs, then a worktree exists at~/.abcd.noindex/worktrees/<root-sha>/<name>/on the named branch, its path is printed, and the checkout's parent directory holds nothing it did not hold before. - Given the store does not yet exist, when
addruns, then each level is created individually and verified as a real directory, and a symlink at any level refuses the whole creation with nothing written. - Given a store level owned by another account or writable by group or others, when
addruns, then it refuses and writes nothing. - Given a name
../x,-x, or a name already in the lane, or a repository with no commits, whenaddruns, then it refuses naming the reason and writes nothing; given--branchnaming an existing branch, the worktree is on that branch and no new branch is made. - Given the build loop creates a lane, when it does, then it goes through the same store primitive
adduses. - Given a lane holding two worktrees and a third worktree of the same repository beside the checkout, when bare
abcd ahoy worktreeruns, then three rows render, the third marked as outside the store, each carrying its branch, clean-or-dirty state and merged-or-unmerged state. - Given lanes for two registered repositories and one unregistered root commit, when
abcd ahoy worktree list --allruns, then the registered lanes render under theirindex.jsonnames and the unregistered one under its SHA, and a registrypathlabel that no longer exists changes nothing in the rows; a lane whose checkout has moved renders as "checkout not found" with the repair hint, and a directory named by an abbreviated root commit renders as "not a lane", naming this repository's full-key lane when the abbreviation matches it and saying its worktrees are retired by hand. - Given a worktree whose branch is merged into the default branch and whose tree is clean, when
pruneruns, then the directory is gone, git no longer lists it, the branch still exists, and the removal is reported by name. - Given a clean worktree whose branch was squash-merged (no ancestry, every change on the default branch by content), when
pruneruns with no forge reachable, then it is reclaimed. - Given a reclaimable worktree whose
.abcd/.work.local/holds a handover note and a log, whenpruneruns, then both files exist, redacted, under~/.abcd.noindex/notes/<root-sha>/<timestamp>-<name>/beside a manifest naming the worktree, branch, time and files, the report names that path, and nothing from the tier is deleted; given the archive cannot be written, the worktree is kept and named with the reason. - Given an unmerged worktree with no activity for 15 days and no open pull request, when
pruneruns, then it is listed as a quiet candidate with its dossier (dirty state first) and left in place; whenprune --yes <name>runs, then its notes are archived and it is removed, and no other quiet candidate is touched; given the named candidate holds uncommitted changes, it is kept and named with that reason, because prune never forces. - Given a store refusal (a symlinked level, another account's directory), when
pruneruns, then it exits 2 and removes nothing. - Given a worktree with uncommitted changes, one on an unmerged branch, and one outside the store, when
pruneruns, then all three remain, each named with its reason, and the exit code is 1; given the same lane plus one merged, clean worktree, the exit code is 0. - Given a directory inside the lane that git does not list as a worktree of this repository, or whose common directory is another checkout's, when
pruneruns, then it is left untouched and reported — the store never deletes what fails its proof of belonging. - Given a merged, clean worktree that git holds locked, one that is prune's own working directory, and one the build loop's state file still holds, when
pruneruns, then all three remain, each named with its reason, and no removal is forced. - Given a lane holding a reclaimable worktree, when
prune --dry-runruns, then nothing is removed and the rows are the ones a real run would report;--jsonemits the same rows. - Given a repository whose lane holds worktrees, when the
/abcdboard renders in that checkout, then one line names the count held and the count reclaimable; given an empty lane, the line is absent; the line never appears in the site's rendered status block, and rendering it starts no per-lane status read. - Given the checkout has been moved to another path, when
abcd ahoy worktreeruns from the moved checkout, then the same lane renders, because the key is the root commit and not the path. - Given
abcd ahoy worktree add <name>has run, when the new worktree's directory is inspected, then its mode is0o700. - Given
AGENTS.md's concurrent-sessions convention, when it is read, then it names the verb as the way a session gets its own checkout, and names no sibling-directory form. - Given the plugin page for
ahoy, when it is read, then it carries the host-run step: an agent needing an isolated checkout runsabcd ahoy worktree add <name>and works in the printed path, never a host default location.
Prior Art
- adr-2609091248200336 — the rule: a tool never creates directories in user-owned project space; agent and session scratch is machine-scoped. This intent is its enforcement on the write side.
- adr-2609091248201071 and
internal/core/history/location.go— the sibling store this copies: user-level, root-SHA-keyed as a directory, self-creating through one seam, never through a symlink. The reasoning for the key is written there and is not repeated here. - adr-35 — the voyage store moved to
~/.abcd.noindex/on the same ground, and embark's destination gate "never overwrites a directory abcd did not produce": the same stance, at the destination, that prune takes in the lane. - iss-2609020721142452 — the captured defect and its three options; this intent is its option 2, with the list and prune verbs and the merged-branch rule the issue asked for.
- itd-118 — the post-merge tidy, which names the worktree among the residue a merge leaves; this intent supplies the store it tidies and the verb it calls.
AGENTS.md§ Concurrent sessions — the isolation rule this intent gives a location to.git worktree list --porcelainandgit worktree prune— the primitives; the verbs wrap them and add the store, the state columns and the refusal reasons, and no registry of their own.durable-state-lives-where-the-platform-says-it-survives— adjacent: that rule chooses a home by what survives the platform's lifecycle; this store's home is chosen by whose space it is. The store satisfies both, and neither is the other.
Decisions
Ruled at this draft's planning interview, 2026-09-30 (the product thinker and technical facilitator, one question at a time):
- Decomposition: file as is. One intent; the store primitive and an invariant citing adr-2609091248200336 go to the brief's internals; no new ADR or principle.
- Merged means the forge's recorded pull-request state or patch-equivalence, working without a forge (the ruling itd-148's 2026-08-26 interview made, carried here).
- Quiet worktrees (14 days, repo-configurable, no open pull request) are surfaced with a dossier and removed only when named (itd-148's 2026-08-26 ruling, carried here).
- The local tier is moved, never deleted, into a notes archive that is a sibling of the transcript store, recorded and timestamped.
- The verb is a sub-verb of
ahoy, which owns both stores. - The plugin page carries
addas a host-run step. - The worktree directory is
0o700. - Exit codes: 0 reclaimed something, 1 reclaimed nothing, 2 refusal or fault.
- A short-key directory's row names the full-key lane.
- The store's root is spelled under the renamed home (2026-10-04, recorded with spc-2610031309233367's step 4, no interview):
~/.abcd.noindex/worktrees/<root-sha>/<name>/and~/.abcd.noindex/notes/<root-sha>/, the location adr-2610031751065746 sets when it supersedes adr-2609091248200336's~/.abcd/spelling. The trust rule and everything else in this record are unchanged.
SOTA
Surveyed on 2026-10-23. The host-side findings are shared with the
security and isolation SOTA note
(its itd-148 section) and the autonomous loops SOTA note.
The store-manager comparison is new here. The internal lineage stays under
## Prior Art.
- The first host now makes worktrees itself, inside the checkout. A
sub-agent's
isolation: worktreegives it a temporary worktree, held undergit worktree lockwhile it runs, at.claude/worktrees/under the repository by default. Headless runs leave their worktrees and locks behind for a later sweep. The host refuses a worktree whose git identity resolves into the main checkout (Claude Code worktrees documentation, cited in both notes). The host's default lands exactly where adr-2609091248200336 forbids, which makes "never a host's default worktree location" a live rule rather than a hypothetical. - The host offers the seam to relocate. A
WorktreeCreatehook "replaces the default git behavior", so a hook callingabcd ahoy worktree addcan put the host's own isolation worktrees in the store (Claude Code hooks documentation, cited in the autonomous loops note). That turns the AGENTS.md convention into a mechanism on that host. The seam is a pair of hooks, not one: the host's periodic sweep keeps every worktree without its own marker, a hook-made one included, and removes a hook-made worktree only through a pairedWorktreeRemovehook (Claude Code worktrees documentation). - Third-party managers split on where worktrees live. gwq keeps a central
base directory,
~/worktreesby default, laid out by remote URL ({{.Host}}/{{.Owner}}/{{.Repository}}/{{.Branch}}). It lists every repository's worktrees with-gand deletes an unmerged branch only with--force-delete-branch(gwq). Worktrunk computes paths from a template, and its quick start shows the sibling layout~/repo.feature-auth, the beside-the-project placement the ADR rules out (Worktrunk README). Worktrunk'swt mergeremoves worktree and branch together after a local merge. The record keeps branch deletion out of scope (itd-118). - A URL key and a root-commit key fail differently. A URL key (gwq)
breaks on a rename, a fork and a repository with no remote. The root-commit
key survives all three, at the cost of putting every clone of one project on
the machine in one lane. The prune proof already ties a worktree to its
checkout. A name collision between two clones'
addis the remaining case. - Merged-and-clean is the field's prune rule too. gwq's force flag for unmerged branches and the host's lock-while-running match the record's refusals: dirty, unmerged, locked, outside the store.
Path 2: native, with git's own worktree and prune as the primitives and
the root-commit key the sibling stores use. The host's WorktreeCreate hook
is named as the seam that routes a host-made worktree into the store. No
dependency. The record was ruled at its 2026-09-30 planning interview; this pass raises
two questions below.
Questions this pass raised for the fit-challenge before the build. They are kept here, not under Open Questions, so they do not hold a build the record already rules ready:
- Two clones of one project on one machine share a lane, because they share a root commit. What does
adddo when the other clone already holds a worktree of that name: refuse naming the other checkout, or qualify the name? - Should abcd ship the host's
WorktreeCreatehook, so a host-made isolation worktree lands in the store, or leave the AGENTS.md convention as the only route on that host? If it ships it, does it ship the pairedWorktreeRemovehook too, since the host's sweep never removes a hook-made worktree and a sub-agent's throwaway lane would otherwise sit in the store until prune names it?
Open Questions
None open.
Audit Notes
Empty. Populated by intent-auditor when intent moves to shipped/.
Grounds
- pursued: working copies keep piling up with nothing to list or clear them; wrong if, after it ships, copies still accumulate outside the store.
Frontmatter
| id | itd-2609091014076309 |
|---|---|
| type | intent |
| lifecycle | planned |
| kind | standalone |
| severity | major |
.abcd/development/intents/planned/itd-2609091014076309-session-and-agent-worktrees-live-in-a.md
open on GitHub ↗ · commit history ↗
References4
- adr-2609091248201071The transcript corpus is a sibling store that creates itself through the canonical directory primitiverelated
- itd-118Merged work leaves no residue: abcd-managed repos delete a PR branch on merge, and abcd tidies the stale local branch, tracking ref, and worktreerelated
- itd-148Every change starts in its own worktree in abcd's store: the primary checkout is a read-only surface, and abcd blocks mutations there for every session but a declared coordinatorrelated
- itd-2610161621364075The agents abcd launches on its own work inside a sandbox the operating system enforcesrelated
Referenced by20
- adr-2609091014087993A tool never creates directories in user-owned project space; agent and session scratch is machine-scopedrelated
- adr-2609091248200336A tool never creates directories in user-owned project space; the store's location binds now and its verbs bind when the store shipsrelated
- adr-2610031751065746The worktree store lives under the renamed home, `~/.abcd.noindex/worktrees/<root-sha>/<name>/`, and the rest of its rule is unchangedrelated
- iss-2610130501296659Merged branches and spent worktrees pile up because nothing removes them: by the close of 2026-10-12 the checkout held 137 local branches (83 already merged into main), 94 worktrees and 4 merged remote branches, and a person had to ask for the cleanup by hand. The person asked (2026-10-13) that a future drain and similar runs do this automatically. Done by hand that day with a read-only inventory (merged into origin/main, contained in a kept branch, PR state, worktree clean, local-tier file count) and a dry run before acting: 92 local branches, 40 worktrees and 4 remote branches removed, every worktree's local tier archived first. Draft itd-118 (merged work leaves no residue) and planned itd-2609091014076309 (the worktree store and its reclaim) describe the want; neither runs at the end of a drain or a cut.related
- iss-2610230014564359The worktree store's spec (spc-2609301811532881) never mentions the embargo store, which lives inside the same lane as ~/.abcd.noindex/worktrees/<root-sha>/embargo/ (internal/core/embargo/storepath, Name = "embargo"): a later step's add could take the name embargo, and list, prune and quiet-candidate scans could judge or reclaim the embargo store's detached worktree as a stale lane, so an unpublished advisory's private records could be archived or removed; found by the ruthless review of the store's step 1 on 2026-10-23, which judged nothing reaches it yet because lane names cannot be embargorelated
- iss-2610231003000222The worktree store's merged judgement keeps for good a branch whose merge it cannot see from the reflog or the forge: a branch rebased locally and then merged leaves no commit entry of its own in its reflog, and so does a merged branch whose reflog has expired; with no forge answer the content tests are always false once the tip is an ancestor of the default branch, so prune keeps the worktree on every run although its work has landed. Found by the Opus review of the store's steps 3 and 4 on 2026-10-23; it errs on the safe side, never reclaiming what it cannot prove.related
- iss-2610231011465325The worktree store's merged judgement reads a branch's own commits from its reflog, and a reflog can carry another branch's history: a branch copied from a branch that was itself created, then force-reset and left until its unreachable reflog entries expire, keeps the source's creation entry and commit entries, so it is judged merged by ancestry with no commit of its own, and prune could remove its clean worktree with its ignored files. Every commit it holds is already on the default branch, so no commit is lost. Found by the Opus review of the store's steps 3 and 4 on 2026-10-23, after three earlier reflog shapes were closed.related
- iss-2610231125086511Two concurrent runs of the worktree store's add for the same name race: the second run's git fails because the branch already exists, and the adopt-after-failure path then claims the first run's worktree, reports that the checkout hook failed with git's branch-exists message, which is false, and lays the local tier inside a worktree the first run may still be checking out. Found by the Opus review of the store's step 2 on 2026-10-23.related
- iss-2610231125096250When the worktree store's add finishes a worktree git made after a failing checkout hook, and the finishing itself fails (the start record or the local tier cannot be written), the refusal drops the hook's exit status, so a person sees only the finishing failure and not that the hook failed first. Found by the Opus review of the store's step 2 on 2026-10-23.related
- iss-2610262031473469The naming rules forbid a plain unfiltered `<verb> list`, but itd-2609091014076309 makes `abcd ahoy worktree list` render the same thing as the bare verb.related
- iss-2610262037293150The worktree-store intent calls a pruned worktree's archived local tier its notes and keeps it under ~/.abcd.noindex/notes/; the memory brief's notes are the memory notes in memory.jsonrelated
- iss-2610262039245351The worktree-store intent makes its verb a sub-verb of `ahoy`, but the surfaces register lists it as a top-level `/abcd:worktree`related
- itd-148Every change starts in its own worktree in abcd's store: the primary checkout is a read-only surface, and abcd blocks mutations there for every session but a declared coordinatorblocks
- itd-2609091034175565Before an agent starts on an item, it reserves it on the shared remote, and everyone who works on the project sees the reservationrelated
- itd-2609091416295622A session sees the records its peers hold before it mints or fixes onerelated
- itd-2609201916151817One verb takes a READY intent to delivered, and refuses while a decision is openbuilt on by
- itd-2609221656373558Two sessions share one run, and the run measures which way of dividing the work is bestrelated
- itd-2610161621364075The agents abcd launches on its own work inside a sandbox the operating system enforcesbuilt on by
- itd-2610222214476887One command shows everything a repository has left lying around, and clears what is provably finishedblocks
- spc-2609301811532881The worktree store: `abcd ahoy worktree add|list|prune`, one lane primitive, and a notes archive beside itimplemented by
mentions28
- adr-35The lifeboat is a coverage experiment — read-only, out-of-tree, and proven before it is packed (supersedes adr-4)
- dcn-26090900000000022026-09-09 — A tool never creates directories in user-owned project space, and agent and session scratch is machine-scoped (maintainer, on finding twenty-two agent worktrees created as siblings of the checkout beside their other projects: "I don't want a user to be surprised that a folder is all of a sudden full of stuff"). The isolation stands — the checkout is the unit of isolation — and the location moves: a session's worktree goes under `~/.abcd/worktrees/<root-sha>/<name>/`, keyed the way the history, transcript and voyage stores are keyed, and a store nobody can list is refused as the same pile somewhere less visible, so listing and reclaiming are part of the capability. Decomposed on the maintainer's ruling into three records: adr-2609091014087993 (the trust rule, accepted), itd-2609091014076309 (the store, its list and prune verbs and the status-board line, in drafts pending adoption) and the principle `the-users-directory-is-theirs` (the stance, with its write-side enforcement stated as the intent's future work). Rejected: the in-tree `.abcd/.work.local/worktrees/` placement, because every tree scan walks it; a per-user configured location, because its default would still have to be something. The existing sibling worktrees are a hand cleanup, never a migration: the store moves nothing it did not create.
- dcn-26092600000000072026-09-26 — The build loop's worktree store is keyed on the FULL root sha: a lane lives at `~/.abcd/worktrees/<root-sha>/<run-id>-<lane-id>` with the 40-hex root commit, the form the history, transcript and voyage stores use and the one the store's draft (itd-2609091014076309) specifies. The lanes of autonomous run A made by hand under the abbreviated key (`~/.abcd/worktrees/488a0aa9/<name>/`) are the pre-verb convention, not a second form of the store: the loop never reads or adopts a lane under that key, and those worktrees are retired with `git worktree remove` like any other (implementer of fix round fix2-loop2, autonomous run A, on item 4 of the loop2 review; spc-2609202134338445 piece 6).
- dcn-26092900000000072026-09-29 — The product thinker answered twenty-six owed rulings and the published-history question in one interview, asked one at a time by orchestrator abcd-23 of autonomous run A (recorded by lane recRulings for orchestrator abcd-cb). The answers are kept verbatim in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29.md`, dated 2026-09-29, and are restated here because that file is not committed. Published history is left as it is, with no rewrite. The build loop's fidelity audit runs once, on the lane that closes the spec, over the whole delivery (AI). On the vendor denylist the product thinker answered, verbatim: "I think we need an allow list rather than trying to catch a deny list." The interviewer's reading of that answer, recorded as a reading and not as the product thinker's words, is that the per-provider allowlist is the control and the vendor denylist is not extended to chase spellings, with whether the bundled `anthropic/*` entry stays as a backstop left to the technical facilitator (AA(a)). Only a route the person set up on their own machine may use their paid key, and a repository's own route to it is refused, which replaces the route half of itd-2609081951381895 decision 8 (AA(b)). The live site setup, the API adapter and the decision adapter may each close on tests against a simulated service, with the live paid check recorded as owed to a person (iss-2609291925387011, iss-2609291925393913, iss-2609291925398978). itd-111's last criterion is met by an agent's timing check on macOS plus the Linux CI run, so the intent may be finished. Automated dependency updates are switched on, pushed with a personal or app token the person creates and stores as a repository secret (itd-2609221842494980; creating the token is the person's act, and the lane builds everything else and names the secret it expects). itd-6 adds a Go MCP module, proposed for the person's explicit sign-off before it is fetched; closing itd-6 on a fake server was not ruled in this interview. (A later answer the same day, H1b in the local-tier file `.abcd/.work.local/scratch/reports/rulings-answered-2609-29-b.md`, dated 2026-09-29, rules that it is not: itd-6 is finished only after running once against the real RepoPrompt app, a check owed to a person.) For itd-148: the store under the home folder wins (adr-2609091248200336); the store draft itd-2609091014076309 owns add, list and clean-up and itd-148 uses them; a session that declares itself the coordinator is exempt from the write block in the primary checkout and every other session is blocked; and the store draft is planned first, with nothing from the store draft, the merged-worktree clean-up included, built before its planning interview (Q4). itd-148's own wait is not part of that ruling: it follows from Q2, because itd-148 uses the store's verbs, and is carried by the `blocked_by` below. itd-148 and spc-42 are rewritten to match in this change, and itd-148 is now `blocked_by` the store draft. A release retrospective is seeded from a release, the intents a tag shipped (AD); itd-24 and its spec are rewritten to the release in this change. An unpointed route is refused naming the one setting to add, as built in 35100512e, so AC11 of itd-2609170822093401 is complete (BR1). A missed target becomes `next`, whatever the following release is numbered (BS1). Spec pieces keep "step" and the lane stages are renamed, a breaking change to the JSON fields `step`, `performed` and `refusal.step` that resolves iss-2609291313276243 and corrects the glossary entry `core/step.md` (BU1), and the old command spellings are removed in that same breaking release (BT1). The text board shows Later as a count only while the site and `--json` keep the full list (BV1), and an intent in progress appears only under Now (BV2). A pause of zero is allowed (BW1); drain stays in the agents' help list until it runs end to end (BX1); a managed repository must hold the drain eligibility decision in its own record, and drain refuses there until it does (BX2); a capture without a remedy is refused (BX3), which tightens the one-line capture friction `commands/capture.md` promises, so the lane that builds it reconciles that page, the principle wording and every caller that files a capture; a run's pick reason keeps the shared `pursued:` kind with its marker (BZ1); and an intent whose blocker was superseded waits on the successor, transitively (BZ2). The five itd-48 follow-ups are planned as one intent, drafted as itd-2609291923559186 (BT2), and all warn rules share one shrink-only baseline file in which a new warning fails, drafted as itd-2609291924469783 (BT3); both drafts wait on their planning interviews. Each ruling that contradicts a shipped behaviour or another record is amended in the change that builds it, not here. itd-7's planning interview stays owed.
- dcn-26093000000000222026-09-30 — The worktree store is planned (itd-2609091014076309, spc-2609301811532881), after two adversarial reviews and a planning interview with the product thinker and technical facilitator, asked one at a time. Decomposition: file as is, with no new ADR or principle; the build loop's lane primitive moves into the store's package, so the intent `refines` itd-2609201916151817. Rulings: the verb is `abcd ahoy worktree add|list|prune`; "merged" is the forge's recorded pull-request state or patch-equivalence, and it works with no forge; an unmerged worktree quiet for 14 days (repo-configurable) is surfaced with a dossier and removed only when named (`--yes <name>`), never forced; a reclaimed worktree's local tier is moved, never deleted, into a new sibling store `~/.abcd/notes/<root-sha>/<timestamp>-<name>/`, redacted on write with a manifest; the plugin page carries `add` as a host-run step; the worktree directory is `0o700`; prune exits 0 when it reclaimed something, 1 when it reclaimed nothing, 2 on a refusal; a directory keyed on an abbreviated root commit is listed as "not a lane" and names the full-key lane; the board line keeps "can be cleared" as an estimate. The 21 acceptance criteria are the product thinker's. Grounds: "pursued: working copies keep piling up with nothing to list or clear them; wrong if, after it ships, copies still accumulate outside the store." Carried from itd-148's 2026-08-26 interview and confirmed here: the merge proof and the quiet sweep. Also ruled: itd-148 keeps its mint-visibility criterion as peer awareness (its Decision 5).
- dcn-26100200000000092026-10-02 — Ruling H3-0 of the product thinker (2026-09-30, in the planning interview for the draft itd-2609091014076309 run by the run's interviewer abcd-a2 [75597f]; the answers are kept in the local-tier file `.abcd/.work.local/scratch/reports/planning-answers-2609-30.md` and restated here because that file is not committed; recorded by lane rd2). Put because the draft had no record of the two adversarial reviews the planning-interview prerequisite requires; the answer, verbatim: "REVIEWS FIRST, THEN INTERVIEW (two reviewers one after the other — design/feasibility, then record-discipline — findings applied or rejected in the draft, then the interview on the corrected text)." Applied: both reviews ran and their dispositions were applied before the interview, on branch plan/worktree-store-review, which is not on the default branch at this entry's base (the intent is still in drafts/ here).
- dcn-26102222162644212026-10-22 — Decided by the technical facilitator on delegation (coordinator ruling, 2026-10-22): the sweep command (itd-2610222214476887) refines and is blocked by the worktree store (itd-2609091014076309) and calls the store's prune as its one reclaim primitive, never a second copy, on the one-canonical-primitive principle.
- dcn-26102222273363782026-10-22 — The worktree store's footprint is narrowed to the files its steps change (abcd-ae's ruling as coordinator of 2026-10-22, spc-2609301811532881, itd-2609091014076309): the spec's footprint named whole packages, among them internal/core/implement/loop and internal/surface/cli, which the merge gate's spec also names, so the first to reserve would have held the other off for days; each spec now names the files its steps' packages and tests lines change, read from those lines and never guessed narrow, and a file still in both after narrowing goes to the merge gate first, the store sequencing its step on that file after the merge gate lands; agreed with abcd-43 the same night: internal/core/board/board.go and its test, internal/surface/cli/cli.go and internal/surface/cli/board.go are the merge gate's first, so the store's footprint leaves them out until the merge gate lands and its steps 2 and 6, which need them, are not driven before then, the footprint then widening to take them; and the generated docs/reference/cli/commands.md and .abcd/development/release/surface.json are reserved by neither spec, since they derive from the code and whoever lands second regenerates them; and since other live lanes hold the brief and command-page files only step 7 needs, the footprint names step 1's files first and widens as each step is reached, which the build's reservation supports by reserving again with the widened footprint (grounds: AGENTS.md concurrent sessions, reserve with the footprint the work will change; the reservation's overlap rule)
- dcn-26102719462697592026-10-27 — The worktree store's board line keeps the peers scan's estimate for its reclaimable count, decided by the technical facilitator's session abcd-e8 on the product thinker's standing delegation of 2026-10-27. A change a crashed session left uncommitted, salvaged as commit 6f8012a0f, made the line count by prune's own judgement; it is reverted because itd-2609091014076309's acceptance criterion for the board line says rendering it starts no per-lane status read, and that judgement runs git status, the squash test and a notes read for every worktree in the lane on each render, while the 2026-09-30 planning ruling (dcn-2609300000000022) already accepts the scan's count as an estimate. The salvaged change stays in the branch's history and as a patch in the local tier. The line's words are settled separately: it says can be cleared, as that ruling names them (dcn-2610272035450672).
- iss-213Several agents sharing ONE git worktree silently invalidated a verification result and came close to losing committed work. Observed repeatedly during the 2026-08-11 install-test round, in a repo that is about to run more agents, not fewer.
- iss-2608230847432285Per-agent worktrees do not isolate a session whose shell cwd reverts to the shared checkout, so the mitigation iss-213 recommended has a hole. This refines iss-213, which recorded several agents sharing one git worktree silently invalidating a verification result, and whose recommended direction was to give each agent its own worktree so the whole class disappears. That direction is now in force via the AGENTS.md Concurrent sessions section, and on 2026-08-23 three concurrent sessions demonstrated it does not hold. A session's shell cwd can be silently reset from its worktree back to the primary working directory, and the notice arrives on the tool result AFTER the command that caused it, so the contamination lands on the NEXT command. It fails toward the shared tree, which is the wrong direction: two sessions wrote into the main checkout while believing they were in their own worktree. One appended to two Go test files, the other filed a capture and edited three record files, and both discovered it only when a third session read git status in the main checkout and asked who owned the diffs. Nobody lost work, because the convention that a diff you did not make is a peer's work held and the owners were asked rather than the files committed. The isolation property did not hold; the coordination convention compensated for it. Two details generalise beyond this instance. First, the failure is silent in BOTH directions: nothing warns the writer, and nothing would have warned a committer using git add -A, because the misplaced files are indistinguishable from that session's own in git status. What stood between this and a bad commit was one session committing with explicit paths and another happening to run git status for an unrelated reason. Detection was not mechanical in any of the three cases. Second, this is the write-side twin of iss-213 rather than a restatement of it: iss-213's dangerous member was a verification result that described no tree in particular, on the read side, while this is work landing in a tree whose HEAD another session is preparing to move. Same root cause, that the checkout is the unit of isolation and nothing enforces which checkout a session is in, on opposite sides of the read/write boundary. The mechanical mitigation is to address the tree explicitly on every git invocation, git -C <abs-path>, and to use absolute paths for file writes, rather than relying on a persisted cd: a cd is a session-global mutation with no scope and no expiry, which is the wrong shape for the mechanism that is supposed to provide isolation. AGENTS.md states that the checkout is the unit of isolation without saying what makes a session stay in its checkout, and that gap is what let three sessions make the same mistake in one day. Decide the routing: an AGENTS.md line under Concurrent sessions is the cheapest rung and matches how the sequential-id caveat was handled, while the durable form is whatever makes a session's tree unambiguous rather than remembered.
- iss-2609020721142452Worktrees for parallel lanes are created one directory above the repository (../abcd-wt-<lane>, ../abcd-f185, ../abcd-integration), which litters the parent directory with a dozen sibling checkouts named by convention only, leaves them behind when a session dies (21 spent worktrees, 1.4G, were removed by hand on 2026-09-01), and gives nothing a stable place to look for them. Assessment of where they should go: (1) inside the repository under .abcd/.work.local/worktrees/ keeps them with their owner and gitignored, but a worktree inside the main working tree is walked by every tree scan (the payload gate, name-guard, docs-lint), which already flakes during worktree creation (iss-2608261331317889) and would slow every gate; (2) under the user-level home, ~/.abcd/worktrees/<root-sha>/<branch-slug>/, keyed the way the history store already keys a repository by its root commit, keeps them out of every scan, survives a session, and gives an abcd verb a place to list, prune and reattach them; (3) the git default (anywhere) is the status quo. Option 2 fits the existing home layout and the one-canonical-primitive rule (the root-sha keying exists), and it makes the concurrent-sessions convention in AGENTS.md checkable: a session lists its peers' worktrees by reading one directory. It needs: a verb or documented one-liner (abcd worktree add <lane> / list / prune), the AGENTS.md convention updated, the lane brief updated, and a prune rule for worktrees whose branch is merged.
- iss-2609091129426411adr-2609091014087993 says two things about `AGENTS.md` that cannot both be read at face value. Among its normative clauses, in the present indicative: "`AGENTS.md`'s concurrent-sessions convention names the store as the place a session's worktree goes, and names no sibling-directory form." Among its Consequences: "The concurrent-sessions convention gains the store as the place a worktree goes, and loses any wording that reads as a sibling directory. That edit lands with the intent, because a convention naming a verb that does not exist is a phantom gate." The first states as fact what the second schedules, and when the ADR was accepted neither was true of the file: § Concurrent sessions was byte-unchanged, named no store, and treated creating a worktree inside the checkout as ordinary — the shape the ADR rejects as its alternative 2.
- iss-2609240646458365The machine-scoped stores are documented as keyed on the repository's root commit (`~/.abcd/<store>/<root-sha>/`), and no page says the key is the full forty-character sha. The verbs that create a store (the run store, the history store, the transcript store) key on the full sha. The worktree store has no verb yet and is laid by hand, and its lanes sit under the eight-character short form; AGENTS.md says it is keyed "the way the history, transcript and voyage stores already are", which only a reader who checks those stores reads as the full sha. Autonomous run A's run file named the run store by the short form too, so the orchestrator appended its first events by hand to `~/.abcd/runs/<short-sha>/` while `abcd implement` wrote to the full-sha directory, and the two logs were joined by moving the short directory and leaving a symlink in its place. Wanted: AGENTS.md, the brief's configuration chapter and the worktree-store draft itd-2609091014076309 say the key is the full sha, and whatever lists the stores names a short-form directory beside a full one.
- iss-2610090744073409The CONCURRENCY rule in .abcd/rules.json (and its AGENTS.md short form, if it repeats it) says the worktree store's own verbs are itd-2609091014076309, in drafts/, but that intent is in planned/; its link already points at planned/, so the prose contradicts the link beside it.
- iss-2610111926302443TestWorktreeStoreRuleNamesTheIntentsActualState (internal/core/lint/conventionsfile_test.go) checks for stale draft wording per blank-line paragraph, and the references list in .abcd/development/brief/04-surfaces/34-build.md is one paragraph naming six intents, so a later '(a draft)' beside a different intent in that list would fail the test naming itd-2609091014076309 falsely.
- iss-2610111926302792Present-state planning prose still calls itd-2609091014076309 'the store draft' although the intent is in planned/: itd-148 (lines 64 and 188), spc-42 (lines 27 and 112) and iss-213 (line 11). The drift fix for iss-2610090744073409 corrected the CONCURRENCY rule, the build brief page and lane.go, and left these planning records as out of its remedy's scope; its ruthless review recorded them.
- iss-2610112237488013Three planning records still treat the worktree-store intent itd-2609091014076309's planning interview as owed, though it was held on 2026-09-30 (its record lists the rulings, and its Open Questions read 'None open'): spc-42's '## Blocked on' lists that interview as the blocker; iss-213's deferral_reason says 'Owed: the store draft's planning interview'; itd-148's Acceptance Criteria note and Open Questions defer to after it; and spc-42's Scope item 2 calls the check verb's name one of the store intent's open questions, though that intent's Decision 5 settled it as a sub-verb of ahoy. Found by the lane for iss-2610111926302792.
- iss-2610222022157889abcd reserve refuses in a fresh worktree: AGENTS.md sends a concurrent session to a plain git worktree add under the machine-scoped store and then to abcd reserve before any work, but a worktree just added has no .abcd/.work.local/ (the tier is gitignored and per-worktree), so tierPresent in internal/core/claim/copykey.go refuses with 'is not a directory in this checkout, so there is nowhere to keep this copy's key; run abcd ahoy to set it up' and nothing is pushed. Every new worktree hits it on its first reservation; on 2026-10-22 the go-1-26-10 worktree's reserve was refused this way, and a mkdir of the tier (made as a side effect of a preflight log directory) cleared it, with no ahoy run.
- iss-2610231004012419abcd reserve cannot widen a live reservation from another copy of the same person on the same machine. A second worktree that reserves the same id with more footprint paths is refused as if the hold sat on another computer (internal/core/claim/remote.go:403, 'you are already building it on another computer; pass --take-over'), and a take-over moves the hold, which can disturb the run that owns it. Two sessions hit this on 2026-10-23 in the hand-rolled coordination log: abcd-54 at 10:04 could not add the worktree store's step 3 and 4 files to its live itd-2609091014076309 hold from a second copy, and borrowed an unrelated open record's reservation to announce them; abcd-f1 at 10:26 and 10:41 had its own hold read from a sibling worktree as another computer, and added the paths from the reserving worktree instead. Related: iss-2610161457209287 (holds do not say which of a person's copies has them).
- itd-2610030720038073abcd keeps its home folder out of desktop indexing by default
- spc-42every-change-starts-in-its-own-worktree-the-primary-checkout
- spc-2609202134338445one-verb-takes-a-single-intent-from-ready-to-delivered-witho
- spc-2610031309233367abcd-keeps-its-home-folder-out-of-desktop-search-by-default
- spc-2610031753359849Reservations on the shared remote: `abcd reserve`, one liveness judgement, and the build, drain and land integration
- spc-2610161751056096a-person-who-leaves-abcd-to-run-on-its
- spc-2610230730269295`abcd sweep`: one dry run over every leftover, and `--apply` for what is provably spent
- the-users-directory-is-theirsThe user's directory is theirs