pi-agent-replicate 0.0.0-stage → 1.4.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.
Files changed (82) hide show
  1. package/AGENTS.md +27 -0
  2. package/CHANGELOG.md +152 -0
  3. package/CONTEXT.md +63 -0
  4. package/LICENSE +21 -0
  5. package/README.md +68 -2
  6. package/RELEASING.md +132 -0
  7. package/assets/banner.png +0 -0
  8. package/config.json.example +6 -0
  9. package/docs/README.md +23 -0
  10. package/docs/adr/0003-installable-role-packs.md +29 -0
  11. package/docs/adr/0008-adopt-pi-only-subagent-execution.md +22 -0
  12. package/docs/adr/0009-durable-recursive-subagent-ownership.md +23 -0
  13. package/docs/adr/0010-public-subagent-tree-registry.md +16 -0
  14. package/docs/configuration.md +14 -0
  15. package/docs/development.md +27 -0
  16. package/docs/public-subagent-tree-api.md +58 -0
  17. package/docs/retained-roles.md +71 -0
  18. package/docs/settled-lifecycle-contract.md +71 -0
  19. package/docs/worktree-subagents.md +58 -0
  20. package/examples/role-pack/extension.ts +18 -0
  21. package/examples/role-pack/package.json +16 -0
  22. package/examples/role-pack/roles/example-reviewer.md +12 -0
  23. package/index.d.ts +116 -0
  24. package/index.js +1064 -0
  25. package/index.ts +155 -0
  26. package/package.json +70 -4
  27. package/pi-extension/subagents/activity.js +575 -0
  28. package/pi-extension/subagents/activity.ts +980 -0
  29. package/pi-extension/subagents/completion.js +184 -0
  30. package/pi-extension/subagents/completion.ts +229 -0
  31. package/pi-extension/subagents/delivery-drain.js +15 -0
  32. package/pi-extension/subagents/delivery-drain.ts +20 -0
  33. package/pi-extension/subagents/delivery-files.js +86 -0
  34. package/pi-extension/subagents/delivery-files.ts +105 -0
  35. package/pi-extension/subagents/delivery-log.js +51 -0
  36. package/pi-extension/subagents/delivery-log.ts +51 -0
  37. package/pi-extension/subagents/delivery-payload.ts +55 -0
  38. package/pi-extension/subagents/delivery.ts +219 -0
  39. package/pi-extension/subagents/execution-owner.js +30 -0
  40. package/pi-extension/subagents/execution-owner.ts +38 -0
  41. package/pi-extension/subagents/herdr.js +645 -0
  42. package/pi-extension/subagents/herdr.ts +927 -0
  43. package/pi-extension/subagents/index.ts +3400 -0
  44. package/pi-extension/subagents/launch.js +774 -0
  45. package/pi-extension/subagents/launch.ts +1124 -0
  46. package/pi-extension/subagents/lifecycle.js +477 -0
  47. package/pi-extension/subagents/lifecycle.ts +582 -0
  48. package/pi-extension/subagents/lineage.js +806 -0
  49. package/pi-extension/subagents/lineage.ts +841 -0
  50. package/pi-extension/subagents/replica-background.js +32 -0
  51. package/pi-extension/subagents/replica-background.ts +38 -0
  52. package/pi-extension/subagents/replica-branch.ts +45 -0
  53. package/pi-extension/subagents/replica-directory-settings.ts +51 -0
  54. package/pi-extension/subagents/replica-history.js +22 -0
  55. package/pi-extension/subagents/replica-history.ts +21 -0
  56. package/pi-extension/subagents/replica-work.js +326 -0
  57. package/pi-extension/subagents/replica-work.ts +336 -0
  58. package/pi-extension/subagents/replica-worker.js +74 -0
  59. package/pi-extension/subagents/replica-worker.ts +71 -0
  60. package/pi-extension/subagents/replication.ts +99 -0
  61. package/pi-extension/subagents/result-delivery-queue.ts +33 -0
  62. package/pi-extension/subagents/role-catalog.js +123 -0
  63. package/pi-extension/subagents/role-catalog.ts +24 -0
  64. package/pi-extension/subagents/runtime-routing.js +154 -0
  65. package/pi-extension/subagents/runtime-routing.ts +239 -0
  66. package/pi-extension/subagents/session-binding.js +346 -0
  67. package/pi-extension/subagents/session-binding.ts +339 -0
  68. package/pi-extension/subagents/session-work.js +219 -0
  69. package/pi-extension/subagents/session-work.ts +167 -0
  70. package/pi-extension/subagents/session.js +318 -0
  71. package/pi-extension/subagents/session.ts +448 -0
  72. package/pi-extension/subagents/settled-contract.js +47 -0
  73. package/pi-extension/subagents/settled-contract.ts +175 -0
  74. package/pi-extension/subagents/source-state.js +180 -0
  75. package/pi-extension/subagents/source-state.ts +184 -0
  76. package/pi-extension/subagents/status.ts +493 -0
  77. package/pi-extension/subagents/subagent-done.js +805 -0
  78. package/pi-extension/subagents/subagent-done.ts +878 -0
  79. package/pi-extension/subagents/terminal.js +111 -0
  80. package/pi-extension/subagents/terminal.ts +208 -0
  81. package/pi-extension/subagents/watchdog.js +122 -0
  82. package/pi-extension/subagents/watchdog.ts +171 -0
