pi-herdr-agents 3.0.2 → 3.1.0

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.
Files changed (33) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +42 -1
  3. package/CONTEXT.md +25 -4
  4. package/README.md +126 -13
  5. package/RELEASING.md +2 -0
  6. package/docs/README.md +2 -1
  7. package/docs/adr/0011-explicit-worktree-cleanup.md +1 -1
  8. package/docs/adr/0015-report-opened-primary-workspace.md +121 -0
  9. package/docs/worktree-subagents.md +5 -3
  10. package/maestro/adapters/pi/activity-file.ts +11 -1
  11. package/maestro/adapters/pi/launch.ts +29 -15
  12. package/maestro/adapters/pi/process-identity.ts +38 -0
  13. package/maestro/core/activity.ts +1 -1
  14. package/maestro/core/config/model-config.ts +83 -10
  15. package/maestro/core/config/task-model-init.ts +208 -13
  16. package/maestro/core/config/task-model-types.ts +19 -1
  17. package/maestro/core/config/type-guards.ts +5 -0
  18. package/maestro/core/opened-primary-workspace.ts +108 -0
  19. package/maestro/core/supervision.ts +38 -13
  20. package/maestro/core/surface-provider.ts +22 -0
  21. package/maestro/core/wake.ts +37 -0
  22. package/maestro/core/worktree-cleanup.ts +52 -1
  23. package/maestro/core/worktree.ts +2 -0
  24. package/maestro/runtime/index.ts +2 -1
  25. package/maestro/runtime/task-model-init.ts +106 -9
  26. package/maestro/runtime/worktree-operations.ts +2 -0
  27. package/maestro/surfaces/fake/fake-surface-provider.ts +8 -0
  28. package/maestro/surfaces/herdr/herdr-surface-provider.ts +12 -1
  29. package/maestro/surfaces/herdr/herdr.ts +345 -49
  30. package/maestro/surfaces/herdr/terminal.ts +0 -10
  31. package/package.json +1 -1
  32. package/pi-extension/subagents/index.ts +170 -16
  33. package/skills/pi-herdr-agents/SKILL.md +16 -4
package/AGENTS.md CHANGED
@@ -96,6 +96,8 @@ npm pack --dry-run
96
96
  git diff --check
