pi-herdr-agents 3.0.0 → 3.0.2

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/CHANGELOG.md CHANGED
@@ -7,10 +7,30 @@ 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.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v2.0.5...v3.0.0)
10
+ ## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2)
11
11
 
12
12
  ### Merged
13
13
 
14
+ - fix(subagents): resolve submodule sources and refresh fallback context [`#76`](https://github.com/giuseppecrj/pi-herdr-agents/pull/76)
15
+
16
+ ## [v3.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1) - 2026-10-06
17
+
18
+ ### Merged
19
+
20
+ - docs: README accuracy and install confidence for 3.0.0 [`#75`](https://github.com/giuseppecrj/pi-herdr-agents/pull/75)
21
+
22
+ ### Commits
23
+
24
+ - docs: README for 3.0.0: pstack ships poteto-mode not a role, safety and uninstall, contents, agent pointer [`7ed9969`](https://github.com/giuseppecrj/pi-herdr-agents/commit/7ed99693b1a2d25137d4ba2430f23255923ce3ea)
25
+ - chore: release v3.0.1 [`ba349db`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ba349db4a4280a39b70a067fb62e7816f9a2061f)
26
+ - docs: hero image uses generic agent labels instead of removed role names [`70ad6f0`](https://github.com/giuseppecrj/pi-herdr-agents/commit/70ad6f04d492f58e04062e01c1a529ddd1708431)
27
+ - docs: address review: full write footprint, worktree cleanup before uninstall, skill table delimiter [`ababee3`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ababee3a3f18d2112ec8959303986e1a08e3dc2a)
28
+
29
+ ## [v3.0.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v2.0.5...v3.0.0) - 2026-10-06
30
+
31
+ ### Merged
32
+
33
+ - Release 3.0.0 [`#74`](https://github.com/giuseppecrj/pi-herdr-agents/pull/74)
14
34
  - Add subagent_cancel for ordinary managed subagents [`#73`](https://github.com/giuseppecrj/pi-herdr-agents/pull/73)
15
35
  - Add the pi-herdr-agents operating skill [`#72`](https://github.com/giuseppecrj/pi-herdr-agents/pull/72)
16
36
  - Conditional task-model writes with expectedConfigRevision [`#71`](https://github.com/giuseppecrj/pi-herdr-agents/pull/71)
@@ -36,7 +56,9 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
36
56
  - docs: add subagent-lifecycle host skill [`bfce217`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bfce2177984a7b5fededc2b3a7fe255cc966e46c)
37
57
  - fix: keep a failed SIGTERM to a live identity unconfirmed and drop overdue provider answers [`4a3992d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4a3992d865eeb0a11a23350ff4de92dd892e30c2)
38
58
  - docs: fix host operating skill review findings and packaging wording [`bbeb580`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bbeb580a5fb38d780209babcd8b5f7848b8543e2)
59
+ - docs: add 3.0.0 release notes and clarify bare-spawn systemPrompt delivery [`b8b23a0`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b8b23a0893d5d9e6176703305838e24bc91391e8)
39
60
  - docs(skill): document subagent_cancel in host operating guide [`7a3508c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/7a3508cfe886b5f7932bdf77d3d3a516d2a7dfcd)
61
+ - chore: release v3.0.0 [`b383574`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b383574778aad1498c12ddeb48af6dd32f383856)
40
62
  - test(integration): scope the untouched-pane check to the runner's own workspace [`fa483ea`](https://github.com/giuseppecrj/pi-herdr-agents/commit/fa483ea918f862da35162ca4d73ae6191288fa99)
41
63
  - docs: scope the pack-neutral claim to roles and workflows [`e75bea8`](https://github.com/giuseppecrj/pi-herdr-agents/commit/e75bea84a79218c9367ea5a165cbab161e2fb217)
42
64
  - fix: declare pi.skills so the lifecycle skill actually loads [`5b45e7f`](https://github.com/giuseppecrj/pi-herdr-agents/commit/5b45e7fb17080fe7b5d1d7e675d234fc140ed144)
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Pi Herdr Agents
2
2
 
3
- ![Pi Herdr Agents: parallel Pi agents running asynchronously in dedicated Herdr panes and managed worktrees.](https://raw.githubusercontent.com/giuseppecrj/pi-herdr-agents/main/docs/assets/pi-herdr-agents-gallery.png)
3
+ ![Pi Herdr Agents: a parent Pi session delegating to parallel child agents in dedicated Herdr panes, an isolated worktree and a retained session, with a live status widget.](https://raw.githubusercontent.com/giuseppecrj/pi-herdr-agents/main/docs/assets/pi-herdr-agents-gallery.png)
4
+
5
+ > **Agents:** the tool list is in [What's Included](#whats-included) and the operating guide is [`skills/pi-herdr-agents/SKILL.md`](skills/pi-herdr-agents/SKILL.md). Contributors: read [`AGENTS.md`](AGENTS.md).
4
6
 
5
7
  Asynchronous subagents for [Pi](https://github.com/earendil-works/pi), running exclusively in [Herdr](https://herdr.dev).
6
8
 
@@ -48,36 +50,24 @@ pi
48
50
 
49
51
  Restart or `/reload` Pi after installation. Review package source before installing any Pi package.
50
52
 
51
- ## Release notes
53
+ > **Upgrading from 2.x?** Bundled roles and several commands moved out. See [3.0.0 release notes](#300).
52
54
 
53
- [`CHANGELOG.md`](CHANGELOG.md) is generated from Git history on each release.
54
- Hand-written upgrade notes for breaking releases live here.
55
+ ## Contents
55
56
 
56
- ### 3.0.0
57
+ - [Requirements](#requirements), [Install](#install), [Safety and uninstall](#safety-and-uninstall)
58
+ - [Quick start](#quick-start), [Release notes](#release-notes), [How it works](#how-it-works)
59
+ - [What's Included](#whats-included): tools, commands, the operating skill, role packs
60
+ - [Async Subagent Flow](#async-subagent-flow): status, configuration, model routing
61
+ - [Spawning Subagents](#spawning-subagents), [Persistent specialists](#persistent-specialists), [Interrupting](#interrupting-a-running-subagent), [Cancelling](#cancelling-a-running-subagent)
62
+ - [The `/worktree` Workflow](#the-worktree-workflow), [Custom Agents](#custom-agents), [Tool Access Control](#tool-access-control)
63
+ - [Development](#development), [License](#license)
57
64
 
58
- 3.0.0 makes the host pack-neutral. It also adds conditional task-model writes
59
- (`expectedConfigRevision`, an advisory lock, and the reported `configRevision`),
60
- the `pi-herdr-agents` operating skill, and `subagent_cancel`.
65
+ ## Safety and uninstall
61
66
 
62
- **Breaking changes.** The package no longer ships the seven former bundled roles,
63
- `/plan` and its plan skill, or `/skill:orchestrate`; these moved to optional role
64
- packs. `/iterate`, `/btw`, and `/btw-close` were removed without replacement.
65
- `roles.bundled` is now a deprecated no-op. A named launch of a role that no
66
- definition or installed pack supplies now fails before Herdr creates a pane or
67
- worktree.
68
-
69
- **Migration.**
70
-
71
- - Install `pi-herdr-roles` for the six generic roles (`scout`, `planner`,
72
- `worker`, `reviewer`, `adversarial-reviewer`, `visual-tester`), `/plan`, and
73
- `/skill:orchestrate`.
74
- - Install `pi-herdr-pstack` for the `poteto` role.
75
- - Remove `roles.bundled` from `$PI_CODING_AGENT_DIR/herdr-agents/config.json`.
76
- - Alternatively, copy a former role's definition into `.pi/agents/` or the
77
- global agents directory to keep it without a pack.
78
-
79
- See [Migrating from bundled roles](#migrating-from-bundled-roles) for the full
80
- mapping and pack-compatibility notes.
67
+ - Child agents are real Pi processes running with your user account's permissions, inside Herdr panes. Worktrees isolate Git checkouts, not processes or permissions.
68
+ - The extension creates Herdr panes, tabs and managed worktrees only when a launch asks for them. It never pushes, merges, opens pull requests, deletes branches or removes worktrees on its own; cleanup is an explicit parent action ([worktree cleanup](#explicit-worktree-cleanup)).
69
+ - What it writes: `$PI_CODING_AGENT_DIR/herdr-agents/config.json` (only through `/subagents-init` or the writer tool, never on startup); per-launch artifacts under the parent session's `artifacts/<session-id>/` directory beside Pi's session store, which hold the child's full task text and any `systemPrompt` as Markdown files, activity snapshots and worktree manifests; and the child's own Pi session file. A managed worktree that carries its own `.pi/agent` directory receives that child's session inside the checkout. Treat task text as potentially sensitive when you share or inspect those files.
70
+ - To uninstall: first list and remove any retained worktrees while the extension is still loaded (`/worktree list`, then `/worktree remove <target>`), because those commands leave with the package. Then run `pi remove npm:pi-herdr-agents`, and delete `$PI_CODING_AGENT_DIR/herdr-agents/` and the `artifacts/` directories above if you no longer want the configuration and launch records.
81
71
 
82
72
  ## Quick start
83
73
 
@@ -117,6 +107,38 @@ subagent({ name: "Auth summary", model: "<provider>/<fast-tier-id>", thinking: "
117
107
 
118
108
  Use ordinary panes for read-only agents. A single or sequential writer can work in the parent checkout; give each parallel independent writing agent a unique managed worktree. The parent acts as coordinator: decompose work, give each child one bounded outcome with its goal, allowed files, verification, and commit instruction, and keep dependent writes sequential. Children are leaves by default; the parent owns integration and final verification. See [Worktree subagents](docs/worktree-subagents.md).
119
109
 
110
+ ## Release notes
111
+
112
+ [`CHANGELOG.md`](CHANGELOG.md) is generated from Git history on each release.
113
+ Hand-written upgrade notes for breaking releases live here.
114
+
115
+ ### 3.0.0
116
+
117
+ 3.0.0 makes the host pack-neutral. It also adds conditional task-model writes
118
+ (`expectedConfigRevision`, an advisory lock, and the reported `configRevision`),
119
+ the `pi-herdr-agents` operating skill, and `subagent_cancel`.
120
+
121
+ **Breaking changes.** The package no longer ships the seven former bundled roles,
122
+ `/plan` and its plan skill, or `/skill:orchestrate`; these moved to optional role
123
+ packs. `/iterate`, `/btw`, and `/btw-close` were removed without replacement.
124
+ `roles.bundled` is now a deprecated no-op. A named launch of a role that no
125
+ definition or installed pack supplies now fails before Herdr creates a pane or
126
+ worktree.
127
+
128
+ **Migration.**
129
+
130
+ - Install `pi-herdr-roles` for the six generic roles (`scout`, `planner`,
131
+ `worker`, `reviewer`, `adversarial-reviewer`, `visual-tester`), `/plan`, and
132
+ `/skill:orchestrate`.
133
+ - Install `pi-herdr-pstack` for the `poteto-mode` methodology skill and command.
134
+ It ships no named roles; its delegates are bare.
135
+ - Remove `roles.bundled` from `$PI_CODING_AGENT_DIR/herdr-agents/config.json`.
136
+ - Alternatively, copy a former role's definition into `.pi/agents/` or the
137
+ global agents directory to keep it without a pack.
138
+
139
+ See [Migrating from bundled roles](#migrating-from-bundled-roles) for the full
140
+ mapping and pack-compatibility notes.
141
+
120
142
  ## How it works
121
143
 
122
144
  ![Pi Herdr Agents lifecycle: spawn a child, run it in Herdr, supervise live state, and deliver one bounded result to the parent.](https://raw.githubusercontent.com/giuseppecrj/pi-herdr-agents/main/docs/assets/async-subagent-lifecycle.png)
@@ -172,6 +194,10 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
172
194
  | `subagent_resume` | Resume a previous Pi-backed sub-agent session in a new ordinary pane (async) |
173
195
  | `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` |
174
196
 
197
+ | Skill | Description |
198
+ | ----- | ----------- |
199
+ | `pi-herdr-agents` | Operating guide for this host: launching, supervising, interrupting, cancelling and resuming children, worktrees, persistent specialists, model routing and configuration. Loaded by agents on demand; see `skills/pi-herdr-agents/SKILL.md` |
200
+
175
201
  | Pi child-only tool | Description |
176
202
  | ---------------- | ------------------------------------------------------------------------- |
177
203
  | `caller_ping` | Ask the parent for help; ordinary children exit, persistent specialists stay alive |
@@ -233,7 +259,7 @@ modified.
233
259
  | `scout`, `planner`, `worker`, `reviewer`, `adversarial-reviewer`, `visual-tester` roles | Moved | `pi-herdr-roles` role pack |
234
260
  | `/plan` command and its plan skill | Moved | `pi-herdr-roles` |
235
261
  | `/skill:orchestrate` and its adversarial-review resources | Moved | `pi-herdr-roles` |
236
- | `poteto` role | Moved | `pi-herdr-pstack` role pack |
262
+ | `poteto` role | Removed; replaced by the `poteto-mode` skill and `/poteto-mode` command | `pi-herdr-pstack` |
237
263
  | `/iterate` | Removed, not relocated | Call `subagent({ name, task, fork: true, interactive: true })` directly |
238
264
  | `/btw`, `/btw-close` | Removed, not relocated | None |
239
265
  | `roles.bundled` setting | Deprecated no-op | Remove it from `config.json` |
@@ -599,10 +625,17 @@ example `provider/preferred, provider/fallback`. The tool argument also accepts
599
625
  candidate before launch, then launches later candidates only after the selected
600
626
  child settles with a provider/agent error. Pi owns any automatic transient
601
627
  retrying inside that child; the extension does not infer retry counts or
602
- permanence from the error text. A completed child result, including a negative
603
- task result, never switches models. Completion metadata reports the requested
604
- candidate, every attempted candidate, the model actually used, and each raw
605
- model failure in attempt order when fallbacks are tried.
628
+ permanence from the error text. A later candidate that launches after a parent
629
+ `/reload`, `/new`, `/resume`, or `/fork` uses the live parent session for its
630
+ artifacts and lineage, as completion delivery does. Its candidate list and
631
+ thinking level stay those of the original call. The original parent directory
632
+ and process directory remain the bases for directory resolution. Role files are
633
+ read again for each attempt, so a changed role `cwd` can redirect a fallback
634
+ when the tool call did not specify `cwd`. If no live parent context is available,
635
+ that candidate fails without launching. A completed child result, including a
636
+ negative task result, never switches models. Completion metadata reports the
637
+ requested candidate, every attempted candidate, the model actually used, and
638
+ each raw model failure in attempt order when fallbacks are tried.
606
639
 
607
640
  A catalog-listed model and configured authentication do not prove that the
608
641
  active provider account can use that model. Providers may reject an account /
@@ -839,7 +872,7 @@ is no other parent/child protocol.
839
872
 
840
873
  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.
841
874
 
842
- 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. 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.
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.
843
876
 
844
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.
845
878
 
@@ -1250,4 +1283,4 @@ This package builds on earlier open-source work by [HazAT/pi-interactive-subagen
1250
1283
 
1251
1284
  ## License
1252
1285
 
1253
- MIT. Copyright notice retained from the upstream lineage (`HazAT`).
1286
+ MIT, see [LICENSE](LICENSE). Copyright notice retained from the upstream lineage (`HazAT`).
@@ -223,6 +223,8 @@ The equivalent tools are `worktree_list({})` and `worktree_remove({ target, pres
223
223
 
224
224
  Authorization uses **cwd containment**: the canonical source repository root must equal or descend from the canonical session cwd. For linked checkouts, this means the canonical principal/source repository under the invoking parent session's cwd; a successful launch does not itself authorize cleanup, and authorization does not use `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. 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.
225
225
 
226
+ A source repository can be a Git submodule. Its shared Git directory lives under the superproject's `.git/modules`, so cleanup reads `core.worktree` through `git config`, which also follows included configuration files. Cleanup then requires the same top-level and common-directory proofs as for any other source. If the read or a proof fails, the entry is unknown and cannot be removed. Initialized submodules inside a managed checkout still block removal.
227
+
226
228
  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.
227
229
 
228
230
  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.
@@ -11,7 +11,7 @@ import {
11
11
  unlinkSync,
12
12
  } from "node:fs";
13
13
  import { homedir } from "node:os";
14
- import { dirname, isAbsolute, join, relative, sep } from "node:path";
14
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
15
  import { isRecord, type JsonObject } from "../core/config/type-guards.ts";
16
16
  import type { SurfaceProvider } from "../core/surface-provider.ts";
17
17
  import type {
@@ -245,7 +245,19 @@ function resolveSource(path: string): string {
245
245
  "--git-common-dir",
246
246
  ]).trim(),
247
247
  );
248
- const root = realpathSync(dirname(common));
248
+ // A submodule's common directory lives under the superproject's
249
+ // .git/modules; only core.worktree, including included config, names its checkout.
250
+ const configured = git(common, [
251
+ `--git-dir=${common}`,
252
+ "config",
253
+ "--default",
254
+ "",
255
+ "--get",
256
+ "core.worktree",
257
+ ]).trim();
258
+ const root = realpathSync(
259
+ configured ? resolve(common, configured) : dirname(common),
260
+ );
249
261
  if (realpathSync(git(root, ["rev-parse", "--show-toplevel"]).trim()) !== root)
250
262
  throw new Error("Cannot prove source repository root");
251
263
  if (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-herdr-agents",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Asynchronous Pi subagents in Herdr, with optional isolated Git worktrees",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1950,10 +1950,6 @@ function startWidgetRefresh() {
1950
1950
  writeGlobalSlot(WIDGET_INTERVAL_KEY, widgetInterval);
1951
1951
  }
1952
1952
 
1953
- /**
1954
- * Normalize each actual attempt against its invocation context. Role files may
1955
- * change between attempts; the validated runtime candidates do not.
1956
- */
1957
1953
  function normalizePiAttempt(
1958
1954
  params: typeof SubagentParams.static,
1959
1955
  ctx: {
@@ -1975,6 +1971,7 @@ function normalizePiAttempt(
1975
1971
  options: {
1976
1972
  id: string;
1977
1973
  controlTaskId: string;
1974
+ origin: Pick<PiLaunchSnapshot["parent"], "cwd" | "invocationCwd">;
1978
1975
  },
1979
1976
  ): PiAttemptSnapshot {
1980
1977
  const agentDefs = params.agent
@@ -1999,15 +1996,19 @@ function normalizePiAttempt(
1999
1996
  const taskId = randomUUID();
2000
1997
  const parentSessionFile = ctx.sessionManager.getSessionFile();
2001
1998
  if (!parentSessionFile) throw new Error("No session file");
1999
+ const snapshot = launchSnapshot(ctx, parentThinking);
2002
2000
 
2003
2001
  return {
2004
- snapshot: launchSnapshot(ctx, parentThinking),
2002
+ snapshot: {
2003
+ ...snapshot,
2004
+ parent: { ...snapshot.parent, ...options.origin },
2005
+ },
2005
2006
  task: {
2006
2007
  id: options.controlTaskId,
2007
2008
  name: params.name,
2008
2009
  prompt: params.task,
2009
2010
  role: params.agent ?? "",
2010
- cwd: ctx.cwd,
2011
+ cwd: options.origin.cwd,
2011
2012
  worktree: params.worktree ?? undefined,
2012
2013
  session: { mode: resolveEffectiveSessionMode(params, agentDefs) },
2013
2014
  behavior: {
@@ -2792,20 +2793,24 @@ export default function subagentsExtension(
2792
2793
  );
2793
2794
  runtime.latestCtx = ctx;
2794
2795
  const id = randomUUID();
2795
- const first = normalizePiAttempt(params, ctx, parentThinking, {
2796
+ const attempt = {
2796
2797
  id,
2797
2798
  controlTaskId: id,
2798
- });
2799
+ origin: { cwd: ctx.cwd, invocationCwd: process.cwd() },
2800
+ };
2801
+ const first = normalizePiAttempt(params, ctx, parentThinking, attempt);
2799
2802
  const [plan, ...fallbacks] = runtimePlans;
2800
2803
  if (!plan) throw new Error("No resolved runtime plans");
2801
2804
  const input: PiLaunchInput = {
2802
2805
  ...first,
2803
2806
  plans: [plan, ...fallbacks],
2804
- prepareAttempt: () =>
2805
- normalizePiAttempt(params, ctx, parentThinking, {
2806
- id,
2807
- controlTaskId: id,
2808
- }),
2807
+ // Fallbacks outlive this tool call; session replacement invalidates ctx.
2808
+ prepareAttempt: () => {
2809
+ const live = runtime.latestCtx;
2810
+ if (!live)
2811
+ throw new Error("No live parent context for the fallback launch");
2812
+ return normalizePiAttempt(params, live, parentThinking, attempt);
2813
+ },
2809
2814
  };
2810
2815
  const session = runtime.session!;
2811
2816
  const handle = await session.spawnPi(input);