pi-herdr-agents 1.7.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -48,7 +48,7 @@ Preserve these invariants when changing worktree behavior:
48
48
  6. Herdr creates the workspace without stealing focus; launch targets the returned root pane explicitly.
49
49
  7. Successful, failed, and help-requesting runs retain their worktree workspace.
50
50
  8. Completion reports reviewable Git state; inspection failures are unknown, never guessed clean or conflict-free.
51
- 9. The extension does not push, create PRs, merge, cherry-pick, switch the parent checkout, or remove worktrees/branches automatically.
51
+ 9. The extension does not push, create PRs, merge, cherry-pick, switch the parent checkout, or remove worktrees automatically. Explicit parent-owned cleanup uses cwd containment and fail-closed eligibility; branches are never deleted.
52
52
  10. Ordinary non-worktree subagent behavior remains unchanged.
53
53
 
54
54
  Read [`docs/worktree-subagents.md`](docs/worktree-subagents.md) before changing any of these semantics.
@@ -105,8 +105,8 @@ Use `PI_TEST_MODEL="openai-codex/gpt-5.6-luna" PI_TEST_TIMEOUT=180000 npm run te
105
105
  Before committing:
106
106
 
107
107
  - inspect `git status` and the final diff;
108
- - confirm the package preview includes `CHANGELOG.md`, `skills/orchestrate/SKILL.md`, `skills/orchestrate/adversarial-review.md`, and `skills/orchestrate/adversarial-review-example.js`, while excluding `pi-extension/subagents/workflow-worker.js`, plans, journals, sessions, prototypes, generated evidence, and local config;
109
- - run `npm pack --dry-run` when package contents or documentation paths changed;
108
+ - confirm the package preview includes `CHANGELOG.md`, `skills/orchestrate/SKILL.md`, `skills/orchestrate/adversarial-review.md`, and `skills/orchestrate/adversarial-review-example.js`, while excluding `pi-extension/subagents/workflow-worker.js`, plans, journals, sessions, prototypes, generated evidence, local config, and `openspec/`;
109
+ - run `npm pack --dry-run` when package contents or documentation paths changed; durable configuration is `$PI_CODING_AGENT_DIR/herdr-agents/config.json`, never package-root `config.json` (move old files manually or re-run `/subagents-init`);
110
110
  - confirm that no generated plans, journals, sessions, provider configuration, test scripts, or review artifacts are staged; and
111
111
  - confirm that no accidental empty directory exists at the repository root:
112
112
 
package/CHANGELOG.md CHANGED
@@ -7,7 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
9
9
 
10
- ## [v1.7.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.6.0...v1.7.0)
10
+ ## [v2.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v2.0.0...v2.0.1)
11
+
12
+ ### Merged
13
+
14
+ - fix: initialize task routing from the live model registry [`#51`](https://github.com/giuseppecrj/pi-herdr-agents/pull/51)
15
+
16
+ ## [v2.0.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.7.0...v2.0.0) - 2026-09-18
17
+
18
+ ### Merged
19
+
20
+ - feat(subagents): task-category model routing, durable global config, and /subagents-init [`#49`](https://github.com/giuseppecrj/pi-herdr-agents/pull/49)
21
+ - feat(subagents): managed worktree inventory and explicit cleanup [`#50`](https://github.com/giuseppecrj/pi-herdr-agents/pull/50)
22
+
23
+ ### Commits
24
+
25
+ - feat(subagents): explicit worktree cleanup (astra build) [`76736b2`](https://github.com/giuseppecrj/pi-herdr-agents/commit/76736b23f661331d028c56f95541c5b859b0f6e2)
26
+ - fix(subagents): verify cleanup safety and disclose process visibility [`5ec8631`](https://github.com/giuseppecrj/pi-herdr-agents/commit/5ec86310bb4a33c737557cb1664fda669a971ff8)
27
+ - add openspec [`7a73e0c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/7a73e0c9cd3fadfaae941fabd6c71e07226f8214)
28
+ - fix(subagents): harden worktree cleanup after adversarial review [`d3c7e7f`](https://github.com/giuseppecrj/pi-herdr-agents/commit/d3c7e7f732b88b895077e8a8c2a946d5f616562c)
29
+ - feat(subagents): add task model routing [`07fce0d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/07fce0d10febce30d21db862c2ae91f435f627de)
30
+ - fix(subagents): repair task model routing review findings [`912ac78`](https://github.com/giuseppecrj/pi-herdr-agents/commit/912ac78fadbb5ab2011f6b9ec680ffb51ad7f9d5)
31
+ - docs(openspec): propose task-model-routing change [`6b97fc7`](https://github.com/giuseppecrj/pi-herdr-agents/commit/6b97fc73bd9cf68dce2275db4882622d1fcee6e2)
32
+ - docs(openspec): address adversarial review of task-model-routing [`7942440`](https://github.com/giuseppecrj/pi-herdr-agents/commit/7942440c0fe21b8900f56aba2563f57330037e82)
33
+ - chore: release v2.0.0 [`211a43b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/211a43b01c304b59e867ee7003b6f68573cebeda)
34
+ - test(integration): harden cleanup test against wrap and holder races [`635c79b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/635c79b5fe4f5cda35ad46be9575144ed7f9bf7a)
35
+ - docs(openspec): check off 5.2 after live /subagents-init verification [`879438d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/879438d50b1944631c42cec6009bab9ba8226a6c)
36
+
37
+ ## [v1.7.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.6.0...v1.7.0) - 2026-09-09
11
38
 
12
39
  ### Merged
13
40
 
@@ -23,6 +50,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
23
50
  - fix(subagents): keep no-progress recovery guidance evidence-based (#40) [`9cbf155`](https://github.com/giuseppecrj/pi-herdr-agents/commit/9cbf155e291c063f70ef86800ef3bfe3c742c6c6)
24
51
  - test(integration): hang advisory coverage (#40) [`075f4fe`](https://github.com/giuseppecrj/pi-herdr-agents/commit/075f4feb17938df28a92fa00851d606e5a83b258)
25
52
  - fix(subagents): preserve boundary-aligned records in bounded session tails (#40) [`f096fdb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f096fdb681921e9ab460610d53e456758802725f)
53
+ - chore: release v1.7.0 [`371265e`](https://github.com/giuseppecrj/pi-herdr-agents/commit/371265e74fb7485afbfbc7c028d60dc4f98d2779)
26
54
 
27
55
  ## [v1.6.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v1.5.1...v1.6.0) - 2026-09-08
28
56
 
package/CONTEXT.md CHANGED
@@ -99,3 +99,64 @@ _Avoid_: Completed agent process, disposable pane, automatic worktree cleanup
99
99
  The lifetime-exclusive binding between a persistent specialist generation and
100
100
  one managed worktree, when that specialist writes in a worktree.
101
101
  _Avoid_: Rebindable checkout, shared worktree ownership
102
+
103
+ **Worktree inventory**:
104
+ An inspect-only view joining managed checkout discovery, Git registration/state,
105
+ Herdr workspace association, and reachable owned manifests, including orphans.
106
+ _Avoid_: Session-only resource list, cleanup action
107
+
108
+ **Cwd containment**:
109
+ Cleanup authorization requiring the canonical source repository root to equal or
110
+ be a descendant of the invoking session's canonical cwd.
111
+ _Avoid_: Managed-path containment, manifest ownership authorization
112
+
113
+ **Cleanup eligibility**:
114
+ Fresh evidence of cwd containment, registered checkout identity, a named branch,
115
+ no detected process holder, known live child, or persistent lease, and clean Git
116
+ state. Only Herdr-confirmed idle retained shells are exempt from process checks,
117
+ never runtimes at the same PID. Ignored files and individual process-visibility
118
+ gaps are disclosed, not blockers. Other unknown evidence blocks removal.
119
+ _Avoid_: Guessed idle, presumed clean
120
+
121
+ **Process-inspection warning**:
122
+ Non-blocking disclosure of incomplete same-user process visibility, separate
123
+ from cleanup blockers and requiring no override flag. Scanning continues after
124
+ unreadable details; detected holders still block. Other-user processes are not
125
+ inspected, and a protected process could hold the checkout undetected. Failed
126
+ global enumeration and unsupported platforms remain blockers.
127
+ _Avoid_: Proven unrelated, machine-wide inactivity, bypass permission
128
+
129
+ **Explicit worktree removal**:
130
+ A parent-requested removal of one named managed checkout and its open workspace,
131
+ with absence verification and retained branch history. Never automatic reaping.
132
+ _Avoid_: Branch deletion, completion cleanup
133
+
134
+ **Dirty-state preservation**:
135
+ Explicit opt-in staging and WIP commitment of a worktree's uncommitted and
136
+ untracked files on its retained branch before rechecking removal eligibility.
137
+ Ignored files are not captured. Commit failure restores the original index.
138
+ _Avoid_: Implicit commit, stash, discard
139
+
140
+ **Task-category model preference**:
141
+ An ordered authenticated model shortlist in `models.tasks` for `coding`,
142
+ `review`, `recon`, `qa`, `architecture`, or `docs`. Recon maps to scouts,
143
+ architecture to planning and diagnosis, coding to workers, review to reviewers,
144
+ QA to software and test runners, and docs to documentation workers. Categories
145
+ describe work, not complexity. `/subagents-init [preferences]` drafts them from
146
+ the active extension-loaded registry's synchronous snapshot and existing saved
147
+ choices, with source-based research when available. A dynamic provider awaiting
148
+ its initial catalog refresh might be absent. `task:<category>` is a subagent
149
+ model selector, not a command or parent model change. Ordered authenticated
150
+ candidate plans resolve before launch; ordinary nonpersistent runs can retry
151
+ after launch failure or a running child's provider/agent error, not a completed
152
+ negative task result. Persistent specialists do not advance after a running-child
153
+ error. Worktrees select the first authenticated candidate only, without fallback
154
+ retries. Reviews use exact IDs from a different author family when the author
155
+ is known.
156
+ _Avoid_: Generic tier, reviewer-family enforcement, per-step routing
157
+
158
+ **Loop template**:
159
+ A future reusable orchestration definition beside `models`, describing stages,
160
+ task categories, and a termination/report contract. Loop templates are not
161
+ implemented by task-model routing.
162
+ _Avoid_: Current executable workflow
package/README.md CHANGED
@@ -119,7 +119,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
119
119
 
120
120
  ### Extensions
121
121
 
122
- **Subagents** — 6 main-session tools + 6 commands, plus 2 child-only tools:
122
+ **Subagents** — 9 parent-session tools + 7 commands, plus 2 child-only tools:
123
123
 
124
124
  | Tool | Description |
125
125
  | -------------------- | ------------------------------------------------------------------------------------------- |
@@ -128,7 +128,10 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
128
128
  | `subagent_send` | Deliver a follow-up task to an idle persistent specialist |
129
129
  | `subagent_stop` | Gracefully stop a persistent specialist after its active task settles |
130
130
  | `subagents_list` | List available agent definitions |
131
+ | `worktree_list` | Parent-only inspect-only inventory of managed worktrees and cleanup blockers |
132
+ | `worktree_remove` | Parent-only explicit removal by `target` path, branch, or workspace ID; optional `preserve: true` commits dirty state first |
131
133
  | `subagent_resume` | Resume a previous Pi-backed sub-agent session in a new ordinary pane (async) |
134
+ | `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences |
132
135
 
133
136
  | Pi child-only tool | Description |
134
137
  | ---------------- | ------------------------------------------------------------------------- |
@@ -143,6 +146,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
143
146
  | `/btw-close` | Close the current BTW session |
144
147
  | `/worktree <name> [task]` | Continue this session in a new managed worktree (`/worktree list` lists them) |
145
148
  | `/subagent <agent> <task>` | Spawn a named agent directly (`/subagent list` lists available agents) |
149
+ | `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences |
146
150
 
147
151
  ### Taxonomy and discovery
148
152
 
@@ -271,20 +275,13 @@ A fixed internal watchdog marks a run as `stalled` when pane inspection fails or
271
275
 
272
276
  #### Configuration
273
277
 
274
- The extension reads `config.json` from the installed package root—the directory
275
- containing this README and `package.json`, not `pi-extension/subagents/` or
276
- Herdr's `config.toml`. That file is package-local: npm or git package updates may
277
- overwrite it. Common global package roots are:
278
-
279
- - npm: `~/.pi/agent/npm/node_modules/pi-herdr-agents/`
280
- - git: `~/.pi/agent/git/<host>/<owner>/pi-herdr-agents/`
281
-
282
- Project-local installs use the corresponding `.pi/npm/` or `.pi/git/` root.
283
- From the actual package root, copy the example when you want local overrides:
284
-
285
- ```bash
286
- cp config.json.example config.json
287
- ```
278
+ The durable user configuration is `$PI_CODING_AGENT_DIR/herdr-agents/config.json`,
279
+ defaulting to `~/.pi/agent/herdr-agents/config.json`. It is not read from the
280
+ installed package root, so npm and git package upgrades do not overwrite it.
281
+ Create it by copying the installed package's `config.json.example`, or run
282
+ `/subagents-init` to seed and draft model task preferences. This is a breaking
283
+ migration: manually move an existing package-local `config.json` to this path,
284
+ or re-run `/subagents-init`.
288
285
 
289
286
  ```json
290
287
  {
@@ -327,11 +324,93 @@ exact IDs from your authenticated model catalog:
327
324
  "agents": {
328
325
  "scout": "your-provider/your-fast-model",
329
326
  "reviewer": "your-provider/your-review-model"
327
+ },
328
+ "tasks": {
329
+ "coding": ["your-provider/your-coding-model"],
330
+ "review": ["your-provider/your-review-model"],
331
+ "recon": ["your-provider/your-fast-model"],
332
+ "qa": ["your-provider/your-qa-model"],
333
+ "architecture": ["your-provider/your-architecture-model"],
334
+ "docs": ["your-provider/your-docs-model"]
335
+ },
336
+ "tasksMeta": {
337
+ "generatedAt": "2026-09-17T00:00:00Z",
338
+ "method": "research"
330
339
  }
331
340
  }
332
341
  }
333
342
  ```
334
343
 
344
+ `models.tasks` candidates are ordered exact authenticated IDs. Use
345
+ `task:<category>` only in the `subagent` tool's `model` argument; it is not
346
+ valid in frontmatter or model defaults. For review when the authoring family is
347
+ known, choose an exact shortlist ID from a different family rather than
348
+ `task:review`; this is guidance, not extension enforcement.
349
+
350
+ Run `/subagents-init [preferences]` to draft task-model preferences. For example:
351
+
352
+ ```text
353
+ /subagents-init Prefer capability over price for implementation; keep recon inexpensive
354
+ ```
355
+
356
+ The command supplies a sanitized snapshot of **all available models from the
357
+ active session registry**, including extension-registered providers, exact IDs,
358
+ display names, reported base token costs, context/output limits, input
359
+ modalities, reasoning, and supported thinking levels. Safe extension-registration and auth-source
360
+ metadata is included when Pi exposes it; credentials, endpoints, and raw auth
361
+ labels are not. Configured authentication does not prove account access or a
362
+ successful request. Missing costs remain unknown; reported zero does not mean
363
+ free, and OAuth does not establish subscription billing. The brief uses compact
364
+ JSON without truncating models and reports its model count and JSON character
365
+ count (not a token estimate); large catalogs still consume context. This is the
366
+ current synchronous snapshot: a dynamic provider whose initial catalog refresh
367
+ has not completed might be absent. Init does not refresh providers or probe the
368
+ network for availability.
369
+
370
+ The draft considers current saved task, default, and per-agent preferences.
371
+ Optional command arguments set ranking preferences. Otherwise it favors
372
+ capability for substantive work and efficiency for bounded reconnaissance and
373
+ test execution. Categories describe work, not complexity tiers:
374
+
375
+ | Category | Work |
376
+ | --- | --- |
377
+ | `coding` | Implementation workers |
378
+ | `review` | Code reviewers |
379
+ | `recon` | Reconnaissance scouts |
380
+ | `qa` | Software and test runners |
381
+ | `architecture` | Planning and diagnosis |
382
+ | `docs` | Documentation workers |
383
+
384
+ Init asks the agent to research major candidates across providers using primary
385
+ sources, disclose uncertainty and notable exclusions, and avoid duplicate
386
+ upstream models across routes unless deliberate redundancy is explained. Display
387
+ names help identify candidates but, like aliases, do not prove upstream
388
+ equivalence; research is still required. Price or context size alone is not
389
+ quality evidence. It reports `registry-only` when
390
+ search is unavailable or yields no usable evidence; no live model probes run.
391
+
392
+ The writer validates and atomically replaces `models.tasks` and `tasksMeta`,
393
+ preserving unrelated settings. Its tool schema accepts partial nonempty
394
+ categories (omitted categories are removed), rejects empty `tasks: {}` input,
395
+ and rejects exact duplicate refs within a category after trimming;
396
+ IDs remain case-sensitive. Its result includes normalized saved `tasks`,
397
+ `tasksMeta`, `configPath`, and `missingCategories`. Init requests all six categories
398
+ and a before/after table based on that saved result, not the unsaved draft. It
399
+ must explain missing categories or changed choices; with no available models,
400
+ it must report the limitation without writing.
401
+
402
+ `task:<category>` values select subagent models; they are not slash commands and
403
+ do not change the parent model. Ordered authenticated candidate plans resolve
404
+ before launch. Ordinary nonpersistent runs can retry later candidates after
405
+ launch failure or after a running child settles with a provider/agent error,
406
+ not after a completed negative task result. Persistent specialists do not
407
+ advance after a running-child error. This is not per-step routing; worktrees
408
+ use the first authenticated candidate only, without fallback retries.
409
+ Shortlists do not enforce reviewer independence: select an exact reviewer from
410
+ a different author family when the author is known, and a different provider
411
+ when project policy requires it. Another route to the same family is not
412
+ independent review. Run `/reload` (or start a new session) after writing preferences.
413
+
335
414
  Set `persistent.maxAgents` to the maximum concurrently retained persistent specialists. It defaults to `3`; a persistent spawn at the cap is rejected before Herdr creates a pane or workspace, and no specialist is evicted.
336
415
 
337
416
  Set `roles.bundled` to `false` to exclude this package's bundled role definitions from listing and exact-name launch. It defaults to `true`. Registered role packs remain available, and global and project definitions keep their existing precedence. A role-pack name collides with a bundled role only while that bundled layer is enabled; when it is disabled, the role pack can supply that name.
@@ -344,7 +423,7 @@ collection; it never establishes a result by itself. If the watcher or shared
344
423
  pane inspection becomes unavailable, supervision quietly returns to the legacy
345
424
  one-second polling cadence. No caller action is required.
346
425
 
347
- Set `supervision.forcePolling` to `true` in the package-local `config.json` to
426
+ Set `supervision.forcePolling` to `true` in the durable user `config.json` to
348
427
  disable wake-ups and use that legacy cadence deliberately. The setting is read
349
428
  when the coordinator is created, so run `/reload` after changing it.
350
429
  `subagents_list` reports the active transport mode (`wake+batch`,
@@ -405,7 +484,9 @@ Run `/reload` after changing role, model, or pane settings.
405
484
  followed by agent frontmatter, per-agent config, the global default, and finally
406
485
  the parent model. Model values must be exact authenticated `provider/model-id`
407
486
  references. A value can contain an ordered comma-separated fallback list, for
408
- example `provider/preferred, provider/fallback`. The extension validates every
487
+ example `provider/preferred, provider/fallback`. The tool argument also accepts
488
+ `task:<category>` as its complete value (not in a list), for configured
489
+ `coding`, `review`, `recon`, `qa`, `architecture`, or `docs` preferences. The extension validates every
409
490
  candidate before launch, then launches later candidates only after the selected
410
491
  child settles with a provider/agent error. Pi owns any automatic transient
411
492
  retrying inside that child; the extension does not infer retry counts or
@@ -425,10 +506,9 @@ claim a permanent failure or a retry count that Pi has not exposed. Reliable
425
506
  structured permanence and retry counts require an upstream Pi/ExtensionAPI
426
507
  diagnostics seam for final provider errors and retry outcomes.
427
508
 
428
- `config.json` is gitignored in the source tree so local overrides are not
429
- committed from a checkout. On an installed package root, treat it as disposable
430
- local state that package updates may replace. Run `/reload` after changing it;
431
- status, model, role, and pane configuration are loaded when the extension starts.
509
+ `config.json` is durable user state under the Pi agent directory and is loaded
510
+ when the extension starts. Run `/reload` after changing it. Package-root
511
+ `config.json` files are ignored; move them manually or re-run `/subagents-init`.
432
512
 
433
513
  ---
434
514
 
@@ -468,7 +548,7 @@ subagent({
468
548
  | `fork` | boolean | `false` | Force the full-context fork mode for this spawn, overriding any agent `session-mode` frontmatter |
469
549
  | `persistent` | boolean | `false` | Keep one specialist session alive for sequential tasks; follow-ups use `subagent_send` only |
470
550
  | `interactive` | boolean | derived | Mark this spawn as interactive (don't wake the parent on stall/recovery). Defaults to the agent's `interactive` frontmatter, otherwise the inverse of `auto-exit`. |
471
- | `model` | string | configured or parent | Exact authenticated `provider/model-id`, or an ordered comma-separated Pi fallback list; fallback lists are unavailable for worktree spawns. Resolution is tool argument → agent frontmatter → per-agent config → global config → parent |
551
+ | `model` | string | configured or parent | Exact authenticated `provider/model-id`, ordered fallback list, or whole-value `task:<category>` (coding, review, recon, qa, architecture, docs). Task routing is tool-only; worktrees use its first authenticated candidate. Resolution is tool argument → agent frontmatter → per-agent config → global config → parent |
472
552
  | `thinking` | string | parent level | Pick the model tier first, then set thinking within that model's range: minimal/low for bounded mechanical work, medium for ordinary implementation or review, high+ for architecture, security, or hard diagnosis. Omitting still inherits the parent level; this is a discouraged fallback for orchestrated children. |
473
553
  | `systemPrompt` | string | — | Role/system-prompt text for a bare spawn; named agents keep their definition body |
474
554
  | `skills` | string | — | Comma-separated skill names |
@@ -636,10 +716,18 @@ BTW shares the current working directory. It treats inherited work as reference
636
716
 
637
717
  ## The `/worktree` Workflow
638
718
 
639
- `/worktree <worktree> [task]` creates a Herdr-managed worktree from the current committed branch and launches a new interactive Pi session there with the active conversation branch. The original session remains available. Use `/worktree list` to list worktrees for the current repository. This is a new-process handoff, not an in-place move of the existing shell or Pi process.
719
+ `/worktree <worktree> [task]` creates a Herdr-managed worktree from the current committed branch and launches a new interactive Pi session there with the active conversation branch. The original session remains available. Use `/worktree list` or `worktree_list({})` to inspect managed worktrees, including cross-session orphans, whose canonical source repositories are inside the session's cwd subtree. This is a new-process handoff, not an in-place move of the existing shell or Pi process.
640
720
 
641
721
  ---
642
722
 
723
+ ### Explicit worktree cleanup
724
+
725
+ Parent sessions can call `worktree_remove({ target: "<path|branch|workspace-id>", preserve: true })` or `/worktree remove <target> [--preserve]`. Preservation is optional and never implied: dirty work is blocked unless explicitly committed first or preserved as a WIP commit. The result reports its SHA even if removal later fails or is refused. A failed preservation commit restores the pre-preservation index and never proceeds to removal. Inventory and removal reports disclose exact ignored-file counts: enumeration is streamed rather than buffered as one listing. Ignored files do not block cleanup and are not captured by preservation; failed counting still blocks removal.
726
+
727
+ Eligibility is rechecked at removal time: canonical source-repository cwd containment, registered linked checkout, no detected process holder, known live child, or persistent lease, and clean Git state with no untracked files or conflicts. Unknown inspection, identity disagreements, detached HEAD, locked checkouts, and initialized submodules block removal. Out-of-scope repositories are never eligible. Open workspaces use Herdr removal; orphans use Git removal and registration pruning after checkout absence is verified. Owned reachable manifests are marked `removed`; manifests from other sessions are not required or rewritten. Stale removed manifests never govern a recreated checkout. Missing or dangling manifest paths do not affect unrelated checkouts; undecidable or conflicting manifest identity still blocks removal. A failed manifest update after removal is reported as a warning. Symlinked ancestors are supported; checkout symlinks escaping the canonical managed root are blocked. Process inspection covers observable same-user processes across sessions, regardless of runtime name, exempting only Herdr-confirmed idle retained shells, never runtimes at the same PID. Unreadable individual process details produce non-blocking warnings without an override flag; scanning continues so another observable holder still blocks removal. Human and structured inventory/removal results disclose incomplete coverage, including warnings seen before a later recheck or failure. Same-user inspection is permission-limited, and other-user processes are not inspected: a protected process could hold the checkout undetected. Warnings aggregate counts and bounded PID samples, not commands or environments. Linux uses `/proc`; macOS uses same-user `lsof` cwd records and warns for unreadable individual records. Unsupported platforms, failed global enumeration (including a failed `lsof` with partial output), and other unverifiable eligibility evidence still block removal. Cleanup Git and Herdr calls have 30-second timeouts.
728
+
729
+ Cleanup never deletes or rewrites branches, uses force flags, or runs automatically. Session start only reports retained-worktree counts. Child sessions retain `/worktree list` and `/worktree <name>`, but receive neither cleanup tools nor the remove subcommand. Their repository-local listing labels detached entries `(detached HEAD)`; a detached sibling does not prevent inspection of named branches. See [cleanup and recovery](docs/worktree-subagents.md#cleanup) for details.
730
+
643
731
  ## Custom Agents
644
732
 
645
733
  Custom agent roles are the package's primary extension mechanism. Create one
package/RELEASING.md CHANGED
@@ -41,7 +41,7 @@ The optional live-provider smoke test is not a release gate:
41
41
  PI_TEST_MODEL="openai-codex/gpt-5.6-luna" PI_TEST_TIMEOUT=180000 npm run test:integration:live
42
42
  ```
43
43
 
44
- Do not release from skipped Herdr tests. Confirm the package preview includes `README.md`, `CHANGELOG.md`, `AGENTS.md`, `docs/`, `agents/`, `skills/orchestrate/SKILL.md`, `skills/orchestrate/adversarial-review.md`, `skills/orchestrate/adversarial-review-example.js`, and excludes `pi-extension/subagents/workflow-worker.js`. Confirm it excludes plans, journals, sessions, prototypes, generated evidence, and local `config.json`, and that the worktree integration tests leave no test workspace behind.
44
+ Do not release from skipped Herdr tests. Confirm the package preview includes `README.md`, `CHANGELOG.md`, `AGENTS.md`, `docs/`, `agents/`, `skills/orchestrate/SKILL.md`, `skills/orchestrate/adversarial-review.md`, `skills/orchestrate/adversarial-review-example.js`, and excludes `pi-extension/subagents/workflow-worker.js`. Confirm it excludes plans, journals, sessions, prototypes, generated evidence, local `config.json` and `openspec/`, and that the worktree integration tests leave no test workspace behind. Durable user configuration is `$PI_CODING_AGENT_DIR/herdr-agents/config.json`; package-root configuration is ignored, so users must move an older file manually or re-run `/subagents-init`.
45
45
 
46
46
  ## npm authentication
47
47
 
@@ -40,7 +40,9 @@ identify missing evidence.
40
40
  `fork: false` does not override role `session-mode: fork`. Stop for a
41
41
  non-standalone or unknown mode so no reviewer inherits coordinator context.
42
42
  4. Use the model-catalog source identified by the `subagent` tool guidance or
43
- another project-approved source. Record that source, how authentication was
43
+ another project-approved source. Curated task shortlists can inform selection,
44
+ but review must name an exact ID and exclude known author families; never use
45
+ `task:review` when that exclusion is required. Record that source, how authentication was
44
46
  confirmed, eligible distinct exact IDs, and only IDs actually considered but
45
47
  omitted, with reasons. Never guess unknown catalog entries. Identify every
46
48
  provider/model family that authored the reviewed material, or confirm that it
package/docs/README.md CHANGED
@@ -41,6 +41,7 @@ uses an isolated Herdr server and writes uncommitted raw samples to
41
41
  | [`0008`](adr/0008-adopt-pi-only-subagent-execution.md) | Accepted; implemented | Remove the external CLI adapter and make subagent execution Pi-only. |
42
42
  | [`0009`](adr/0009-remove-workflow-subsystem.md) | Accepted | Remove the workflow subsystem; use public subagent fan-out and parent synthesis. |
43
43
  | [`0010`](adr/0010-persistent-specialists-as-session-generations.md) | Accepted | Define persistent specialists as logical identities with policy-bound session generations. |
44
+ | [`0011`](adr/0011-explicit-worktree-cleanup.md) | Accepted | Authorize explicit worktree cleanup by cwd containment; retain branches and reject automatic reaping. |
44
45
 
45
46
  ## Historical material
46
47
 
@@ -143,7 +143,7 @@ Apply these rules:
143
143
 
144
144
  1. Prefer per-invocation `model` and `thinking` for a workflow's deliberate
145
145
  diversity or cost/quality policy.
146
- 2. Use ignored local `config.json` for a person's durable role preferences.
146
+ 2. Use `$PI_CODING_AGENT_DIR/herdr-agents/config.json` for a person's durable role preferences; package-root configuration is ignored, so move an older file manually or re-run `/subagents-init`.
147
147
  3. Leave bundled role `model` unset unless a particular model is a functional
148
148
  prerequisite.
149
149
  4. State runtime prerequisites before launch and fail closed when a required
@@ -81,7 +81,7 @@ Within the package layer:
81
81
 
82
82
  - bundled roles are protected fallbacks while enabled;
83
83
  - a role pack colliding with an enabled bundled name is rejected;
84
- - copying `config.json.example` to package-local `config.json` and setting `roles.bundled` to `false` removes only the bundled layer; registered role packs remain package roles and may supply those names;
84
+ - copying the packaged `config.json.example` to `$PI_CODING_AGENT_DIR/herdr-agents/config.json` and setting `roles.bundled` to `false` removes only the bundled layer; registered role packs remain package roles and may supply those names; package-root configuration is ignored, so manually move older files or re-run `/subagents-init`;
85
85
  - a name contributed by multiple role packs is disabled;
86
86
  - collisions never resolve through incidental extension load order.
87
87
 
@@ -95,7 +95,7 @@ before Herdr creates a pane or worktree.
95
95
  ## Reload and security
96
96
 
97
97
  Role files are read on each list or launch, so editing Markdown does not require
98
- `/reload`. Changing package-local role configuration, installing, removing,
98
+ `/reload`. Changing durable user role configuration, installing, removing,
99
99
  updating, or changing a role-pack extension uses Pi's normal reload flow.
100
100
  Contributor `session_shutdown` cleanup removes the
101
101
  old event listener before replacement extensions register. Already-running
@@ -0,0 +1,74 @@
1
+ # ADR-0011: Authorize explicit worktree cleanup by cwd containment
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-09-17
5
+ - **Scope:** Managed subagent worktree cleanup (issue #45)
6
+
7
+ ## Decision
8
+
9
+ A parent may explicitly remove one managed worktree when its canonical source
10
+ repository is within the session's canonical cwd subtree. This supersedes the
11
+ session-manifest-only ownership policy discussed in issue #45, per its
12
+ 2026-09-17 scope update: ended sessions leave orphans whose manifests are no
13
+ longer reachable. A reachable owned manifest enriches inventory and is marked
14
+ removed after successful cleanup, but is not the authorization boundary.
15
+
16
+ Discovery joins the managed filesystem root, Git registration and working-tree
17
+ state, live Herdr workspace state, and current-session manifests. Unknown or
18
+ conflicting eligibility evidence blocks removal, subject to the approved
19
+ individual process-visibility exception below. Detected same-user process
20
+ holders, known live children, and persistent leases block removal; process
21
+ names are not a runtime allowlist. Only Herdr-confirmed idle retained shell
22
+ PIDs are exempt, never active runtimes at those PIDs. Dirty work requires an
23
+ explicit WIP preservation commit on the retained branch; detached HEAD,
24
+ conflicts, locks, and initialized submodules cannot be bypassed.
25
+
26
+ The approved process-visibility policy treats unreadable individual process
27
+ details as non-blocking warnings, without an override flag. Inspection continues
28
+ so one hidden process cannot mask an observable holder. Inventory and removal
29
+ results report incomplete coverage separately from blockers, retaining warnings
30
+ through rechecks, preservation, and failures when available. Reports use counts
31
+ and bounded PID samples, not commands or environments. Linux uses `/proc`;
32
+ macOS uses same-user `lsof` cwd records and warns for individual unreadable
33
+ records. Unsupported platforms and failed global enumeration remain blockers;
34
+ partial output from a failed `lsof` is not successful enumeration.
35
+
36
+ Paths are canonicalized, so symlinked ancestors are supported while checkout
37
+ symlinks escaping the managed root are blocked. Ignored files are counted and
38
+ disclosed, but do not block removal and are not captured by preservation. A
39
+ failed preservation commit restores the original index. A created preservation
40
+ SHA remains in the report even when subsequent removal fails.
41
+
42
+ Removed manifests are no-op evidence only and never classify a recreated
43
+ checkout. Post-removal manifest-write failures are warnings, not removal
44
+ failures. Cleanup Git and Herdr calls have bounded 30-second timeouts. Child
45
+ sessions retain their existing `/worktree list` and `/worktree <name>` surfaces;
46
+ only the cleanup tools and removal subcommand are parent-only.
47
+
48
+ Open workspaces are removed through Herdr. Git-only orphans use Git removal,
49
+ verify checkout absence, and then prune stale registrations. Branches and their
50
+ commits are retained. Session start only reports inventory counts.
51
+
52
+ ## Rejected alternatives
53
+
54
+ - Session-only ownership cannot recover cross-session orphans.
55
+ - Global authorization would allow one project to remove another's review state.
56
+ - Automatic reaping at completion, shutdown, or on a timer erases the parent's
57
+ opportunity to review retained work.
58
+ - Force removal and branch deletion are outside this feature's authority.
59
+ - Repo-global stashes make preserved work less visible than retained commits.
60
+ - Recursive filesystem deletion to bypass submodule protection is not supported.
61
+
62
+ ## Consequences
63
+
64
+ Broad cwd values authorize broad repository subtrees, so callers must choose cwd
65
+ deliberately. Worktrees are not sandboxes. Fresh eligibility checks reduce but
66
+ cannot eliminate races with external writers; underlying refusals are surfaced,
67
+ not overridden. Same-user process inspection is permission-limited, and
68
+ other-user processes are not inspected. A protected process could hold the
69
+ checkout undetected; unreadability is not proof that it is unrelated. This
70
+ accepted blind spot replaces the earlier strict individual-unreadability
71
+ blocker, not the global enumeration or identity guards. Restart inventory does
72
+ not reattach watchers or rewrite other sessions' manifests.
73
+
74
+ See the [operating guide](../worktree-subagents.md#cleanup) for the shipped API.
@@ -50,7 +50,7 @@ For a worktree launch:
50
50
  - Uncommitted and untracked files from the parent checkout are not copied. Commit anything the child must see before spawning it, or pass the needed context in the task.
51
51
  - Worktree creation does not steal terminal focus.
52
52
 
53
- For an explicit interactive handoff, use `/worktree <worktree> [task]`. It creates the worktree from the current committed branch, forks the active conversation branch into the target-cwd session, launches a normal long-lived Pi process in the returned root pane, and focuses the destination workspace only after Herdr confirms Pi is running with the expected session and worktree cwd. Use `/worktree list` to inspect worktrees for the current repository. The original process and session remain intact; pane movement is not used to change a running shell's cwd.
53
+ For an explicit interactive handoff, use `/worktree <worktree> [task]`. It creates the worktree from the current committed branch, forks the active conversation branch into the target-cwd session, launches a normal long-lived Pi process in the returned root pane, and focuses the destination workspace only after Herdr confirms Pi is running with the expected session and worktree cwd. Use `/worktree list` to inspect managed worktrees whose source repositories are inside the current cwd subtree, including cross-session orphans. The original process and session remain intact; pane movement is not used to change a running shell's cwd.
54
54
 
55
55
  `worktree` cannot be set in agent frontmatter and is not exposed by the `/subagent <agent> <task>` shorthand. It is selected per call to the `subagent` tool. Ordered model fallback lists are not supported for worktree subagents: a failed attempt retains its worktree and branch for review, so a retry cannot safely reuse the requested branch. A persistent specialist either holds one worktree lease for its full lifetime or runs read-only in an ordinary pane; it cannot be re-bound.
56
56
 
@@ -112,6 +112,7 @@ Possible states are:
112
112
  | `ready_for_review` | Child exited successfully; workspace retained |
113
113
  | `needs_help` | Child called `caller_ping`; workspace retained |
114
114
  | `failed` | Creation, launch, or execution failed; any created workspace is retained |
115
+ | `removed` | Explicit parent cleanup verified checkout absence; branch and manifest retained |
115
116
 
116
117
  The manifest supports ownership and inspection; v1 does not provide automatic reconciliation after a full Pi/Herdr restart. Do not edit manifests by hand.
117
118
 
@@ -131,7 +132,7 @@ The parent receives the normal child summary plus:
131
132
 
132
133
  `clean` means there are no staged, unstaged, or untracked files. It does **not** mean the branch has no commits or diff relative to its base.
133
134
 
134
- If Git inspection fails, SHA/count/state/file fields are reported as unknown rather than guessed, and the warning is included in the handoff. Inspect the retained workspace directly before integrating or deleting it. Every retained handoff also includes the exact `herdr worktree remove --workspace <workspace-id>` command, but run it only after useful state is preserved.
135
+ If Git inspection fails, SHA/count/state/file fields are reported as unknown rather than guessed, and the warning is included in the handoff. Inspect the retained workspace directly before integrating or deleting it. Every retained handoff points to `/worktree remove` and `worktree_remove`, with the raw Herdr command as an operator override after independent safety checks.
135
136
 
136
137
  ## Parallel pull-request review without new worktrees
137
138
 
@@ -207,21 +208,30 @@ This manual continuation is not watched by the original parent lifecycle. Do not
207
208
 
208
209
  Worktree and branch cleanup is always explicit. Ordinary temporary reviewer panes close after result delivery; Herdr removes their tab only if its last pane closes. The retained worktree root shell is excluded from automatic cleanup, and user-added panes are preserved. Persistent specialists retain their pane between tasks and follow the existing explicit stop semantics.
209
210
 
210
- First make sure commits, patches, or uncommitted files are no longer needed. Then remove the Herdr worktree workspace:
211
+ Parent sessions have an inspect-only inventory and an explicit removal surface:
211
212
 
212
- ```bash
213
- herdr worktree remove --workspace <workspace-id>
213
+ ```text
214
+ /worktree list
215
+ /worktree remove <path|branch|workspace-id> [--preserve]
214
216
  ```
215
217
 
216
- Herdr removes the workspace and linked checkout. Without `--force`, dirty worktrees are protected. Do not use `--force` unless discarding all remaining work is intentional and verified.
218
+ The equivalent tools are `worktree_list({})` and `worktree_remove({ target, preserve?: true })`. Use the exact path when a branch name is ambiguous. Children are not offered these tools or `/worktree remove`. Their existing `/worktree list` (repository-local Herdr listing) and `/worktree <name>` handoff remain available. Detached entries are labeled `(detached HEAD)` in the child listing and do not prevent cleanup inspection of named-branch siblings; the detached checkout itself remains blocked.
217
219
 
218
- The branch is a separate Git ref; inspect and delete it separately only when repository policy allows:
220
+ Authorization uses **cwd containment**: the canonical source repository root must equal or descend from the canonical session cwd. It does not use the managed checkout's location or require a current-session manifest. Discovery scans `~/.herdr/worktrees/*/*/`, joins Git registration and Herdr workspace state, and includes orphans from ended sessions. Out-of-scope entries are never removable; unregistered residue and failed probes are unknown, not clean. Inventory includes path, branch, source, workspace, Git state, manifest presence, concrete blockers, and separate process-inspection warnings. The managed root, source, and cwd are canonicalized: symlinked ancestors work normally, but a checkout symlink escaping the managed root is blocked.
219
221
 
220
- ```bash
221
- git branch -d <branch>
222
- ```
222
+ Removal rechecks containment, Git registration, no detected process holder, known live child, or persistent-specialist lease, and no uncommitted, untracked, or conflicted files. Liveness checks observable same-user processes from any session with a cwd inside the checkout, regardless of runtime name (including thread-suffixed Node names). Only Herdr-confirmed idle retained shell PIDs are exempt; an active runtime at the same PID is not. Detached HEAD, locked checkouts, initialized submodules, and conflicting identity evidence block removal. For initialized submodules, deinitialize them deliberately or use operator removal. No force option is provided.
223
+
224
+ Unreadable individual process details are **warnings, not blockers**, with no override flag. Scanning continues after each unreadable entry, so another observable holder still blocks removal. Unknown processes are not classified as unrelated. Inventory rows expose separate `blockers` and `warnings` arrays; removal results expose `warnings` and retain warnings from earlier inspections, including refusals and preservation/removal failures when available. Human-readable output includes the same warnings, with counts and at most ten sampled PIDs per inspection, not process commands or environments. Pi tool refusals and failures use thrown errors, so their warnings travel in the error message rather than structured tool details.
225
+
226
+ This is not proof of machine-wide inactivity: same-user inspection is permission-limited, other-user processes are not inspected, and a protected process could hold the checkout undetected. Linux enumerates `/proc` and checks readable cwd paths; disappeared processes and confirmed zombies do not hold a checkout. macOS uses same-user `lsof` cwd records; unreadable or missing details in individual returned records warn, but a failed global `lsof` blocks even when it returns partial output. Neither platform can establish coverage of processes hidden by the OS. Unsupported platforms, failed or empty global enumeration, and unknown Git, containment, or ownership state remain blockers.
227
+
228
+ Dirty work requires an explicit `--preserve` or `preserve: true`. This stages all uncommitted and untracked files and creates a WIP commit on the retained branch. A failed commit restores the pre-preservation index and leaves the checkout and its uncommitted files in place; removal does not proceed. Success reports the preservation SHA even if the subsequent recheck or removal fails. Conflicts, detached HEAD, and other blockers cannot be bypassed with preservation.
229
+
230
+ Ignored files do not block cleanup. Inventory shows their exact file count, and a successful removal reports how many were deleted. Counting streams the NUL-delimited Git listing rather than buffering all paths; errors and the 30-second timeout still block removal, never report a guessed zero. Preservation does not capture ignored files; attempted and successful preservation reports disclose that exclusion when ignored files are present.
231
+
232
+ Open workspaces are removed through Herdr. Git-only orphans use `git worktree remove`, verify checkout absence, and then prune stale Git registrations. Failed removal is reported, never forced. A reachable owned manifest is merge-updated to `removed` with `workspaceRemovedAt` only after success; absent cross-session manifests are reported and are not rewritten. A manifest update failure after removal is a removed-with-warning result, not a failed removal. Removed manifests provide already-removed no-op evidence only; a new checkout at the same path is classified independently. Missing and dangling manifest paths are non-matches. Permission errors, symlink loops, and other undecidable manifest identities remain blockers, as do genuine identity disagreements. Cleanup Git and Herdr calls have 30-second timeouts; a timed-out operation fails closed rather than proceeding with removal.
223
233
 
224
- Use `git branch -D` only when you have independently verified that discarding unmerged commits is safe.
234
+ Branches and their commits are always retained: cleanup never deletes, force-updates, or rewrites a branch. It never runs on completion, shutdown, or a timer. Parent session start emits only one compact inventory count when contained worktrees remain. Unsupported platforms or failed global process enumeration block removal; individual visibility gaps are disclosed warnings, never proof that an orphan is idle.
225
235
 
226
236
  ## Current limits
227
237
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-herdr-agents",
3
- "version": "1.7.0",
3
+ "version": "2.0.1",
4
4
  "description": "Asynchronous Pi subagents in Herdr, with optional isolated Git worktrees",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -33,7 +33,7 @@
33
33
  "format": "biome format --write pi-extension test skills/orchestrate/adversarial-review-example.js",
34
34
  "format:check": "biome format pi-extension test skills/orchestrate/adversarial-review-example.js",
35
35
  "lint": "oxlint pi-extension test skills/orchestrate/adversarial-review-example.js",
36
- "test": "node --experimental-strip-types --test test/test.ts test/launch.test.ts test/runtime-routing.test.ts test/release-workflow.test.ts test/package-skill.test.js test/evals/*.test.mjs",
36
+ "test": "node --experimental-strip-types --test test/test.ts test/worktree-cleanup.test.ts test/launch.test.ts test/runtime-routing.test.ts test/release-workflow.test.ts test/package-skill.test.js test/evals/*.test.mjs",
37
37
  "test:integration": "node --experimental-strip-types --test --test-concurrency=1 test/integration/*.test.ts",
38
38
  "test:integration:live": "PI_TEST_LIVE=1 node --experimental-strip-types --test --test-concurrency=1 test/integration/*.test.ts",
39
39
  "version": "auto-changelog --package && git add CHANGELOG.md"
@@ -0,0 +1,22 @@
1
+ import { join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../..");
7
+
8
+ export function getAgentConfigDir(): string {
9
+ return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
10
+ }
11
+
12
+ export function getSubagentsConfigPath(): string {
13
+ return join(getAgentConfigDir(), "herdr-agents", "config.json");
14
+ }
15
+
16
+ export function getSubagentsConfigExamplePath(): string {
17
+ return join(PACKAGE_ROOT, "config.json.example");
18
+ }
19
+
20
+ export function getSubagentsPackageRoot(): string {
21
+ return PACKAGE_ROOT;
22
+ }