@@ -0,0 +1,27 @@
1
+ # Development
2
+
3
+ ```bash
4
+ npm test
5
+ npm run lint
6
+ npm pack --dry-run
7
+ npx tsc --noEmit index.ts
8
+ node --check index.js
9
+ git diff --check
10
+ ```
11
+
12
+ Run selected isolated real-Pi and Herdr cases from inside Herdr:
13
+
14
+ ```bash
15
+ npm run test:integration -- test/integration/replication.test.ts
16
+ ```
17
+
18
+ For a manual TUI check with a deterministic provider, set `PI_INTEGRATION_TUI_DIR` to a fresh ignored directory and `PI_INTEGRATION_IDE_ENTRY` to the current IDE source entry. Run only this case with the intended Pi binary on `PATH`:
19
+
20
+ ```bash
21
+ node --experimental-strip-types test/integration/run-isolated.ts \
22
+ --test-name-pattern='renders an active isolated replica' test/integration/replication.test.ts
23
+ ```
24
+
25
+ The case reloads Pi and records the loaded IDE in `runtime.json`. The applied and failed cases each write to their own subdirectory. Use its `pane.json` to attach a read-only observer to the private Herdr endpoint. Capture the active pane, then create `release-child` in the inspection directory. Capture the delivered result, then create `finish` so the runner can clean up. Both observation gates time out after five minutes. Do not use the user's persistent Herdr server.
26
+
27
+ See [RELEASING.md](../RELEASING.md) for release checks.
@@ -0,0 +1,58 @@
1
+ # Public subagent tree API
2
+
3
+ `pi-agent-replicate` exports a registration-free API for installed Pi extensions running in Herdr. Importing the package root does not register model-facing tools.
4
+
5
+ ## Create and launch
6
+
7
+ The model-facing `replicate` tool uses source-snapshot worktrees and host-owned joins. This separate API still supports named roles, bare children and managed worktrees. Its ownership and runtime contracts are unchanged.
8
+
9
+ ```ts
10
+ import {
11
+ createSubagentTree,
12
+ type SubagentTreeResult,
13
+ } from "pi-agent-replicate";
14
+
15
+ const tree = createSubagentTree({ pi, ctx, metadata: { requestId: "search-1" } });
16
+ const child = await tree.launchChild({
17
+ parentId: tree.callerId,
18
+ name: "search-router",
19
+ task: "Find the requested records.",
20
+ tools: ["read"],
21
+ });
22
+ const childResult = await child.result;
23
+ const result: SubagentTreeResult = await tree.result;
24
+ ```
25
+
26
+ A newly created root caller launches with `parentId: tree.callerId`. An attached child branch launches its own descendants with `parentId: handle.ownerId`, where `handle` is that branch's own handle:
27
+
28
+ ```ts
29
+ const handle = attachSubagentTree({ pi, ctx });
30
+ const grandchild = await handle.launchChild({
31
+ parentId: handle.ownerId,
32
+ name: "detail-search",
33
+ task: "Search the narrower records.",
34
+ tools: ["read"],
35
+ });
36
+ ```
37
+
38
+ The package owns `callerId`, `treeId`, `ownerId`, node IDs, parent IDs, and the opaque `ownershipToken`. Consumer metadata is JSON-compatible and limited to 64 KiB of serialized UTF-8 bytes. Returned metadata, results, and the `children` map are read-only. Child result metadata carries those immutable identities plus the consumer value.
39
+
40
+ Launching accepts explicit `cwd` and `worktree` options and reuses the managed-worktree contract described in [Worktree subagents](worktree-subagents.md). Results carry a `SubagentWorktree` with the created path, branch, base, and observed Git state. The package keeps every worktree workspace, including after failures; the caller owns review, integration, and cleanup.
41
+
42
+ ## Reattach and fork
43
+
44
+ Internal Pi launches carry their child lifecycle binding in the session, so moving a child does not depend on copying a `.lineage.json` sidecar. This does not replace a consumer's explicit callback reattachment described below; automatic model-facing result delivery and consumer callback delivery are separate contracts.
45
+
46
+ A consumer calls `attachSubagentTree` from every fresh `session_start` with the current `pi`, `ctx`, and caller credentials, then calls `bindFinalCallback()` on the returned handle. For `/reload`, `/new`, and `/resume`, this replaces the stale session references. For `/fork`, pass `forkLineage: { previousSessionFile }`. The API checks the previous binding and the new session header's `parentSession` before replacing the active context. A failed check leaves the old context untouched.
47
+
48
+ The attached handle rebuilds its read-only `children` map. Each child keeps the same awaitable result identity, including answers that settled while the consumer was unbound. Only the original caller branch may call `bindFinalCallback`; callback bindings are versioned, and a successful callback is delivered once. A callback failure remains pending for a later binding.
49
+
50
+ ## Settlement and cancellation
51
+
52
+ `tree.result` resolves once the root has a clean or explicit-empty settled result and every queued or launched descendant has settled. A root provider error, launch failure, or unexpected abort fails the tree. Descendant errors remain in bounded lifecycle rows. Intentional Escape is not a final tree result. Node rows report actual pane evidence (`open`) and bounded session and error facts; full descendant outputs remain on child promises.
53
+
54
+ `cancel()` returns the one authoritative terminal record. If the tree already has a terminal record, `cancel()` returns that existing result and performs no further work. Otherwise it stops new reservations, waits for in-flight launches to settle, closes known descendant panes, and confirms both pane absence and captured process exit. A confirmed cancellation yields `state: "cancelled"`. A termination it cannot confirm — missing identity or surviving resources — yields `state: "failed"` with error code `cancel_termination_failed` and the retained recovery evidence. Rows inside a failed cancellation record carry conservative recovery flags, notably `open: true`; these are not verified current pane state. Late child records cannot replace a terminal record.
55
+
56
+ ## Public exports
57
+
58
+ The root exports `createSubagentTree`, `attachSubagentTree`, `SubagentTreeHandle`, `SubagentChildHandle`, launch/result/metadata types, `ForkLineage`, and the error classes `SubagentTreeError`, `SubagentTreeValidationError`, and `SubagentTreeOwnershipError`.
@@ -0,0 +1,71 @@
1
+ # Retained role launcher
2
+
3
+ This reference applies to the package-root API and retained internal launcher, not the model-facing `replicate` tool.
4
+
5
+ ## Retained runtime policy
6
+
7
+ Model and thinking are resolved independently:
8
+
9
+ 1. Use the value from the selected role's frontmatter.
10
+ 2. If the role omits it, inherit the value from the parent session.
11
+
12
+ This role policy applies only to the retained internal launcher and package-root API. `replicate` uses the parent runtime without resolving a role. Bare spawns remain supported by those retained paths. They inherit both values. Role models must be one exact authenticated `provider/model-id`. Model fallback lists and `config.json` model routing are not supported.
13
+
14
+ ## Custom roles for the retained API
15
+
16
+ The package has no built-in roles. Add roles in one of these places:
17
+
18
+ | Source | Path |
19
+ | --- | --- |
20
+ | Project | `.pi/agents/<name>.md` |
21
+ | Global | `$PI_CODING_AGENT_DIR/agents/<name>.md` (normally `~/.pi/agent/agents/<name>.md`) |
22
+ | Role pack | An installed Pi package registered through the role discovery event |
23
+
24
+ Priority is project, then global, then role pack. Duplicate role-pack names are rejected.
25
+
26
+ Example:
27
+
28
+ ```markdown
29
+ ---
30
+ name: security-reviewer
31
+ description: Reviews a bounded change for security problems
32
+ model: provider/exact-model-id
33
+ thinking: high
34
+ tools: read, bash
35
+ spawning: false
36
+ auto-exit: true
37
+ ---
38
+
39
+ Review only the requested change. Report concrete findings with file references.
40
+ ```
41
+
42
+ Useful frontmatter fields:
43
+
44
+ - `name`, `description`
45
+ - `model`, `thinking`
46
+ - `tools`, `skills`, `deny-tools`
47
+ - `spawning`, `allow-self-spawn`
48
+ - `auto-exit`, `interactive`
49
+ - `session-mode`: `standalone`, `lineage-only`, or `fork`
50
+ - `cwd`
51
+ - `system-prompt`: `append` or `replace`
52
+ - `disable-model-invocation`
53
+
54
+ Requested skills are saved in the child's conversation once. Reloading or resuming that conversation reuses the saved skills instead of adding another initialization message.
55
+
56
+ Definitions that request an external `cli` fail before Herdr creates resources.
57
+
58
+ ### Role packs
59
+
60
+ A Pi package can register one absolute Markdown file or a directory of direct `.md` children through this event:
61
+
62
+ ```typescript
63
+ pi.events.on(
64
+ "pi-herdr-subagents:roles:discover:v1",
65
+ (request: { apiVersion: number; register(path: string): void }) => {
66
+ if (request.apiVersion === 1) request.register(absoluteRolesDirectory);
67
+ },
68
+ );
69
+ ```
70
+
71
+ A role-pack extension must remove its listener on `session_shutdown`.
@@ -0,0 +1,71 @@
1
+ # Settled lifecycle contract
2
+
3
+ This document defines how child turns become one parent result. The TypeScript source for settled outcome shapes is `pi-extension/subagents/settled-contract.ts`.
4
+
5
+ ## Settled turns stay local
6
+
7
+ A settled assistant entry is still classified for child control:
8
+
9
+ - `clean`: non-empty assistant text with no final provider error.
10
+ - `empty`: a settled assistant entry with no usable text.
11
+ - `error`: `stopReason: "error"`.
12
+ - `intentional-abort`: `stopReason: "aborted"` after the parent recorded an interrupt request.
13
+ - `unexpected-abort`: `stopReason: "aborted"` without an interrupt request.
14
+
15
+ These boundaries do not publish parent results. A persistent child can settle many times while it remains open. Its earlier answers, empty turns, errors, and aborts stay in its own session.
16
+
17
+ For a replica, a clean nonempty response can record terminal intent only after the last descendant result is processed. Earlier responses stay local. A terminal provider error, Escape, an empty required summary or an unsaved close records failure, saves completion evidence and closes after descendant drain. That failure remains the terminal outcome across reload and later turns that process pending work, even if a later turn succeeds. User input does not suppress replica completion. A saved final response without settlement evidence is preserved as history, never promoted to success. Its original receives error facts and a file of all post-fork textual assistant answers, excluding inherited answers, tooling and hidden reasoning.
18
+
19
+ If history export fails, the terminal result reports that error instead of retrying completion forever. An unknown inherited-answer boundary never authorizes exporting the whole conversation. If the session file cannot be checkpointed, terminal facts and source checkpointing still proceed. The checkout stays available, integration is blocked without advertising a saved-copy continuation.
20
+
21
+ A queued self-reload is unfinished work. The acknowledgement before reload is not a terminal answer, and the stopped-replica timer stays suspended until reload continues the session or reports that it was blocked. An old runtime must not publish completion after shutdown, even if its descendant wait finishes later.
22
+
23
+ A stopped model does not imply finished session work. Replicas wait for active tools, background terminal completion messages, pending background request receipts, reported live Linux descendant PIDs and their observed children, and their own replicas. The copy processes completion messages before returning. New work invalidates an older shutdown decision, including one already waiting for descendants. The idle safety timer cannot bypass this guard. Terminal completion remains authoritative for its exit status; observing the same PID exit must not replace it with a status-less duplicate. Work receipts and process ownership are read from the finalized `tool_execution_end` result, after all `tool_result` transformations, including nested calls. A successful terminal deletion retires only that terminal's receipt because deletion suppresses its completion message. Independently tracked live processes and other work still block closure.
24
+
25
+ Search completions committed as atomic `custom_message` boundary entries drain their matching request before final settlement, even when Pi emits no `message_start`. A passive `web-search:cancelled` custom entry with `{ requestId, status: "cancelled", parentToolCallId? }` releases that request without waking a stopped model. A successful `wait_for_web_searches` also drains the known search set captured when that wait started, including searches cancelled during a provider retry. Failed waits, unrelated requests and searches launched after the wait started remain pending.
26
+
27
+ Receipt and process ownership persists for the exact session through reload. Unreported daemons, unsupported native process identities and remote jobs without completion receipts are not discoverable by this guard. It does not adopt unrelated processes or idle runtime helpers.
28
+
29
+ The settled handler returns its finalization promise. Pi must see the shutdown request before it checks that settled boundary; saving a result or starting detached finalization is not proof that the Pi process exited.
30
+
31
+ The retained non-replica lifecycle is unchanged: autonomous clean or empty boundaries can record terminal intent after the last descendant result; provider errors and aborts keep those children open.
32
+
33
+ Manual and automatic context compaction are active child work. `session_before_compact` protects the child from stale recovery until `session_compact` or `session_compact_failed` ends that state. Compaction never publishes or repeats a parent result.
34
+
35
+ ## Failed replica startup
36
+
37
+ The parent records the allocated pane's shell incarnation before starting Pi. If extension loading or an earlier launch step fails, cleanup can use that launch record without waiting for child session initialization. Only a verified owned idle shell is closed; a changed boot, reused shell or live process blocks that cleanup. Keep failed source and session evidence.
38
+
39
+ A terminal marker is process-exit evidence only after the replica process has stopped. Revalidate it after sidecar and diagnostic-read races. Report available startup diagnostics, not unrelated warnings or an inherited parent answer. A native launch error already saved in the exact parent counts as its outcome and is not replayed as another automatic result. A warning without process failure does not terminate a copy.
40
+
41
+ ## One terminal result
42
+
43
+ A child publishes only when it reaches a terminal lifecycle outcome, such as auto-exit, cancellation, confirmed pane loss, or recovery failure. No model-facing completion or help tool is registered.
44
+
45
+ The terminal path:
46
+
47
+ 1. reads the latest child result;
48
+ 2. waits for every recursive descendant to drain;
49
+ 3. writes one immutable result file and one durable inbox entry;
50
+ 4. materializes that inbox in the exact parent session once;
51
+ 5. records delivery before cleanup; and
52
+ 6. removes the child from active supervision as soon as delivery is recorded.
53
+
54
+ The stable terminal delivery ID is the child ID prefixed with `terminal:`. Delivery uses an exclusive durable claim. A successful send is acknowledged once. A send that throws remains retryable. A send that Pi only queued — the streaming parent has not written it to its session yet — is never repeated: the claim stays held until the parent session holds the record, so one result cannot become many copies. Compaction, reload, restored watchers, and later parent tool calls cannot materialize an acknowledged delivery again.
55
+
56
+ Old pending `settled` inbox entries are acknowledged without being sent. This prevents an upgrade or restored session from exposing an intermediate response after the terminal-only rule is active.
57
+
58
+ Cleanup is separate from delivery. An ordinary child pane closes after delivery. A worktree child keeps its review workspace. A delivered child leaves the widget immediately; a rejected Herdr close retries a few times with backoff and then stops with a delivery-log record. Its result is not sent again.
59
+
60
+ Replicas do not automatically restart after process loss or inactivity. Their parent receives a failure with available answer/error facts and can create a fresh copy. Normal settlement reports only after session work drains. Ten continuous seconds with Pi idle and no pending messages, movement or undrained descendants retry a missed completion. A saved settlement checkpoint survives reload; without settlement evidence, this safety net reports failure and preserves the answers. Active requests, tools, retries and compaction do not count as stopped time.
61
+
62
+ Retained non-replica recovery is bounded to three attempts across crash and stale causes. An exhausted child is not killed, resumed or re-delivered on later watchdog passes.
63
+ A recovery watcher starts only after the replacement registers its own session and pane. The old closed pane must not become a manual-cancellation result for the new process.
64
+
65
+ ## Descendants and exact-parent binding
66
+
67
+ A child drains only after its terminal outcome reaches its exact immediate-parent inbox and all descendants drain recursively. Failed inbox publication has no timeout and is never rerouted. A pending session inbox stays bound to the stable parent session ID until that exact session returns, even through a changed file path or pane.
68
+
69
+ Lineage identity belongs to the launched Pi session. A child lifecycle starts only after the runtime opens that session's persisted child binding. Reload startup checks, shell commands and test runners may inherit its environment variables, but that does not let them publish completion, update its activity or take ownership of its descendants. In particular, closing a no-session startup check must not report the live replica as closed.
70
+
71
+ A public `SubagentTree` owner is different. Its callbacks process child results inside the tree runtime. The tree result still waits for all reserved descendants, but ordinary model-facing `subagent_result` delivery follows the one-terminal-result rule above.
@@ -0,0 +1,58 @@
1
+ # Worktree subagents
2
+
3
+ The retained package-root API and internal launcher support opt-in isolated Git worktrees. This document describes those retained paths only. The model-facing `replicate` tool defaults to a source-snapshot worktree with automatic host-owned join and successful cleanup; it also supports a shared-directory mode. See the README for that separate contract.
4
+
5
+ For example, a tree consumer can launch:
6
+
7
+ ```typescript
8
+ tree.launch({
9
+ name: "issue-42-build",
10
+ agent: "implementer",
11
+ task: "Implement the approved change, test it, commit it, and report the SHA.",
12
+ worktree: { branch: "issue/42", base: "HEAD" },
13
+ });
14
+ ```
15
+
16
+ This example assumes an existing `implementer` role. Bare API children are also supported.
17
+
18
+ `cwd` selects the source repository. `base` resolves to an exact commit before creation and defaults to committed `HEAD`. The child starts at the Herdr-created worktree root.
19
+
20
+ ## Guarantees
21
+
22
+ 1. Worktrees are opt-in per launch.
23
+ 2. Parent uncommitted and untracked files are not copied.
24
+ 3. An ownership manifest is written before Herdr creates resources.
25
+ 4. Herdr creates the workspace without changing parent focus.
26
+ 5. Success, failure, and help outcomes retain the workspace.
27
+ 6. Git inspection failures are reported as unknown, never guessed clean.
28
+ 7. The extension never pushes, merges, cherry-picks, switches the parent checkout, or removes worktrees and branches.
29
+
30
+ The manifest lives under the parent session artifacts:
31
+
32
+ ```text
33
+ <session-dir>/artifacts/<session-id>/worktree-runs/<child-id>.json
34
+ ```
35
+
36
+ ## Review
37
+
38
+ After completion, inspect the reported path and Git state. Review the diff and commits before integrating. The parent owns merge, publication, and cleanup.
39
+
40
+ Useful Git commands:
41
+
42
+ ```bash
43
+ git -C <worktree-path> status --short
44
+ git -C <worktree-path> log --oneline <base-sha>..HEAD
45
+ git -C <worktree-path> diff <base-sha>...HEAD
46
+ ```
47
+
48
+ ## Recovery
49
+
50
+ If creation returns an ambiguous Herdr error, the extension may reconcile one exact branch match from Herdr's worktree list. It never guesses among multiple matches. A failed launch retains any verified worktree and records the error in its manifest.
51
+
52
+ The retained internal resume path opens an ordinary pane. It does not reattach ownership of a retained worktree.
53
+
54
+ A parent or child may move through Teleport without transferring or discarding the original worktree ownership. The session's binding still resolves the launch manifest and pending answer. The new session location is used for lifecycle observation and delivery; the retained checkout remains the original launch's review target. Herdr process loss does not authorize closing an unrelated pane that reuses an old ID.
55
+
56
+ ## Cleanup
57
+
58
+ Only remove a worktree after review and integration. Verify its path and branch first, then use your normal Git or Worktrunk cleanup procedure. Never delete `.git/worktrees` entries by hand.
@@ -0,0 +1,18 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ const roles = fileURLToPath(new URL("./roles", import.meta.url));
5
+
6
+ export default function rolePack(pi: ExtensionAPI) {
7
+ const unsubscribe = pi.events.on(
8
+ "pi-herdr-subagents:roles:discover:v1",
9
+ (request) => {
10
+ const discovery = request as {
11
+ apiVersion: number;
12
+ register(path: string): void;
13
+ };
14
+ if (discovery.apiVersion === 1) discovery.register(roles);
15
+ },
16
+ );
17
+ pi.on("session_shutdown", unsubscribe);
18
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@example/pi-herdr-roles",
3
+ "version": "1.0.0",
4
+ "keywords": [
5
+ "pi-package"
6
+ ],
7
+ "type": "module",
8
+ "pi": {
9
+ "extensions": [
10
+ "./extension.ts"
11
+ ]
12
+ },
13
+ "peerDependencies": {
14
+ "@earendil-works/pi-coding-agent": "*"
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: Reviews a bounded change and reports concrete, evidence-backed findings
3
+ tools: read, bash
4
+ spawning: false
5
+ auto-exit: true
6
+ system-prompt: append
7
+ ---
8
+
9
+ # Example Reviewer
10
+
11
+ Review only the requested change. Report concrete findings with file and line
12
+ references. Do not modify files.
package/index.d.ts ADDED
@@ -0,0 +1,116 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
3
+ export interface SubagentTreeMetadata {
4
+ readonly value: unknown;
5
+ /** Immutable package-owned identity; value is the consumer-owned opaque metadata. */
6
+ readonly callerId?: string;
7
+ readonly treeId?: string;
8
+ readonly ownerId?: string;
9
+ readonly nodeId?: string;
10
+ readonly parentId?: string;
11
+ }
12
+ export interface SubagentError {
13
+ readonly code: string;
14
+ readonly message: string;
15
+ }
16
+ export interface SubagentWorktree {
17
+ readonly path?: string;
18
+ readonly branch?: string;
19
+ readonly baseRef?: string;
20
+ readonly baseSha?: string;
21
+ readonly workspaceId?: string;
22
+ readonly paneId?: string;
23
+ readonly headSha?: string | null;
24
+ readonly commitsAhead?: number | null;
25
+ readonly clean?: boolean | null;
26
+ readonly conflicted?: boolean | null;
27
+ readonly changedFiles?: string[] | null;
28
+ readonly untrackedFiles?: string[] | null;
29
+ readonly manifestFile?: string;
30
+ }
31
+ export type SubagentChildOutcome = "clean" | "empty" | "error" | "intentional-abort" | "unexpected-abort";
32
+ export interface SubagentChildResult {
33
+ readonly resultId: string;
34
+ readonly assistantEntryId: string;
35
+ readonly answer: string | null;
36
+ readonly outcome: SubagentChildOutcome;
37
+ readonly sessionReference?: string;
38
+ readonly error?: SubagentError;
39
+ readonly worktree?: SubagentWorktree;
40
+ readonly metadata?: SubagentTreeMetadata;
41
+ }
42
+ export interface SubagentNodeResult {
43
+ readonly nodeId: string;
44
+ readonly parentId: string;
45
+ readonly outcome: SubagentChildOutcome;
46
+ readonly open: boolean;
47
+ readonly sessionReference?: string;
48
+ readonly error?: SubagentError;
49
+ }
50
+ export interface SubagentTreeResult {
51
+ readonly terminalId: string;
52
+ readonly state: "completed" | "failed" | "cancelled";
53
+ readonly rootResult: SubagentChildResult;
54
+ readonly callerMetadata: SubagentTreeMetadata;
55
+ readonly nodes: readonly SubagentNodeResult[];
56
+ readonly error?: SubagentError;
57
+ }
58
+ export interface SubagentChildHandle {
59
+ readonly nodeId: string;
60
+ readonly parentId: string;
61
+ readonly result: Promise<SubagentChildResult>;
62
+ }
63
+ export interface LaunchChildOptions {
64
+ readonly parentId: string;
65
+ readonly name: string;
66
+ readonly task: string;
67
+ readonly agent?: string;
68
+ readonly skills?: string;
69
+ readonly tools: readonly string[];
70
+ readonly cwd?: string;
71
+ readonly worktree?: {
72
+ readonly branch: string;
73
+ readonly base?: string;
74
+ };
75
+ readonly metadata?: unknown;
76
+ }
77
+ export interface SubagentTreeHandle {
78
+ readonly callerId: string;
79
+ readonly treeId: string;
80
+ readonly ownershipToken: string;
81
+ readonly ownerId: string;
82
+ readonly children: ReadonlyMap<string, SubagentChildHandle>;
83
+ readonly result: Promise<SubagentTreeResult>;
84
+ launchChild(options: LaunchChildOptions): Promise<SubagentChildHandle>;
85
+ bindFinalCallback(callback: (result: SubagentTreeResult) => void | Promise<void>): void;
86
+ cancel(): Promise<SubagentTreeResult>;
87
+ }
88
+ export interface CreateSubagentTreeOptions {
89
+ readonly pi: ExtensionAPI;
90
+ readonly ctx: ExtensionContext;
91
+ readonly metadata?: unknown;
92
+ }
93
+ export interface ForkLineage {
94
+ readonly previousSessionFile: string;
95
+ }
96
+ export interface AttachSubagentTreeOptions {
97
+ readonly pi: ExtensionAPI;
98
+ readonly ctx: ExtensionContext;
99
+ readonly callerId?: string;
100
+ readonly treeId?: string;
101
+ readonly ownershipToken?: string;
102
+ readonly forkLineage?: ForkLineage;
103
+ }
104
+ export declare class SubagentTreeError extends Error {
105
+ constructor(message: string);
106
+ }
107
+ export declare class SubagentTreeValidationError extends SubagentTreeError {
108
+ constructor(message: string);
109
+ }
110
+ export declare class SubagentTreeOwnershipError extends SubagentTreeError {
111
+ constructor(message: string);
112
+ }
113
+ /** Create a caller-owned tree. Importing this module performs no registration. */
114
+ export declare function createSubagentTree(options: CreateSubagentTreeOptions): SubagentTreeHandle;
115
+ /** Reattach a caller or claim a child branch using its package context. */
116
+ export declare function attachSubagentTree(options: AttachSubagentTreeOptions): SubagentTreeHandle;