97
97
  ```
98
98
 
99
+ `npm run lint` needs Node.js 22.18+ (or 22.6–22.17 with `NODE_OPTIONS=--experimental-strip-types`) because oxlint imports `oxlint.config.ts` and its `tools/oxlint/anti-slop/index.ts` plugin as TypeScript.
100
+
99
101
  Run LSP diagnostics on every changed TypeScript file; lint and tests do not catch every TypeScript error.
100
102
 
101
103
  For Herdr or lifecycle changes, run the deterministic suite from inside Herdr. Run only one integration suite at a time on a Herdr instance; concurrent suites compete for terminal focus and process capacity and can cause false timeouts or leaked test resources.
package/CHANGELOG.md CHANGED
@@ -7,12 +7,53 @@ 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
- ## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2)
10
+ ## [v3.1.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.3...v3.1.0)
11
+
12
+ ### Commits
13
+
14
+ - feat: route task-model init through pack approval [`1b6bacd`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944)
15
+ - test: dispatch all Pi lifecycle handlers in placement fixtures [`f96e59d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f96e59de8890d39ce6c79be8de245d287fc2485e)
16
+
17
+ ## [v3.0.3](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.2...v3.0.3) - 2026-10-08
18
+
19
+ ### Merged
20
+
21
+ - Release 3.0.3 [`#82`](https://github.com/giuseppecrj/pi-herdr-agents/pull/82)
22
+ - test(integration): re-read worktree row before asserting its open workspace [`#81`](https://github.com/giuseppecrj/pi-herdr-agents/pull/81)
23
+ - Fix Pi readiness check when Pi rewrites argv [`#80`](https://github.com/giuseppecrj/pi-herdr-agents/pull/80)
24
+ - Fix tsc errors and git-version-sensitive index test; document lint Node floor [`#79`](https://github.com/giuseppecrj/pi-herdr-agents/pull/79)
25
+ - fix(worktrees): report the primary workspace creation opened [`#78`](https://github.com/giuseppecrj/pi-herdr-agents/pull/78)
26
+ - fix: keep parked supervision wakes alive until they settle [`#77`](https://github.com/giuseppecrj/pi-herdr-agents/pull/77)
27
+
28
+ ### Commits
29
+
30
+ - remove openspec [`5e31900`](https://github.com/giuseppecrj/pi-herdr-agents/commit/5e31900ea96fdaab6fcf26fc9a335497dcbc528d)
31
+ - fix(worktrees): bound the primary workspace report and hedge its note [`44a18a8`](https://github.com/giuseppecrj/pi-herdr-agents/commit/44a18a8145549801bd17007f05beb8a8cddf41b9)
32
+ - Confirm Pi startup by launch environment, not rewritten argv [`bbfe7e7`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bbfe7e7093bd76d8803562ddb6263c27cfe4a982)
33
+ - fix: settle stranded supervision waits and test wake retain accounting [`d18df1c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/d18df1cb83bedf86951d2f9702a4a6185b44a8e8)
34
+ - test: bound the identity-capture loop anchor and close supervision test gaps [`4a0e71a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4a0e71a97ebc9ba3f21a5377504f24c3d837d9a2)
35
+ - Treat an unreadable Pi environment as unproven, not fatal [`b69151a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b69151ae85bd13c45bdf3e980c0b36e315c7c863)
36
+ - chore: release v3.0.3 [`1c75adb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1c75adb0864af9b922d1471734b086943435f238)
37
+ - test(worktree-cleanup): make the index-restore test hermetic and deterministic [`16840f6`](https://github.com/giuseppecrj/pi-herdr-agents/commit/16840f64b9481983cf44fc928061fd4e6822434d)
38
+ - test(worktree-cleanup): clear inherited git repository variables in the fixture [`323e1fb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/323e1fbf6e6240eb5e7000d63fc84e23b391259a)
39
+ - test(worktrees): expect launch snapshots in scoped inventory [`f733040`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f73304088c8a781b1ed7333a5a09175ec7c896e9)
40
+ - fix(types): resolve strict tsc errors in activity scope guard and recorder [`26bc9ef`](https://github.com/giuseppecrj/pi-herdr-agents/commit/26bc9ef2e8a2afc23901641efdd19661fcd3f756)
41
+ - docs: limit the lint type-stripping fallback to Node 22.6-22.17 [`ccab597`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ccab597cfaf2bcf6e37889ae3103dd6e9c78c6ef)
42
+ - docs: note that npm run lint needs Node.js 22.18 or newer [`b8ffa8d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b8ffa8d1d83419588db8d4aed506d5d236360673)
43
+ - test(integration): check cancel manifest workspace id; clarify Herdr Node note [`aac18d9`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aac18d926385582ccc36783fd8a9475db7df7e3d)
44
+ - test(worktree-cleanup): snapshot the index after git status [`aa869b2`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aa869b2a62c8868d377b134d3f05b029692f2eb0)
45
+ - docs: integration tests need the Herdr server on Node.js 22.19+ [`bdd791b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bdd791bd934ea3174db32e1fcf1353353506224b)
46
+
47
+ ## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2) - 2026-10-06
11
48
 
12
49
  ### Merged
13
50
 
14
51
  - fix(subagents): resolve submodule sources and refresh fallback context [`#76`](https://github.com/giuseppecrj/pi-herdr-agents/pull/76)
15
52
 
53
+ ### Commits
54
+
55
+ - chore: release v3.0.2 [`245e54e`](https://github.com/giuseppecrj/pi-herdr-agents/commit/245e54e7ccc937f69bee6093906d26b03b8a92c9)
56
+
16
57
  ## [v3.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1) - 2026-10-06
17
58
 
18
59
  ### Merged
package/CONTEXT.md CHANGED
@@ -71,6 +71,13 @@ verified by the parent against the Herdr pane. A worktree cancel signals and
71
71
  judges exit only by it.
72
72
  _Avoid_: argv match, foreground process, shell visibility
73
73
 
74
+ **Pi startup confirmation**:
75
+ The bounded check that a `/worktree` handoff's Pi is running before its
76
+ workspace is focused: a Pi process in the root pane with the worktree cwd whose
77
+ launch-time environment names the launched session in
78
+ `PI_HERDR_AGENTS_SESSION`. Nothing inside Pi reads that marker.
79
+ _Avoid_: argv match, `--session` visibility, child-context hint
80
+
74
81
  **No-progress advisory**:
75
82
  An internal warning that an active child shows no durable progress in its session
76
83
  JSONL or activity snapshot. It is advisory only and never changes the child's
@@ -182,9 +189,13 @@ global enumeration and unsupported platforms remain blockers.
182
189
  _Avoid_: Proven unrelated, machine-wide inactivity, bypass permission
183
190
 
184
191
  **Explicit worktree removal**:
185
- A parent-requested removal of one named managed checkout and its open workspace,
186
- with absence verification and retained branch history. Never automatic reaping.
187
- _Avoid_: Branch deletion, completion cleanup
192
+ A parent-requested removal of one named managed checkout and its open worktree
193
+ workspace, with absence verification and retained branch history. Never automatic
194
+ reaping. It never closes the source repository's primary workspace. When this
195
+ process recorded that worktree creation appeared to open that workspace and a
196
+ read taken just before the report still shows it untouched, removal suggests
197
+ the `herdr workspace close` command, to use only if the workspace is unused.
198
+ _Avoid_: Branch deletion, completion cleanup, automatic close of a workspace the user may have used
188
199
 
189
200
  **Dirty-state preservation**:
190
201
  Explicit opt-in staging and WIP commitment of a worktree's uncommitted and
@@ -199,7 +210,9 @@ architecture to planning and diagnosis, coding to workers, review to reviewers,
199
210
  QA to software and test runners, and docs to documentation workers. Categories
200
211
  describe work, not complexity. `/subagents-init [preferences]` drafts them from
201
212
  the active extension-loaded registry's synchronous snapshot and existing saved
202
- choices, with source-based research when available. A dynamic provider awaiting
213
+ choices, with source-based research when available. A single loaded extension
214
+ that offers on the task-model init approval event owns approval and the write
215
+ instead of the host writer. A dynamic provider awaiting
203
216
  its initial catalog refresh might be absent. `task:<category>` is a subagent
204
217
  model selector, not a command or parent model change. Ordered authenticated
205
218
  candidate plans resolve before launch; ordinary nonpersistent runs can retry
@@ -216,6 +229,14 @@ the independence boundary; project policy may separately require a
216
229
  different provider.
217
230
  _Avoid_: Generic tier, reviewer-family enforcement, per-step routing
218
231
 
232
+ **Ranking basis**:
233
+ What a task-model proposal's ranking rests on, as the proposing model submitted
234
+ it: `registry-only`, or `research` with the http(s) sources consulted in that
235
+ run, how each influenced the ranking, and the remaining uncertainty. It travels
236
+ with one write for review and is never saved; `tasksMeta.method` keeps its kind.
237
+ Code checks its shape, not that a source was read.
238
+ _Avoid_: Verified research, evidence store
239
+
219
240
  **Loop template**:
220
241
  A future reusable orchestration definition beside `models`, describing stages,
221
242
  task categories, and a termination/report contract. Loop templates are not
package/README.md CHANGED
@@ -192,7 +192,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
192
192
  | `worktree_list` | Parent-only inspect-only inventory of managed worktrees and cleanup blockers |
193
193
  | `worktree_remove` | Parent-only explicit removal by `target` path, branch, or workspace ID; optional `preserve: true` commits dirty state first |
194
194
  | `subagent_resume` | Resume a previous Pi-backed sub-agent session in a new ordinary pane (async) |
195
- | `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` |
195
+ | `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` and carrying an unsaved ranking `basis` |
196
196
 
197
197
  | Skill | Description |
198
198
  | ----- | ----------- |
@@ -207,7 +207,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
207
207
  | -------------------------- | ------------------------------------ |
208
208
  | `/worktree <name> [task]` | Continue this session in a new managed worktree (`/worktree list` lists them) |
209
209
  | `/subagent <agent> <task>` | Spawn a named agent directly (`/subagent list` lists available agents) |
210
- | `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences |
210
+ | `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences; a loaded approval extension can own the write |
211
211
 
212
212
  ### Taxonomy and discovery
213
213
 
@@ -461,7 +461,10 @@ JSON without truncating models and reports its model count and JSON character
461
461
  count (not a token estimate); large catalogs still consume context. This is the
462
462
  current synchronous snapshot: a dynamic provider whose initial catalog refresh
463
463
  has not completed might be absent. Init does not refresh providers or probe the
464
- network for availability.
464
+ network for availability. The brief also includes `configRevision`, the
465
+ revision of the exact config bytes that supplied the saved preferences. Init
466
+ does not start while a turn runs or messages are queued, and an empty registry
467
+ ends with a notice instead of a prompt. Neither case writes anything.
465
468
 
466
469
  The draft considers current saved task, default, and per-agent preferences.
467
470
  Optional command arguments set ranking preferences. Otherwise it favors
@@ -482,8 +485,23 @@ sources, disclose uncertainty and notable exclusions, and avoid duplicate
482
485
  upstream models across routes unless deliberate redundancy is explained. Display
483
486
  names help identify candidates but, like aliases, do not prove upstream
484
487
  equivalence; research is still required. Price or context size alone is not
485
- quality evidence. It reports `registry-only` when
486
- search is unavailable or yields no usable evidence; no live model probes run.
488
+ quality evidence. No live model probes run.
489
+
490
+ The proposal carries a ranking `basis`. It is `{"kind":"registry-only"}` when
491
+ search is unavailable or yields no usable evidence. It is `{"kind":"research"}`
492
+ only with the http(s) `sources` consulted in this run, the `influence` each had
493
+ on the ranking, and the remaining `uncertainty`. Code checks the basis's shape,
494
+ not whether a source was read, so the basis is labeled as submitted and
495
+ unverified.
496
+
497
+ While init runs, it emits `pi-herdr-subagents:task-models:init:approval:v1`.
498
+ With no offer, the prompt directs the model to `subagents_write_task_models`
499
+ with `expectedConfigRevision`. When exactly one loaded extension offers, init
500
+ opens that extension's flow, and the extension's instructions replace the
501
+ writer instruction; the extension then owns approval and the write. Two or more
502
+ offers, or an invalid one, stop init before any extension opens or a prompt is
503
+ sent. Once an offer is recorded, a refusal or failure also stops init: it never
504
+ falls back to the direct writer. See [Task-model init events](#task-model-init-events).
487
505
 
488
506
  The writer validates and atomically replaces `models.tasks` and `tasksMeta`,
489
507
  preserving unrelated settings. Its tool schema accepts partial nonempty
@@ -491,9 +509,15 @@ categories (omitted categories are removed), rejects empty `tasks: {}` input,
491
509
  and rejects exact duplicate refs within a category after trimming;
492
510
  IDs remain case-sensitive. Its result includes normalized saved `tasks`,
493
511
  `tasksMeta`, `configPath`, `missingCategories`, and `configRevision`. Init requests all six categories
494
- and a before/after table based on that saved result, not the unsaved draft. It
495
- must explain missing categories or changed choices; with no available models,
496
- it must report the limitation without writing.
512
+ and a before/after table based on that saved result, not the unsaved draft, and
513
+ must explain missing categories or changed choices.
514
+
515
+ Optional `basis` must have the same kind as `tasksMeta.method`. A research basis
516
+ needs at least one source with an http(s) URL that has a host and a nonblank
517
+ `influence`, plus a nonblank `uncertainty`. The writer validates it before
518
+ writing, returns it in the result, and never saves it; only `tasksMeta.method`
519
+ persists. Calls without `basis`, including research calls, keep the earlier
520
+ contract.
497
521
 
498
522
  Optional `expectedConfigRevision` makes a write conditional on the config the
499
523
  proposal was read from. A revision is `sha256:` followed by 64 lowercase hex
@@ -549,13 +573,94 @@ rewrites the file. Other values remain configuration errors, as do unknown keys
549
573
  under `roles`. Registered role packs are the entire package layer, and global and
550
574
  project definitions keep their precedence over them.
551
575
 
576
+ ### Task-model init events
577
+
578
+ Two versioned `pi.events` channels let another extension take part in
579
+ `/subagents-init` without importing this package. Both carry the invoking
580
+ command's live `ExtensionCommandContext`, so they are for trusted extensions in
581
+ the same process. The context is current only while the event is emitted.
582
+
583
+ The host emits `pi-herdr-subagents:task-models:init:approval:v1` while init
584
+ runs:
585
+
586
+ ```typescript
587
+ type ApprovalRequest = {
588
+ apiVersion: 1;
589
+ brief: TaskModelBrief; // frozen: operatorPreferences, categories, configRevision, current, models (never empty)
590
+ context: ExtensionCommandContext;
591
+ offer(offer: { owner: string; open(): OpenResult }): "recorded" | "closed";
592
+ };
593
+ type OpenResult =
594
+ | {
595
+ kind: "ready";
596
+ destination: { toolName: string; instructions: string };
597
+ cancel(): void; // closes the flow this open created
598
+ }
599
+ | { kind: "blocked"; reason: string };
600
+ ```
601
+
602
+ - Call `offer` synchronously, before the listener's first `await`. After `emit`
603
+ returns, `offer` answers `"closed"` and records nothing.
604
+ - Offer whenever your extension owns task-model approval, even when it is busy,
605
+ and refuse from `open`. `owner` is one printable token without spaces.
606
+ - The host opens nothing until collection ends. Two offers (even with the same
607
+ owner) or an invalid offer stop init. With one offer, the host calls `open()`
608
+ once, and it must return synchronously.
609
+ - `ready` must leave `toolName` active. Its `instructions` replace the host's
610
+ writer instruction in the prompt the host sends.
611
+ - `ready` must include `cancel`. When the host does not hand the prompt to Pi
612
+ (the tool is inactive, or `pi.sendUserMessage` throws), it calls `cancel()`
613
+ once before reporting `not-started`. `cancel` must close only the flow that
614
+ this `open` created, never a later one. A `cancel` that throws is reported
615
+ with the outcome.
616
+ - A throw, a promise, an invalid result (including a missing `cancel`), or
617
+ `blocked` stops init with a notice and no prompt. The host cannot undo what
618
+ `open` changed before failing.
619
+ - Pi accepts the prompt asynchronously. It can still reject it after `start`
620
+ reports `started`, for example when the selected model has no configured
621
+ auth; the host refuses up front only when no model is selected. Pi delivers
622
+ the prompt to `input` handlers (source `"extension"`) before that check and
623
+ emits `before_agent_start` and `agent_start` only after it passes. An
624
+ extension whose flow outlives one prompt should close it when other input or
625
+ a run arrives before those events, so a later request cannot inherit it.
626
+ - Pi's event bus logs and swallows listener exceptions. A listener that throws
627
+ before offering looks the same as no listener, so init uses the direct
628
+ writer. An extension that guards the writer still refuses that write.
629
+
630
+ Another extension's command emits `pi-herdr-subagents:task-models:init:start:v1`
631
+ to start the same init:
632
+
633
+ ```typescript
634
+ type StartRequest = {
635
+ apiVersion: 1;
636
+ context: ExtensionCommandContext;
637
+ preferences: string; // ranking preferences, passed through as typed
638
+ offer(offer: { owner: string; start(): InitOutcome }): "recorded" | "closed";
639
+ };
640
+ type InitOutcome =
641
+ | { kind: "started"; destination: string }
642
+ | { kind: "not-started"; reason: string };
643
+ ```
644
+
645
+ The host offers synchronously as `pi-herdr-agents` and starts nothing until the
646
+ emitter calls `start()`. Call it only when exactly one host offered. `start`
647
+ runs init with the request's context and returns synchronously; the emitter
648
+ shows a `not-started` reason. The host does not listen in subagent sessions
649
+ and unsubscribes at `session_shutdown`. Unsubscribe an approval listener there
650
+ too, as the role-pack bridge does.
651
+
552
652
  ### Supervision transport
553
653
 
554
654
  On supported local filesystems, supervision uses file wake-ups plus one shared
555
655
  4.8-second pane reconciliation. A wake-up only prompts fresh evidence
556
- collection; it never establishes a result by itself. If the watcher or shared
557
- pane inspection becomes unavailable, supervision quietly returns to the legacy
558
- one-second polling cadence. No caller action is required.
656
+ collection; it never establishes a result by itself. While any child's wait for
657
+ that check is parked, the coordinator's file watches stay referenced, so the
658
+ sidecar is still observed when the parent has no other event-loop work. The
659
+ reference is released when the wait settles, is aborted, or its child is
660
+ unregistered. With no parked wait, the watches stay unreferenced and do not
661
+ keep the process running. If the watcher
662
+ or shared pane inspection becomes unavailable, supervision quietly returns to
663
+ the legacy one-second polling cadence. No caller action is required.
559
664
 
560
665
  Set `supervision.forcePolling` to `true` in the durable user `config.json` to
561
666
  disable wake-ups and use that legacy cadence deliberately. The setting is read
@@ -866,15 +971,19 @@ is no other parent/child protocol.
866
971
 
867
972
  `/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.
868
973
 
974
+ The destination workspace is focused only after Pi startup is confirmed. Every fresh launch, including the handoff, sets `PI_HERDR_AGENTS_SESSION` to its session file; confirmation requires a Pi process in the root pane with the worktree cwd whose launch-time environment carries that value. Pi rewrites its process title, so command-line text is never evidence. Nothing inside Pi reads the variable, and it is not a child-context hint. Confirmation reads Linux `/proc`; see [Worktree subagents](docs/worktree-subagents.md).
975
+
869
976
  ---
870
977
 
871
978
  ### Explicit worktree cleanup
872
979
 
873
980
  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.
874
981
 
875
- 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. A successful launch from a linked checkout does not itself authorize its removal; cleanup uses the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at that principal checkout or an ancestor containing it, then use normal explicit cleanup; `cd` inside an existing Pi session does not change the session cwd. A source repository that is a Git submodule is located through the `core.worktree` setting of its shared Git directory under the superproject's `.git/modules`, not through that directory's parent. 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.
982
+ 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. A successful launch from a linked checkout does not itself authorize its removal; cleanup uses the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at that principal checkout or an ancestor containing it, then use normal explicit cleanup; `cd` inside an existing Pi session does not change the session cwd. A source repository that is a Git submodule is located through the `core.worktree` setting of its shared Git directory under the superproject's `.git/modules`, not through that directory's parent. Unknown inspection, identity disagreements, detached HEAD, locked checkouts, and initialized submodules block removal. Out-of-scope repositories are never eligible. Open worktree workspaces use Herdr removal. The source repository's primary workspace is never closed automatically. If this process recorded that worktree creation appeared to open it and a read taken just before the report still shows it untouched, removal adds `wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX`; a missing or wrong-typed field, another open workspace for the repository, another session, or a restarted process reports nothing. 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.
983
+
984
+ A worktree launch may add three synchronous Herdr snapshots (`worktree list` before create, `worktree list` after create, and `pane list` for the opened primary), each bounded at 3 seconds, 9 seconds if all three run to their timeouts. A failed snapshot claims nothing and is returned as a launch diagnostic, shown in the `subagent` acknowledgement and the `/worktree` notice; it is not written to the console. Claims are kept only in this process and are not written to the manifest. Ordinary non-worktree launches add no Herdr calls. Explicit removal adds a primary-workspace report only when this process holds a claim for the removed source repository: `worktree list`, `workspace get`, and `pane list`, each asynchronous and bounded at 3 seconds, taken after the checkout is removed. That is at most three calls and 9 seconds of wall time, without blocking the event loop. These report calls sit on top of the existing cleanup Git and Herdr calls, which stay at 30 seconds each.
876
985
 
877
- Cleanup never deletes or rewrites branches, uses force flags, or runs automatically. Session startup does not scan worktree inventory, avoiding blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. 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.
986
+ Cleanup never deletes or rewrites branches, uses force flags, or runs automatically. It never closes the source repository's primary workspace. Session startup does not scan worktree inventory, avoiding blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. 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.
878
987
 
879
988
  ## Custom Agents
880
989
 
@@ -1258,12 +1367,16 @@ npm run lint
1258
1367
  npm pack --dry-run
1259
1368
  ```
1260
1369
 
1370
+ `npm run lint` needs Node.js 22.18+ (or 22.6–22.17 with `NODE_OPTIONS=--experimental-strip-types`) because oxlint imports TypeScript directly: its configuration, `oxlint.config.ts`, and the JavaScript plugin it loads, `tools/oxlint/anti-slop/index.ts`. Without type stripping, oxlint fails before linting with `Unknown file extension ".ts"`. This is a contributor tooling requirement, not a runtime requirement for package users.
1371
+
1261
1372
  Run the required end-to-end suite from inside Herdr:
1262
1373
 
1263
1374
  ```bash
1264
1375
  npm run test:integration
1265
1376
  ```
1266
1377
 
1378
+ Start the Herdr server with Node.js 22.19+ on its `PATH`, because test panes inherit the server's environment, not the test runner's: Pi 1.0.0 needs Node.js 22.19+ and crashes at startup on versions older than 22.15.
1379
+
1267
1380
  The deterministic suite launches real Pi sessions, Herdr panes, and worktrees without provider credentials. The optional live-provider smoke test is not a merge gate:
1268
1381
 
1269
1382
  ```bash
package/RELEASING.md CHANGED
@@ -10,6 +10,8 @@ The published version must be unique on npm.
10
10
 
11
11
  `3.0.0` is the pack-neutral baseline. It is a major release because it removes the bundled roles, `/plan`, `/skill:orchestrate`, `/iterate`, `/btw`, and `/btw-close`, and makes `roles.bundled` a deprecated no-op. Its hand-written breaking changes and migration notes are in the README [Release notes](README.md#release-notes) section, because `npm version` regenerates `CHANGELOG.md` and would discard hand edits there. Record later breaking-release notes in the same section.
12
12
 
13
+ `3.1.0` is a compatible minor release. It adds the request-scoped task-model init events that let a loaded pack approve `/subagents-init` proposals; with no pack offering, the direct writer is unchanged. It was prepared from feature commit `1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944`. It is not published until the release workflow succeeds.
14
+
13
15
  Do not design a release that creates a GitHub Release without a successful npm publish for a new version. The workflow publishes first, then tags and creates the GitHub Release.
14
16
 
15
17
  ## Prerequisites
package/docs/README.md CHANGED
@@ -60,10 +60,11 @@ real implementations; in-memory fakes are conformance fixtures only.
60
60
  | [`0008`](adr/0008-adopt-pi-only-subagent-execution.md) | Partially superseded by 0012 | Keep Pi-only execution; 0012 supersedes only the adapter-seam restriction. |
61
61
  | [`0009`](adr/0009-remove-workflow-subsystem.md) | Accepted; skill ownership moved by 0013 | Remove the workflow subsystem; use public subagent fan-out and parent synthesis. |
62
62
  | [`0010`](adr/0010-persistent-specialists-as-session-generations.md) | Accepted | Define persistent specialists as logical identities with policy-bound session generations. |
63
- | [`0011`](adr/0011-explicit-worktree-cleanup.md) | Accepted | Authorize explicit worktree cleanup by cwd containment; retain branches and reject automatic reaping. |
63
+ | [`0011`](adr/0011-explicit-worktree-cleanup.md) | Accepted; amended by 0015 | Authorize explicit worktree cleanup by cwd containment; retain branches and reject automatic reaping. |
64
64
  | [`0012`](adr/0012-adopt-maestro-seams-in-repo.md) | Accepted | Adopt in-repo seams and conformance fakes; keep one package, Pi-only execution, and Herdr-only surfaces. |
65
65
  | [`0013`](adr/0013-pack-neutral-execution-host.md) | Accepted | Ship a pack-neutral execution host; roles and workflows move to optional packs, and `/iterate` and `/btw` are removed. |
66
66
  | [`0014`](adr/0014-operator-cancel-terminal-intent.md) | Accepted | Operator cancel records terminal intent before owned termination: no fallback, one confirmed cancelled result, unconfirmed runs stay live. |
67
+ | [`0015`](adr/0015-report-opened-primary-workspace.md) | Accepted | Report, and never automatically close, a primary workspace that worktree creation opened. |
67
68
 
68
69
  ## Historical material
69
70
 
@@ -1,6 +1,6 @@
1
1
  # ADR-0011: Authorize explicit worktree cleanup by cwd containment
2
2
 
3
- - **Status:** Accepted
3
+ - **Status:** Accepted; amended by [ADR-0015](0015-report-opened-primary-workspace.md)
4
4
  - **Date:** 2026-09-17
5
5
  - **Scope:** Managed subagent worktree cleanup (issue #45)
6
6
 
@@ -0,0 +1,121 @@
1
+ # ADR-0015: Report the primary workspace worktree creation opened
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-07
5
+ - **Amends:** [ADR-0011](0011-explicit-worktree-cleanup.md)
6
+
7
+ ## Context
8
+
9
+ `herdr worktree create` groups a new worktree with the source repository's
10
+ primary workspace and opens that workspace when none is open. `herdr worktree
11
+ remove` removes only the linked checkout, so the first worktree of an idle
12
+ repository leaves an empty shell workspace behind.
13
+
14
+ Herdr's `WorktreeCreated` result does not include `created_parent`, so this
15
+ extension cannot ask Herdr which workspace the create call opened. The only
16
+ signal is a `herdr worktree list --cwd` snapshot before create and another
17
+ after it. A workspace the user or another session opens during a long checkout
18
+ can be attributed to this create. That race is inherent in the snapshots.
19
+
20
+ ## Decision
21
+
22
+ Explicit worktree removal never closes the source repository's primary
23
+ workspace.
24
+
25
+ On a worktree launch, this process may record one claim: workspace id, the
26
+ source `repo_key`, the only pane's `terminal_id`, and the checkout path the
27
+ workspace was opened at. A workspace that was already open is not claimed. A
28
+ snapshot that fails, times out, or lacks a required field claims nothing. The
29
+ failure is returned as a launch diagnostic, shown in the `subagent`
30
+ acknowledgement and the `/worktree` notice, and never written to the console.
31
+ Ordinary non-worktree launches do not take these snapshots.
32
+
33
+ The claim lives only in the creating process and is not written to the
34
+ manifest. Removal from another session, or after this process restarts,
35
+ reports nothing. Extension reload inside the same process keeps the in-memory
36
+ claim.
37
+
38
+ On explicit removal, core checks whether this process holds a claim whose
39
+ checkout is the removed source repository. If not, removal makes no report
40
+ calls at all. Otherwise the Herdr surface reads the source once more, after
41
+ the checkout is removed and immediately before any sentence. It may tell the
42
+ parent:
43
+
44
+ `wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX`
45
+
46
+ only when that read shows the workspace untouched: the default label equal to
47
+ the repository name (both present), not focused, one tab, not a linked
48
+ worktree, the claimed `repo_key`, one pane, the recorded terminal, and `cwd`
49
+ and `foreground_cwd` at the recorded checkout. No other workspace may be open
50
+ for the repository; a listed row with an open workspace and no boolean
51
+ `is_linked_worktree` blocks the sentence too. Any missing or wrong-typed field
52
+ a check uses means the workspace is not untouched, so removal says nothing
53
+ about it. Shell idleness and child processes are not checked: the sentence is
54
+ advisory and hedged, and the extra reads would double the removal cost
55
+ without making a close safe.
56
+
57
+ The surface method is `SurfaceProvider.reportOpenedPrimaryWorkspace`. Herdr
58
+ implements it. Other surfaces return no report. Core does not import Herdr.
59
+
60
+ A failed report is a warning. The checkout removal still succeeds. Nothing in
61
+ this path polls, retries, or sleeps.
62
+
63
+ ### Blocking time
64
+
65
+ A worktree launch adds at most three synchronous Herdr calls, each bounded at
66
+ 3 seconds:
67
+
68
+ 1. `herdr worktree list --cwd` before create
69
+ 2. `herdr worktree list --cwd` after create, only when the before snapshot
70
+ succeeded and showed no primary workspace
71
+ 3. `herdr pane list --workspace` for that workspace's terminal, only when the
72
+ after snapshot shows a new primary workspace with a repo key and checkout
73
+ path
74
+
75
+ If each runs to its timeout, the extra blocking time is 9 seconds. A failed
76
+ before-snapshot skips the later two. `herdr worktree create` itself is an
77
+ existing call and is not given a new timeout here.
78
+
79
+ Explicit removal adds at most three asynchronous calls, each bounded at
80
+ 3 seconds, and only when this process holds a claim for the removed source
81
+ repository:
82
+
83
+ 1. `herdr worktree list --cwd`
84
+ 2. `herdr workspace get`
85
+ 3. `herdr pane list --workspace`
86
+
87
+ If each runs to its timeout, removal takes up to 9 more seconds of wall time.
88
+ The calls do not block the event loop. A failed or decisive call stops the
89
+ rest. These calls are in addition to the existing cleanup Git and Herdr calls,
90
+ which stay at 30 seconds each.
91
+
92
+ ## Rejected alternatives
93
+
94
+ - **Automatic close from the untouched-workspace heuristic.** One tab, one
95
+ pane, the same terminal id, cwd at the checkout, and the default label, even
96
+ with an idle shell and no children, cannot tell whether the user worked in
97
+ the workspace. `git
98
+ log`, a test run, and `cd` back to the checkout leave that same picture, and
99
+ closing it would destroy the scrollback.
100
+ - **Pane fingerprinting.** Reading `herdr pane read --source recent` at claim
101
+ time and requiring the same text before a close would catch some commands
102
+ the heuristic misses. It is not clearly better than reporting. Prompt
103
+ redraws, hooks, and Herdr's own startup change the text without a user
104
+ command; a cleared screen looks untouched; a read can fail; and the gap
105
+ between the confirming read and a close is still a race. A mistaken close
106
+ is destructive. A mistaken report is a command the user can ignore.
107
+ - **Trusting the manifest after restart.** The creating process is the only
108
+ observer of the before/after snapshots. A later process would be acting on
109
+ a claim it did not make.
110
+
111
+ ## Consequences
112
+
113
+ Cwd containment, branch retention, and the rejection of automatic reaping in
114
+ ADR-0011 are unchanged. A misattributed claim can name a workspace this create
115
+ did not open, so the sentence says the workspace "appears" to have been opened
116
+ and asks the user to close it only if they have not used it. The message does
117
+ not close it. There is no second re-check: the single set of reads is taken
118
+ immediately before the sentence, and a workspace that changes after those
119
+ reads can still be named. The sentence is still only a suggestion.
120
+
121
+ See the [operating guide](../worktree-subagents.md#cleanup).
@@ -48,11 +48,12 @@ For a worktree launch:
48
48
  - `worktree.branch` is a new, unique branch name. Git/Herdr rejects a branch that cannot be created or is already checked out elsewhere.
49
49
  - `worktree.base` may be any revision that resolves to a commit in the source repository. It defaults to committed `HEAD`.
50
50
  - The extension resolves `base` to an exact SHA, writes an ownership manifest, then calls `herdr worktree create --no-focus`.
51
+ - Herdr groups the new worktree with the source repository's primary workspace and opens one when none is open. The extension reads `herdr worktree list --cwd` before and after creation, and `herdr pane list` for the opened workspace's terminal, only to record a claim in this process. Each of those reads has a 3-second timeout. A worktree launch runs at most three of them; if each runs to its timeout, that is 9 seconds on top of create. A workspace you already had open is not claimed. An unreadable, timed-out, or incomplete snapshot claims nothing and is reported as a launch diagnostic in the `subagent` acknowledgement or `/worktree` notice, never on the console. A failed before-snapshot skips the later reads. Ordinary pane launches do not make these calls. The claim is not written to the manifest and is not kept after this process exits.
51
52
  - The child starts at the root of the returned worktree, in that workspace's retained root pane and newly owned `Agents` tab. Finishing Pi returns to the interactive checkout shell; it does not close the root pane. A successful launch from a linked checkout does not itself authorize cleanup there: cleanup checks the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at the principal checkout or an ancestor containing it, then use normal explicit cleanup; changing directories inside an existing Pi session does not change its session cwd.
52
53
  - 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.
53
54
  - Worktree creation does not steal terminal focus.
54
55
 
55
- 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.
56
+ 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 Pi startup is confirmed: a Pi process in the root pane's Herdr foreground list must have the worktree cwd and, in its launch-time environment (`/proc/<pid>/environ`), `PI_HERDR_AGENTS_SESSION` naming the forked session file. An unreadable environment (for example, a process that is exiting) never confirms startup. Command-line text is never evidence, because Pi rewrites its process title and Herdr then reports only `pi`. The confirmation reads Linux `/proc`; on other hosts it fails within its bounded wait and the launch-failure retention below applies. 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.
56
57
 
57
58
  `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.
58
59
 
@@ -235,7 +236,7 @@ Dirty work requires an explicit `--preserve` or `preserve: true`. This stages al
235
236
 
236
237
  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.
237
238
 
238
- 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.
239
+ Open worktree workspaces are removed through Herdr. That removal never closes the source repository's primary workspace. If this process recorded that worktree creation opened it, removal reads the workspace after the checkout is removed and may report `wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX` only when those reads, taken immediately before that sentence, show the workspace untouched: the default label equal to the repository name, not focused, one tab, not a linked worktree, the claimed `repo_key`, one pane, the recorded terminal, and both `cwd` and `foreground_cwd` at the recorded checkout, with no other workspace open for the repository. A listed row with an open workspace but no boolean `is_linked_worktree` blocks the sentence. A missing or wrong-typed `label`, `repo_name`, `focused`, `tab_count`, `foreground_cwd`, `terminal_id`, or any other field a check uses means the workspace is not untouched, so removal says nothing about it. Shell idleness and child processes are not checked; the sentence is a hedged suggestion, not a close. The before/after list can attribute a workspace that you or another session opened during a long checkout, because Herdr does not say which workspace create opened; the message does not close anything, so a wrong id is only a suggestion. Claims live only in the creating process: removal from another session, or after a restart, reports nothing. The report's longest path is three asynchronous calls — `worktree list`, `workspace get`, and `pane list`, 3 seconds each — 9 seconds of wall time if every call runs to its timeout, without blocking the event loop. A failed call stops the rest. With no claim for the removed source repository, removal adds none of these calls. 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.
239
240
 
240
241
  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. Session startup skips worktree inventory to avoid blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. Outside Herdr, explicit inventory tools remain available and report unavailable inspection as unknown. Unsupported platforms or failed global process enumeration block removal; individual visibility gaps are disclosed warnings, never proof that an orphan is idle.
241
242
 
@@ -246,7 +247,8 @@ This first version intentionally does not provide:
246
247
  - automatic push, PR creation, merge, or cherry-pick
247
248
  - automatic worktree or branch removal
248
249
  - worktree-aware `subagent_resume`
249
- - durable restart reconciliation
250
+ - durable restart reconciliation (an opened-primary-workspace claim does not survive process restart)
251
+ - automatic close of the source repository's primary workspace
250
252
  - dependency DAG scheduling or merge queues
251
253
  - stacked-branch management
252
254
  - multi-repository workspaces
@@ -316,8 +316,18 @@ export function createSubagentActivityRecorder(params: {
316
316
  const runningChildId = params.runningChildId?.trim();
317
317
  const activityFile = params.activityFile?.trim();
318
318
  if (!runningChildId || !activityFile) return createNoopRecorder();
319
+ return createFileActivityRecorder(
320
+ runningChildId,
321
+ activityFile,
322
+ params.now ?? (() => Date.now()),
323
+ );
324
+ }
319
325
 
320
- const now = params.now ?? (() => Date.now());
326
+ function createFileActivityRecorder(
327
+ runningChildId: string,
328
+ activityFile: string,
329
+ now: () => number,
330
+ ): SubagentActivityRecorder {
321
331
  const createdAt = now();
322
332
  const activity: SubagentActivityState = {
323
333
  version: 1,