@herbertgao/pi-extensions 2026.9.8 → 2026.9.10

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 (144) hide show
  1. package/README.md +8 -4
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
  7. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  8. package/node_modules/@herbertgao/pi-subagents/README.md +427 -120
  9. package/node_modules/@herbertgao/pi-subagents/docs/rpc.md +184 -0
  10. package/node_modules/@herbertgao/pi-subagents/docs/workflows.md +466 -0
  11. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +6 -6
  12. package/node_modules/@herbertgao/pi-subagents/examples/workflows/compose.js +52 -0
  13. package/node_modules/@herbertgao/pi-subagents/examples/workflows/fan-out-audit.js +56 -0
  14. package/node_modules/@herbertgao/pi-subagents/examples/workflows/gated-fix.js +60 -0
  15. package/node_modules/@herbertgao/pi-subagents/examples/workflows/lib/count-child.js +30 -0
  16. package/node_modules/@herbertgao/pi-subagents/examples/workflows/review-panel.js +68 -0
  17. package/node_modules/@herbertgao/pi-subagents/examples/workflows/structured-findings.js +81 -0
  18. package/node_modules/@herbertgao/pi-subagents/package.json +11 -9
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +52 -12
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +837 -146
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +213 -39
  22. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +73 -14
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +101 -47
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +2249 -914
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +13 -0
  26. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +215 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/mention.ts +147 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +9 -1
  29. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +40 -26
  30. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +18 -8
  31. package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +46 -9
  32. package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +21 -16
  33. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +137 -7
  34. package/node_modules/@herbertgao/pi-subagents/src/structured-output.ts +136 -0
  35. package/node_modules/@herbertgao/pi-subagents/src/types.ts +126 -8
  36. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-mention.ts +274 -0
  37. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +20 -5
  38. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +10 -4
  39. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +167 -22
  40. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-card.ts +555 -0
  41. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-dialog.ts +1304 -0
  42. package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-menu.ts +226 -0
  43. package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +122 -0
  44. package/node_modules/@herbertgao/pi-subagents/src/workflow/entry.ts +47 -0
  45. package/node_modules/@herbertgao/pi-subagents/src/workflow/host.ts +463 -0
  46. package/node_modules/@herbertgao/pi-subagents/src/workflow/journal.ts +164 -0
  47. package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +142 -0
  48. package/node_modules/@herbertgao/pi-subagents/src/workflow/meta.ts +401 -0
  49. package/node_modules/@herbertgao/pi-subagents/src/workflow/progress.ts +622 -0
  50. package/node_modules/@herbertgao/pi-subagents/src/workflow/runtime.ts +1399 -0
  51. package/node_modules/@herbertgao/pi-subagents/src/workflow/saved.ts +230 -0
  52. package/node_modules/@herbertgao/pi-subagents/src/workflow/task.ts +333 -0
  53. package/node_modules/@herbertgao/pi-subagents/src/workflow/tool-description.ts +200 -0
  54. package/node_modules/@herbertgao/pi-subagents/src/workflow/worker-source.ts +781 -0
  55. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +97 -95
  56. package/node_modules/@herbertgao/pi-subagents/src/xml.ts +13 -0
  57. package/node_modules/@herbertgao/resume-from/package.json +2 -2
  58. package/node_modules/@narumitw/pi-btw/README.md +6 -1
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +209 -23
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  61. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +1 -12
  63. package/node_modules/@narumitw/pi-btw/src/menu.ts +244 -15
  64. package/node_modules/@narumitw/pi-btw/src/settings.ts +5 -0
  65. package/node_modules/pi-jev-auto-mode/CHANGELOG.md +110 -0
  66. package/node_modules/pi-jev-auto-mode/LICENSE +21 -0
  67. package/node_modules/pi-jev-auto-mode/README.md +285 -0
  68. package/node_modules/pi-jev-auto-mode/SECURITY.md +32 -0
  69. package/node_modules/pi-jev-auto-mode/docs/calibration.md +154 -0
  70. package/node_modules/pi-jev-auto-mode/docs/design.md +164 -0
  71. package/node_modules/pi-jev-auto-mode/docs/security.md +124 -0
  72. package/node_modules/pi-jev-auto-mode/index.ts +1 -0
  73. package/node_modules/pi-jev-auto-mode/package.json +72 -0
  74. package/node_modules/pi-jev-auto-mode/src/call.ts +180 -0
  75. package/node_modules/pi-jev-auto-mode/src/decide.ts +86 -0
  76. package/node_modules/pi-jev-auto-mode/src/extension.ts +862 -0
  77. package/node_modules/pi-jev-auto-mode/src/intent.ts +71 -0
  78. package/node_modules/pi-jev-auto-mode/src/jev/availability.ts +53 -0
  79. package/node_modules/pi-jev-auto-mode/src/jev/criteria.ts +19 -0
  80. package/node_modules/pi-jev-auto-mode/src/jev/decide.ts +187 -0
  81. package/node_modules/pi-jev-auto-mode/src/jev/engine.ts +165 -0
  82. package/node_modules/pi-jev-auto-mode/src/jev/index.ts +20 -0
  83. package/node_modules/pi-jev-auto-mode/src/jev/questions.ts +273 -0
  84. package/node_modules/pi-jev-auto-mode/src/jev/response.ts +64 -0
  85. package/node_modules/pi-jev-auto-mode/src/jev/state.ts +20 -0
  86. package/node_modules/pi-jev-auto-mode/src/jev/transport.ts +117 -0
  87. package/node_modules/pi-jev-auto-mode/src/jev/types.ts +46 -0
  88. package/node_modules/pi-jev-auto-mode/src/policy.ts +558 -0
  89. package/node_modules/pi-jev-auto-mode/src/records.ts +118 -0
  90. package/node_modules/pi-jev-auto-mode/src/settings.ts +327 -0
  91. package/node_modules/pi-jev-auto-mode/src/ui.ts +233 -0
  92. package/node_modules/pi-lens/CHANGELOG.md +45 -0
  93. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +21 -9
  94. package/node_modules/pi-lens/dist/clients/cascade-format.js +24 -0
  95. package/node_modules/pi-lens/dist/clients/extension-log.js +48 -1
  96. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +11 -12
  97. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +3 -0
  98. package/node_modules/pi-lens/dist/clients/lsp/index.js +41 -7
  99. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +9 -2
  100. package/node_modules/pi-lens/dist/clients/path-utils.js +51 -9
  101. package/node_modules/pi-lens/dist/clients/persistent-reverify.js +271 -0
  102. package/node_modules/pi-lens/dist/clients/pipeline.js +15 -1
  103. package/node_modules/pi-lens/dist/clients/runtime-turn.js +124 -7
  104. package/node_modules/pi-lens/dist/clients/skills-resolver.js +38 -2
  105. package/node_modules/pi-lens/dist/clients/widget-state.js +152 -6
  106. package/node_modules/pi-lens/dist/index.js +729 -344
  107. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +14 -2
  108. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +84 -17
  109. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +32 -5
  110. package/node_modules/pi-lens/package.json +1 -1
  111. package/node_modules/pi-typesafe/LICENSE +21 -0
  112. package/node_modules/pi-typesafe/README.md +153 -0
  113. package/node_modules/pi-typesafe/dist/ask.d.ts +32 -0
  114. package/node_modules/pi-typesafe/dist/ask.js +25 -0
  115. package/node_modules/pi-typesafe/dist/auth.d.ts +56 -0
  116. package/node_modules/pi-typesafe/dist/auth.js +114 -0
  117. package/node_modules/pi-typesafe/dist/batch.d.ts +74 -0
  118. package/node_modules/pi-typesafe/dist/batch.js +116 -0
  119. package/node_modules/pi-typesafe/dist/calibrate.d.ts +106 -0
  120. package/node_modules/pi-typesafe/dist/calibrate.js +157 -0
  121. package/node_modules/pi-typesafe/dist/client.d.ts +69 -0
  122. package/node_modules/pi-typesafe/dist/client.js +188 -0
  123. package/node_modules/pi-typesafe/dist/credentials.d.ts +46 -0
  124. package/node_modules/pi-typesafe/dist/credentials.js +112 -0
  125. package/node_modules/pi-typesafe/dist/errors.d.ts +9 -0
  126. package/node_modules/pi-typesafe/dist/errors.js +31 -0
  127. package/node_modules/pi-typesafe/dist/extension.d.ts +3 -0
  128. package/node_modules/pi-typesafe/dist/extension.js +222 -0
  129. package/node_modules/pi-typesafe/dist/index.d.ts +18 -0
  130. package/node_modules/pi-typesafe/dist/index.js +9 -0
  131. package/node_modules/pi-typesafe/dist/key-prompt.d.ts +3 -0
  132. package/node_modules/pi-typesafe/dist/key-prompt.js +43 -0
  133. package/node_modules/pi-typesafe/dist/login.d.ts +27 -0
  134. package/node_modules/pi-typesafe/dist/login.js +38 -0
  135. package/node_modules/pi-typesafe/dist/schema.d.ts +43 -0
  136. package/node_modules/pi-typesafe/dist/schema.js +152 -0
  137. package/node_modules/pi-typesafe/dist/ui.d.ts +3 -0
  138. package/node_modules/pi-typesafe/dist/ui.js +3 -0
  139. package/node_modules/pi-typesafe/dist/usage.d.ts +73 -0
  140. package/node_modules/pi-typesafe/dist/usage.js +182 -0
  141. package/node_modules/pi-typesafe/examples/decision-extension.ts +41 -0
  142. package/node_modules/pi-typesafe/extensions/index.js +2 -0
  143. package/node_modules/pi-typesafe/package.json +89 -0
  144. package/package.json +16 -9
@@ -1,38 +1,37 @@
1
1
  # @herbertgao/pi-subagents
2
2
 
3
- A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-agents** to pi.
4
-
5
- > HerbertGao-maintained fork of [@tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents), distributed under MIT with the original attribution preserved. This fork adds Claude Code/Agency Agents-compatible agent-name colors across the tool header, widget, FleetView, and conversation viewer.
6
-
7
- Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
3
+ A [pi](https://pi.dev) extension that brings **Claude Code-style autonomous sub-agents and workflow orchestration** to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in the background (the default) or block on them, steer them mid-run, resume completed sessions, and define your own custom agent types. When the orchestration shouldn't be improvised, hand a deterministic JavaScript script to the `SubagentWorkflow` tool — `agent()`, `parallel()`, `pipeline()` — and scripts written for Claude Code's `Workflow` tool run here unchanged.
8
4
 
9
5
  <img width="600" alt="pi-subagents screenshot" src="https://raw.githubusercontent.com/HerbertGao/pi-extensions/master/packages/pi-subagents/media/screenshot.png" />
10
6
 
11
- <https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543>
7
+ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
8
+
9
+ <img width="600" alt="pi-color-badges-white" src="https://github.com/user-attachments/assets/555dcae4-333e-4ff0-b420-7b3369c018a4" />
12
10
 
13
11
  ## Features
14
12
 
15
13
  - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
16
- - **Independent concurrency pools** — detached agents use `maxConcurrent` (default 10); blocking agents can be bounded separately with `maxConcurrentForeground` (default 0 = unlimited). Nested children occupy neither pool, avoiding parent/child deadlocks
17
- - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`; opt-in `showModel` adds the effective model and thinking level
14
+ - **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 10) and smart group join (consolidated notifications)
15
+ - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`
18
16
  - **FleetView** — Claude Code-style navigable list of `main` + every running subagent rendered below the editor (earliest-launched first). Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓` to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
19
- - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation. Assistant Markdown renders by default; press `m` to cycle raw / assistant-only / assistant+tool-results. Individual tool and bash results are capped at 16K characters with literal fallback if Markdown rendering fails. `Enter` steers and `x`, `x` stops a running agent
20
- - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions, and Claude Code-compatible colored name badges. UTF-8 BOM-prefixed files are parsed safely
17
+ - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). Steer a running agent inline by pressing `Enter` to open a composer, typing, then `Enter` to send (`Esc` or an empty submit returns) the message appears as a user message and redirects the agent after its current tool. Stop a still-running agent by pressing `x` (then `x` again to confirm) — both work for background agents too. Assistant text renders as Markdown; `m` cycles that between off, assistant-only and everything (see [Viewer markdown](#persistent-settings))
18
+ - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions, and Claude Code-compatible colored name badges
21
19
  - **Nested subagents** — opt-in, default-off delegation: a custom agent that sets `allowed_subagents` gets its own ownership-scoped `Agent`, `get_subagent_result`, and `steer_subagent` tools, depth-capped from the main session (default 2). It can control only its own children, they are stopped when it finishes, and their transcripts and token spend roll up to it. The allowlist is a privilege boundary — a child runs with its own tools, so pick it as carefully as `tools:` itself
20
+ - **Agent mentions** — subagents are first-class: type `@explore also check the RPC path` at the prompt and it goes to that agent instead of the main model, without a word of it entering the chat. One syntax covers the whole lifecycle — message it while it runs, resume it once it has finished, reopen its session from disk long after that, or start it if it never ran. Mentioning an agent that isn't running spawns it through an off-screen clone of the conversation, so it gets Claude Code's context-written prompt and a real `Agent` tool call without a word of it reaching the chat; `direct` mode starts it here from your text instead, with no model call at all. The orchestrator can `name` an agent so you address it as `@auth-audit`, and handles work in `steer_subagent`/`get_subagent_result` too. `@` completes live agents, resumable ones, and startable types alongside pi's file completion; `@main` forces text back to the main model. Toggle via `/agents → Settings → Agent mentions`
21
+ - **Scripted workflows** — a `SubagentWorkflow` tool that runs a deterministic JavaScript script orchestrating many subagents: `agent()`, `parallel()`, `pipeline()`, `phase()`, `log()` and `args`, with a pure-literal `meta` block declaring the phases. `pipeline()` has no barrier between stages, so one item can be in a later stage while another is still in the first — unlike `parallel()`, which idles every fast agent until the slowest finishes. Runs in the background with a live card, inspectable via `/agents → Workflows` or by selecting the run in FleetView. `agent()` also takes `gate: "npm test"` to verify a child by running a command (inside its worktree, when isolated) rather than asking another model, and `resume: "<label>"` to continue a child instead of re-paying its context. Scripts run in a `node:vm` sandbox on a worker thread where `Date.now()`, `Math.random()` and `eval` throw. On by default, but it stands down for company: if another extension already provides a `Workflow` or `SubagentWorkflow` tool, this one warns and disables itself for the session rather than offering the model two orchestrators. Pin it either way with `"workflowsEnabled"` in `subagents.json` or `/agents → Settings → Workflows`. A script written for Claude Code's `Workflow` tool runs here unchanged: same globals, `schema` returns a validated object exactly as it does there, `budget` is present and always reports no token target (pi has no such directive) so its `budget.total`-guarded patterns still take the branch they were written for, and nested `workflow()` composes saved workflows one level deep. **[Full guide](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/workflows.md)**
22
22
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
23
- - **Session resume** — pick up where an agent left off, preserving full conversation context
23
+ - **Session resume** — pick up where an agent left off, preserving full conversation context. Resumes detached by default and notifies you on completion, just like a fresh spawn; pass `run_in_background: false` to block and get the result inline
24
24
  - **Graceful turn limits** — agents get a "wrap up" warning before hard abort, producing clean partial results instead of cut-off output
25
- - **Case-insensitive agent types** — `"explore"`, `"Explore"`, `"EXPLORE"` all work. Unknown types fall back to general-purpose with a note
25
+ - **Case-insensitive agent types** — `"explore"`, `"Explore"`, `"EXPLORE"` all work. A type that doesn't resolve to exactly one _enabled_ agent — unknown, disabled, or ambiguous between two agents differing only by case — falls back to general-purpose with a note, or is refused outright under [`fallbackSubagent: none`](#persistent-settings)
26
26
  - **Fuzzy model selection** — specify models by name (`"haiku"`, `"sonnet"`) instead of full IDs, with automatic filtering to only available/configured models
27
27
  - **Context inheritance** — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
28
28
  - **Persistent agent memory** — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
29
- - **Git worktree isolation** — run agents in isolated repo copies; `isolation: off` and the project-wide `worktreeIsolation` switch keep work in the current checkout, while preservation commits bypass interactive signing
30
- - **Usage and cost reporting** — optionally include subagent spend in the parent session's stats (`reportUsage`) and show estimated USD cost across subagent UI surfaces (`showCost`)
29
+ - **Git worktree isolation** — run agents in isolated repo copies; changes auto-committed to branches on completion
31
30
  - **Skill preloading** — inject named skills into agent system prompts, discovered from `.pi/skills/`, `.agents/skills/`, and global locations (Pi-standard `<name>/SKILL.md` directory layout supported)
32
31
  - **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
33
32
  - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
34
33
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
35
- - **Cross-extension RPC** — other pi extensions can spawn, stop, and consume subagent results via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). String model overrides use the same tolerant resolver as the Agent tool, and both strings and `Model` objects honor `scopeModels`. Emits `subagents:ready` on session start
34
+ - **Cross-extension RPC** — other pi extensions can spawn, stop, and join subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start. **[Full reference](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/rpc.md)**
36
35
  - **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results land via the same `subagent-notification` followUp path as manual background completions; manage via `/agents → Scheduled jobs`
37
36
  - **Model scope enforcement** — opt-in validation that subagent model choices stay within your pi `enabledModels` allowlist (sourced from `/scoped-models`, with both global and project-local pi settings honored). Caller-supplied out-of-scope → hard error to orchestrator; frontmatter-pinned out-of-scope → warning + runs anyway (frontmatter authoritative). Toggle via `/agents → Settings → Scope models`
38
37
 
@@ -48,6 +47,16 @@ Or load directly for development:
48
47
  pi -e ./src/index.ts
49
48
  ```
50
49
 
50
+ Requires pi **0.84.0 or newer**: the [`SubagentWorkflow`](#subagentworkflow) tool builds on `constrainedSampling` (pi 0.82.0) and pi-tui's `stripTerminalSequences` (0.84.0). The `peerDependencies` range declares it, so npm flags an older pi at install time.
51
+
52
+ ### Other hosts
53
+
54
+ This extension is developed and tested against [pi](https://pi.dev).
55
+
56
+ Third-party adapters report running it elsewhere. These are maintained independently of this project: not tested here, not covered by our CI, and compatibility may break with any release.
57
+
58
+ - **DeepSeek Harness (`dsh`)** — via an adapter that maps pi's host API onto native DSH agents. Details and reports: [#258](https://github.com/tintinweb/pi-subagents/issues/258)
59
+
51
60
  ## Quick Start
52
61
 
53
62
  The parent agent spawns sub-agents using the `Agent` tool:
@@ -61,7 +70,7 @@ Agent({
61
70
  })
62
71
  ```
63
72
 
64
- Agents run in the background by default: calls return an ID immediately and notify on completion. Pass `run_in_background: false` to block and return the full result inline. Nested spawns keep the safer foreground default unless explicitly overridden.
73
+ Agents run in the background by default: the call returns an ID immediately and notifies you on completion, carrying a preview of the result (use `get_subagent_result` for the full text). Pass `run_in_background: false` to block until the agent finishes and get its full output inline.
65
74
 
66
75
  ### Scheduling
67
76
 
@@ -92,13 +101,13 @@ Schedules are **session-scoped**: they reset on `/new` and restore on `/resume`.
92
101
  Restrictions:
93
102
 
94
103
  - `schedule` cannot be combined with `inherit_context` (no parent conversation exists at fire time) or `resume` (schedules create fresh agents).
95
- - `run_in_background` is forced to `true`.
104
+ - `run_in_background: false` is refused scheduled jobs always run in the background. Omitting it, or passing `true`, is fine.
96
105
  - Scheduled fires bypass the `maxConcurrent` queue so a 5-minute interval cannot be deferred behind long-running manual agents.
97
106
  - **Headless `pi -p` doesn't wait for scheduled subagents.**
98
107
 
99
108
  ## UI
100
109
 
101
- The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`. Tool results and the conversation viewer always show the effective model and thinking level; enable `/agents → Settings → Show model` to add them to running widget rows:
110
+ The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`:
102
111
 
103
112
  ```
104
113
  ● Agents
@@ -124,12 +133,85 @@ While subagents are running, a Claude Code-style navigable list renders **below*
124
133
  esc to interrupt · ← for agents · ↓ to manage
125
134
 
126
135
  ● main
136
+ ○ workflow audit-src 12/40 agents · 32s · ↓ 26.4k tokens
127
137
  ○ general-purpose Sleep then report 1 11s · ↓ 13.1k tokens
128
138
  ○ general-purpose Sleep then report 2 11s · ↓ 13.1k tokens
129
139
  ↓ 3 more
130
140
  ```
131
141
 
132
- The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
142
+ Running [workflows](#subagentworkflow) appear as a single `workflow` row above the agents, carrying their agent counts in place of a description. `Enter` on one opens the same two-pane inspector `/agents → Workflows` does, rather than a conversation overlay. A run's own agents are _not_ listed separately — they belong to the run, which reports for them, so they are filtered out of the fleet list, the above-editor widget, the `/agents` menus and `@handle` resolution exactly as nested children are. They are also outside the `maxConcurrent` pool: the run has its own concurrency cap, and routing a fan-out through the session pool as well would let one workflow starve everything else. The agents are ordered earliest-launched first, and only agents you can actually open are shown (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. The selected row renders in the theme's primary text color rather than the muted/dim treatment of the others; an agent with a configured `color` shows its badge there too, bolded. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
143
+
144
+ ### Agent mentions
145
+
146
+ Subagents are addressable. Every agent has a typeable handle — the agent type, lowercased, numbered when instances collide (`explore`, `explore-2`) — and `@handle <message>` at the prompt talks to that agent, whatever state it happens to be in. Type `@` to pick one:
147
+
148
+ ```
149
+ ❯ @
150
+ @auth-audit send message · Explore · running · audit the auth flow
151
+ @explore-2 send message · running · find flaky tests
152
+ @code-review resume · code-review · check the diff
153
+ @plan start agent · Software architect agent for designing implementation plans.
154
+ index.ts src/index.ts ← pi's own file rows, still there
155
+ index.d.ts dist/index.d.ts
156
+ ```
157
+
158
+ The handle names the **agent**, not one process, so a single syntax covers its whole lifecycle:
159
+
160
+ | State | `@explore fix the flaky test` does |
161
+ | ------------------------------ | ------------------------------------------------------------------------------------------------- |
162
+ | running or queued | sends the message into its conversation, exactly as `steer_subagent` would |
163
+ | finished | **resumes** it in the background from its existing session, continuing where it left off |
164
+ | finished long ago, record gone | **reopens** its session from disk and continues there |
165
+ | never started | **starts** it — by default, through a clone of this conversation ([below](#starting-a-new-agent)) |
166
+
167
+ No turn is ever spent in the main conversation, and nothing about the mention enters the chat. The answer comes back as the ordinary background-completion notification, so the main model can relay it.
168
+
169
+ #### Starting a new agent
170
+
171
+ Claude Code does not start a mentioned agent itself. `@agent-<type>` becomes an _attachment_ appending a `<system-reminder>` to your prompt — "the user has expressed a desire to invoke the agent X; please invoke the agent appropriately, passing in the required context to it" — and the main model makes the tool call. There is no tool forcing and no allowed-tools narrowing: the mention constrains _which_ agent, not what it is told. So the model writes the agent's prompt, giving it the conversation context a cold spawn lacks.
172
+
173
+ The cost is a visible turn — the model's reasoning and its tool block, narrating a decision you already made by typing the handle. This extension keeps the mechanism and moves it off-screen. The conversation is copied into a throwaway in-memory session, that clone takes the turn holding only the `Agent` tool, and what it starts is an ordinary top-level agent:
174
+
175
+ ```
176
+ @cyan whats your favorite color → (nothing in the chat)
177
+ └─ clone of this conversation, off-screen
178
+ └─ Agent(subagent_type: "cyan", prompt: …)
179
+ ▸ Cyan Agent favorite color ← widget, fleet row, handle
180
+ ```
181
+
182
+ It is a literal clone — the session's own entries and the same system prompt, not [`inherit_context`](#agent-frontmatter)'s text rendering of them — taken from memory and compaction-aware, so what the copy reads is what the main model is working from. The clone gets one tool and one job; it cannot read, write or run anything, because an invisible turn with the full toolset could do invisible work. The agent it starts is attributed to the _real_ session, so its transcript and `rootSessionId` land where they would have anyway, and it carries no `tool-use-id` — the main conversation never issued one.
183
+
184
+ | Mode | `@plan sketch the migration`, with no Plan agent running |
185
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
+ | `model` (default) | a clone of this conversation takes the turn off-screen and calls `Agent`, so the agent starts with a prompt **written from the conversation**. Nothing reaches the chat but a `Prompting @plan…` toast — the wording marks the wait for that turn, where `direct`'s `Started @plan` means it is already running |
187
+ | `direct` | the agent starts here, immediately, with your message verbatim as its prompt. No model call at all, so no latency before it begins |
188
+ | `off` | `@` means only "attach a file" again |
189
+
190
+ Either way the started agent honours its own frontmatter — `model:`, `thinking:`, `max_turns:` all apply, since neither path passes them and the agent's config wins. Mentioning something as the very first thing in a session works: there is simply no history to carry, and the clone still runs on your model and system prompt. If it cannot deliver at all — a model can always answer in prose instead of calling the tool — the agent is started directly with your text and the toast says so, rather than leaving you with nothing running.
191
+
192
+ `model` is also the only mode that works outside the TUI: `pi -p '@plan the migration'` clones, spawns, and reports through the normal completion path, where a direct start would have detached the agent and printed nothing. Messaging and resuming stay TUI-only for that reason, in both modes.
193
+
194
+ Two things to weigh against `direct`: the clone re-sends the whole conversation, and the agent does not start until that turn finishes.
195
+
196
+ **Named agents.** The `Agent` tool takes an optional `name`, so the orchestrator can call one `auth-audit` instead of leaving you to tell `@explore-2` from `@explore-3`. A name is _additive_: the type-derived handle is still assigned, so `@explore` keeps reaching that agent rather than starting a second one beside it. Both names share one namespace — an alias can never shadow a live handle or the reverse — and the popup shows one row per agent, under its alias, with the type moved into the description. `steer_subagent` and `get_subagent_result` accept a handle too, so you and the model address agents the same way.
197
+
198
+ **Resuming much later.** Because subagent sessions are persisted by default ([`rememberAgents`](#persistent-settings)), a handle keeps working after the agent's in-memory record is evicted: `@explore anything else?` reopens the conversation from disk. Only the _definition_ is re-resolved, so a continuation runs under the agent type's current frontmatter, not the one the first run used. If the type has since been deleted or disabled, the resume is refused rather than falling back to another agent — re-enable it and the handle works again. Names from an evicted agent stay reserved, so a later Explore becomes `explore-2` rather than shadowing something you can still reach; the 100 most recent are kept, and all of them are forgotten on `/new` and session switch. A resumed agent takes those names back, so `@explore` keeps meaning the same conversation. An agent whose session was only ever in memory leaves nothing to reopen, and the mention starts a fresh one instead; if the session file has since been deleted, the mention says so and frees the handle rather than silently sending your message to a new agent.
199
+
200
+ The grammar mirrors Claude Code's, and is deliberately narrow so nothing gets swallowed by accident:
201
+
202
+ | Input | Goes to |
203
+ | ----------------------------------- | --------------------------------------------------------------------- |
204
+ | `@explore fix the flaky test` | the `explore` agent |
205
+ | `@agent-explore fix the flaky test` | the same agent — Claude Code's manual spelling, accepted as a synonym |
206
+ | `@main @explore is not a mention` | the main model, with `@main ` stripped — the escape hatch |
207
+ | `@explore` (no message) | the main model — a bare handle is never a send |
208
+ | `hey @explore look at this` | the main model — only a **leading** mention is routed |
209
+ | `@src/index.ts summarize this` | the main model, with pi's normal file attachment |
210
+ | `@nosuchagent hello` | the main model, verbatim — no agent, no type, no interception |
211
+
212
+ While an agent is live its handle addresses _it_, so `@explore` never starts a second Explore alongside a running one — use the `Agent` tool for deliberate parallelism. `@<agent-id>` works too. `main` is reserved and can never be an agent's handle (a type slugging to it gets `main-2`); handles are capped at 64 characters. A handle written as typed always wins over the `@agent-` form, so an agent genuinely called `agent-explore` stays reachable. [Nested subagents](#nested-subagents) are not addressable — they are hidden from every top-level surface and only their owner may steer them, so a handle that would name one starts a fresh top-level agent instead of reaching through that boundary. Suggestions list live agents first, then resumable ones, then startable types — and then pi's own file rows, in the same popup: `@` stays the file picker it always was, and the handles are added to it rather than replacing it. Disable the whole thing via `/agents → Settings → Agent mentions`.
213
+
214
+ A `direct`-mode start takes the non-tool spawn path shared with the scheduler and cross-extension RPC, so — like those — it writes no `.output` transcript. That is the trade for skipping the model call: a `model`-mode start goes through the real `Agent` tool and keeps everything. Live tool activity and the turn counter are _not_ part of that trade — a direct start renders them like any other agent. A mention-_resumed_ agent goes through the full resume wiring and keeps both in either mode.
133
215
 
134
216
  Individual agent results render Claude Code-style in the conversation:
135
217
 
@@ -150,7 +232,7 @@ By default, foreground and background agents each stream their full conversation
150
232
  ✓ Find auth files completed
151
233
  ↻3 · 3 tool uses · 12.4k token · 4.1s
152
234
  ⎿ Found 5 files related to authentication...
153
- transcript: .pi/output/agent-abc123.jsonl
235
+ transcript: /tmp/pi-subagents-501/home-user-project/sess-1/tasks/agent-abc123.output
154
236
  ```
155
237
 
156
238
  Group completions render each agent as a separate block. The LLM receives structured `<task-notification>` XML for parsing, while the user sees the themed visual.
@@ -169,7 +251,7 @@ Default agents can be **ejected** (`/agents` → select agent → Eject) to expo
169
251
 
170
252
  ## Custom Agents
171
253
 
172
- Define custom agent types by creating `.md` files. The filename becomes the `subagent_type` and dispatch identity; `name` and `display_name` only change the UI label. Using a default agent's filename overrides it.
254
+ Define custom agent types by creating `.md` files. The frontmatter `name:` is the `subagent_type` and dispatch identity, falling back to the filename when absent; `display_name` only changes the UI label. Claiming a default agent's name overrides it.
173
255
 
174
256
  Agents are discovered from three locations (higher priority wins):
175
257
 
@@ -179,15 +261,14 @@ Agents are discovered from three locations (higher priority wins):
179
261
  | 2 | `.agents/agents/<name>.md` | Project — the shared cross-tool `.agents` workspace (same convention as `.agents/skills/`) |
180
262
  | 3 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/<name>.md`) | Global — available everywhere |
181
263
 
182
- Project-level agents override global ones with the same name, so you can customize a global agent for a specific project. If both project locations define the same name, **`.pi/agents/` wins** — `.pi` stays the project authority; `.agents/agents/` is an additional read location for projects that keep their agent assets in the `.agents` workspace. The global location follows the upstream `PI_CODING_AGENT_DIR` env var — set it to relocate all pi-coding-agent state (agents, skills, settings) to a custom directory.
264
+ Project-level agents override global ones with the same name, so you can customize a global agent for a specific project. If both project locations define the same name, **`.pi/agents/` wins** — `.pi` stays the project authority; `.agents/agents/` is an additional read location for projects that keep their agent assets in the `.agents` workspace. The global location follows the upstream `PI_CODING_AGENT_DIR` env var — set it to relocate all pi-coding-agent state (agents, skills, settings) to a custom directory. An agent's name is its frontmatter `name:`, falling back to the filename, so two files can now claim the same one — the later load wins, and the warning below names the file that took over.
183
265
 
184
- An unreadable or unparseable agent file is skipped by default, with a warning that names the file and error. If the skipped file was overriding a same-named agent, another warning names the earlier file that remains active. Set `strictAgentFiles: true` in `subagents.json` (or `/agents → Settings → Strict agent files`) to fail startup on a broken file instead; mid-session reloads remain tolerant.
266
+ An unreadable or unparseable agent file is skipped, not fatal a warning names the file and the error. If it was overriding a same-named agent, a second line names the file that loads instead. Set `strictAgentFiles: true` in `subagents.json` (or `/agents → Settings → Strict agent files`) to fail startup on a broken file instead; mid-session reloads still only warn.
185
267
 
186
268
  ### Example: `.pi/agents/auditor.md`
187
269
 
188
270
  ```markdown
189
271
  ---
190
- name: Security Auditor
191
272
  color: red
192
273
  description: Security Code Reviewer
193
274
  tools: read, grep, find, bash
@@ -216,31 +297,31 @@ Agent({ subagent_type: "auditor", prompt: "Review the auth module", description:
216
297
 
217
298
  All fields are optional — sensible defaults for everything.
218
299
 
219
- | Field | Default | Description |
220
- | -------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
221
- | `description` | filename | Agent description shown in tool listings |
222
- | `name` | filename | Claude Code-compatible UI display name. The filename still determines the `subagent_type`; `display_name` wins when both are set |
223
- | `display_name` | `name`, then filename | Pi-specific UI display name (e.g. widget, FleetView, conversation viewer) |
224
- | `color` | — | Background color for the agent name badge in the Agent tool header, widget, FleetView, and conversation viewer. Supports Claude Code's `red`, `blue`, `green`, `yellow`, `purple`, `orange`, `pink`, `cyan`; quoted six-digit hex such as `"#8B5CF6"`; and Agency Agents aliases (`amber`, `teal`, `indigo`, `gold`, `neon-green`, `neon-cyan`, `metallic-blue`, `violet`, `rose`, `lime`, `gray`/`grey`, `fuchsia`, `slate`, `navy`). The badge automatically selects black or white text for at least 4.5:1 contrast against the rendered background. Invalid values render no badge and preserve each surface's existing theme foreground |
225
- | `tools` | all 7 | Which tools the agent can call. Built-in names (`read, grep, …`), `*` / `all` (all built-ins), `none`, and `ext:<extension>` / `ext:<extension>/<tool>` selectors for extension tools. See [Tool & extension scoping](#tool--extension-scoping) below |
226
- | `extensions` | `true` | Which extensions to load for the agent. `true` (all defaults), `false` (none), or an explicit list: `[mcp, "/abs/path.ts", "*"]`. See [Tool & extension scoping](#tool--extension-scoping) below |
227
- | `exclude_extensions` | — | Extension denylist applied after `extensions:` — exclude wins. Plain names only (case-insensitive), no paths or `*`. Useful with `extensions: true` to drop one extension (e.g. `pi-notify`) |
228
- | `skills` | `true` | Inherit skills from parent. Can be a comma-separated list of skill names to preload (see [Skill Preloading](#skill-preloading) for discovery locations) |
229
- | `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
230
- | `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
231
- | `isolation` | — | `worktree` runs in an isolated git worktree; `off` vetoes a caller-requested worktree |
232
- | `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp are interchangeable) and falls back to the same model under another provider if the named one doesn't have it |
233
- | `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
234
- | `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
235
- | `persist_session` | `false` | Persist this subagent as a normal pi session instead of keeping the session in memory only. The subagent's `.output` transcript is still written either way unless `output_transcript: false` |
236
- | `output_transcript` | `true` (or `subagents.json` `outputTranscript`) | Write this subagent's `.output` transcript; when set, overrides the `subagents.json` `outputTranscript` default. Set `false` to write no transcript file or path. Governs only the transcript — independent of `persist_session`, `isolation: worktree`, and `memory:` |
237
- | `session_dir` | pi default | Optional session directory when `persist_session: true`; omitted uses pi's normal session location, and relative paths resolve from the agent cwd |
238
- | `allowed_subagents` | none | Opt in to scoped nested `Agent`, `get_subagent_result`, and `steer_subagent` tools. Omitted / empty / `none` / `false` = no nesting; `all` (or `"*"` / `true`) = any enabled agent; comma-separated list = only those agent types |
239
- | `prompt_mode` | `replace` | `replace`: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). `append`: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
240
- | `inherit_context` | `false` | Fork parent conversation into agent |
241
- | `run_in_background` | `backgroundByDefault` | Pin this agent to background (`true`) or foreground (`false`); omit to follow the project default |
242
- | `isolated` | `false` | Hermetic specialist mode: forces `extensions: false` + `skills: false` + drops `ext:` selectors. Only built-in tools. Distinct from `isolation: worktree` (filesystem) |
243
- | `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
300
+ | Field | Default | Description |
301
+ | -------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
302
+ | `description` | filename | Agent description shown in tool listings |
303
+ | `name` | filename | **The agent's type** what `subagent_type` and `@handle` address. Claude Code's rule: the filename doesn't have to match, so `blubb.md` with `name: code-review` dispatches as `code-review`. Omit it and the filename is used. Any value works except one containing `:`, which Claude Code reserves for plugin-scoped identifiers — such a file is skipped with a warning. Two files may declare the same name; the later load wins, as a filename clash always did |
304
+ | `display_name` | the type | Label shown in the UI (widget, agent list, badges) — cosmetic only, and independent of `name`. Claude Code has no equivalent; a file that sets only `name` badges as its type, unchanged |
305
+ | `color` | — | Background color for the agent name badge in the Agent tool header, widget, FleetView, and conversation viewer. Supports Claude Code's `red`, `blue`, `green`, `yellow`, `purple`, `orange`, `pink`, `cyan` (the values its own default theme uses); quoted six-digit hex such as `"#8B5CF6"`; and Agency Agents aliases (`amber`, `teal`, `indigo`, `gold`, `neon-green`, `neon-cyan`, `metallic-blue`, `violet`, `rose`, `lime`, `gray`/`grey`, `fuchsia`, `slate`, `navy`). Badge text is black or white, whichever clears 4.5:1 against the rendered background — Claude Code uses one inverse color for every badge. Invalid values render no badge and preserve each surface's existing theme foreground |
306
+ | `tools` | all 7 | Which tools the agent can call. Built-in names (`read, grep, …`), `*` / `all` (all built-ins), `none`, and `ext:<extension>` / `ext:<extension>/<tool>` selectors for extension tools. See [Tool & extension scoping](#tool--extension-scoping) below |
307
+ | `extensions` | `true` | Which extensions to load for the agent. `true` (all defaults), `false` (none), or an explicit list: `[mcp, "/abs/path.ts", "*"]`. See [Tool & extension scoping](#tool--extension-scoping) below |
308
+ | `exclude_extensions` | — | Extension denylist applied after `extensions:` — exclude wins. Plain names only (case-insensitive), no paths or `*`. Useful with `extensions: true` to drop one extension (e.g. `pi-notify`) |
309
+ | `skills` | `true` | `true` inherits the parent's skills; `false` inherits none. A comma-separated list preloads **only** those skills into the system prompt and does not inherit the rest (see [Skill Preloading](#skill-preloading) for discovery locations) |
310
+ | `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
311
+ | `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
312
+ | `isolation` | — | Set to `worktree` to run in an isolated git worktree, or `off` to refuse one even when the caller passes `isolation: "worktree"` (frontmatter is authoritative). `none`, `no`, and `false` are accepted spellings of `off` |
313
+ | `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp are interchangeable) and falls back to the same model under another provider if the named one doesn't have it |
314
+ | `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
315
+ | `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
316
+ | `persist_session` | `subagents.json` `rememberAgents` (default `true`) | Persist this subagent as a normal pi session instead of keeping the session in memory only; overrides the `rememberAgents` project default in both directions. It records its spawning session as parent, so it nests under it in `/resume`. The subagent's `.output` transcript is still written either way unless `output_transcript: false` |
317
+ | `output_transcript` | `true` (or `subagents.json` `outputTranscript`) | Write this subagent's `.output` transcript; when set, overrides the `subagents.json` `outputTranscript` default. Set `false` to write no transcript file or path. Governs only the transcript — independent of `persist_session`, `isolation: worktree`, and `memory:` |
318
+ | `session_dir` | pi default | Optional session directory when `persist_session: true`; omitted uses pi's normal session location, and relative paths resolve from the agent cwd. A session outside the parent's session directory (this override, or `isolation: worktree`) is listed separately, so it shows as a root instead of nesting |
319
+ | `allowed_subagents` | none | Opt in to scoped nested `Agent`, `get_subagent_result`, and `steer_subagent` tools. Omitted / empty / `none` / `false` = no nesting; `all` (or `"*"` / `true`) = any enabled agent; comma-separated list = only those agent types |
320
+ | `prompt_mode` | `replace` | `replace`: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). `append`: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
321
+ | `inherit_context` | `false` | Fork parent conversation into agent |
322
+ | `run_in_background` | | Pin this agent to background (`true`) or foreground (`false`). Omit to follow `backgroundByDefault` |
323
+ | `isolated` | `false` | Hermetic specialist mode: forces `extensions: false` + `skills: false` + drops `ext:` selectors. Only built-in tools. Distinct from `isolation: worktree` (filesystem) |
324
+ | `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
244
325
 
245
326
  Frontmatter is authoritative. If an agent file sets `model`, `thinking`, `max_turns`, `inherit_context`, `run_in_background`, `isolated`, or `isolation`, those values are locked for that agent. `Agent` tool parameters only fill fields the agent config leaves unspecified.
246
327
 
@@ -264,7 +345,7 @@ allowed_subagents: support-file-finder, support-callsite-tracer # or `all`
264
345
 
265
346
  The hard cap is depth 2 by default: main session (0) → subagent (1) → nested child (2). Change it project-wide with `maxSubagentDepth` in `subagents.json` (or `/agents → Settings → Nested depth`); `0` or `1` turns nesting off everywhere. An agent already at the cap gets no nested tools at all — not even `get_subagent_result`, since it can never own a child. A child must independently set `allowed_subagents` to delegate again; isolated agents never receive nested tools.
266
347
 
267
- Nested children don't occupy `maxConcurrent` or `maxConcurrentForeground` slots — queueing a child behind a parent waiting for it would deadlock. The depth cap bounds how _deep_ nesting goes, not how _wide_: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
348
+ Nested children occupy no concurrency slot, in either pool their parent already holds one, and queueing them behind it would deadlock a parent waiting on its own child. The depth cap bounds how _deep_ nesting goes, not how _wide_: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
268
349
 
269
350
  Because a subagent session never activates this extension (that is what keeps a child from building a second agent manager, and it is why nested tools are injected directly instead), a subagent also gets none of the extension's other surfaces: no `/agents` command, no cross-extension RPC handlers, no `subagents:ready` event.
270
351
 
@@ -299,32 +380,92 @@ A few rules the examples don't make obvious:
299
380
  - Any `ext:` entry flips extension tools to an explicit allowlist — unnamed extensions still load (handlers fire) but expose no tools. So `tools: "*, ext:mcp/search"` exposes only `search` from `mcp`, nothing from any other extension.
300
381
  - Extension names match case-insensitively (`[Mcp]` = `[mcp]`); tool names in `ext:foo/bar` stay case-sensitive.
301
382
  - Extensions that register tools **lazily** work too. MCP-backed extensions typically can't enumerate their tools until their servers connect, so they register from `session_start` or `before_agent_start` rather than at load. Subagent scoping is re-derived as tools appear, so these surface normally — including under `ext:` selectors, which keep narrowing correctly no matter when a tool shows up.
302
- - Extensions bound into a child session receive `session_shutdown` before disposal, including record eviction and root-session shutdown; release per-session timers, watchers, sockets, and temp resources there.
383
+ - Extensions bound into a subagent see **both ends** of that session's lifecycle: `session_start` when the agent starts, `session_shutdown` (reason `quit`) when its session is disposed — on quit, and when its record is evicted ~10 minutes after it finishes. Release per-session resources there; anything left armed outlives the session it belongs to. Handlers are given three seconds on quit, after which teardown proceeds regardless.
303
384
  - An installed **package** extension matches by its package short name (`@scope/pi-subagents` → `[pi-subagents]`), in addition to its path-derived name (a package whose entry is `src/index.ts` also answers to `[src]`). Prefer the package name — the path-derived one is incidental.
304
385
  - Plain `tools:` typos fail loudly: `tools: reed, grep` fires `tools-error:…` instead of silently producing an under-tooled agent.
305
386
  - `exclude_extensions:` wins over `extensions:` and over `ext:` selectors — an excluded extension never loads and a `tools: ext:` entry can't pull it back. Plain names only (no paths, no `*`); a name matching nothing fires an `extension-error:…` warning.
306
387
  - `exclude_extensions:` is **not a sandbox**: excluded extensions' factory code still executes once during loading. Exclusion suppresses their tools and their bound lifecycle hooks (`pi.on` handlers like `session_start` only fire for extensions bound to the session), but not other load-time side effects — a factory that subscribes directly to the shared `pi.events` bus stays live. Don't rely on it to contain an untrusted extension.
307
388
  - Array and string forms are equivalent: `[a, b]` == `"a, b"`.
308
389
 
390
+ **How an agent's scope is advertised.** The Agent tool description lists every available agent with a `(Tools: …)` suffix, and that suffix is what the orchestrator reads when deciding where to route work. It describes **built-in scope only** — extension tools are resolved when the agent runs (extensions may register lazily, see above), so they can't be enumerated when the description is built:
391
+
392
+ | `tools:` | suffix |
393
+ | ------------------------------------------------------- | ------------------------------------ |
394
+ | omitted, `*`, or `all` | `*` |
395
+ | a list of built-ins | that list, e.g. `read, grep` |
396
+ | `none` with `isolated: true` or `extensions: false` | `none` |
397
+ | `none`, or only `ext:` entries, with extensions loading | `no built-ins, extension tools only` |
398
+
399
+ The last two rows are separate because zero built-ins is not zero tools: `tools: none` alongside `extensions:` still surfaces every extension tool, so calling it `none` would understate what the agent can do. Note `*` doesn't enumerate extension tools either — an agent with `tools: "*, ext:mcp/search"` advertises `*`.
400
+
309
401
  ## Tools
310
402
 
311
403
  ### `Agent`
312
404
 
313
405
  Launch a sub-agent.
314
406
 
315
- | Parameter | Type | Required | Description |
316
- | ------------------- | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
317
- | `prompt` | string | yes | The task for the agent |
318
- | `description` | string | yes | Short 3-5 word summary (shown in UI) |
319
- | `subagent_type` | string | yes | Agent type (built-in or custom) |
320
- | `model` | string | no | Model `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp interchangeable) with provider fallback |
321
- | `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
322
- | `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
323
- | `run_in_background` | boolean | no | Defaults to `true`; `false` blocks and returns the result inline |
324
- | `resume` | string | no | Agent ID to resume a previous session |
325
- | `isolated` | boolean | no | No extension/MCP tools |
326
- | `isolation` | `"off"` \| `"worktree"` | no | `worktree` isolates; `off` keeps the current checkout. Omitted when worktree isolation is disabled |
327
- | `inherit_context` | boolean | no | Fork parent conversation into agent |
407
+ | Parameter | Type | Required | Description |
408
+ | ------------------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
409
+ | `prompt` | string | yes | The task for the agent |
410
+ | `description` | string | yes | Short 3-5 word summary (shown in UI) |
411
+ | `name` | string | no | Memorable name for this agent (`auth-audit`), addressable as `@name` and accepted by `steer_subagent`/`get_subagent_result`. Additive — the type-derived handle is still assigned |
412
+ | `subagent_type` | string | yes | Agent type (built-in or custom) |
413
+ | `model` | string | no | Model `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp interchangeable) with provider fallback |
414
+ | `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
415
+ | `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
416
+ | `run_in_background` | boolean | no | Defaults to `true`; `false` blocks and returns the result inline |
417
+ | `resume` | string | no | Agent ID to resume a previous session |
418
+ | `isolated` | boolean | no | No extension/MCP tools |
419
+ | `isolation` | `"off"` \| `"worktree"` | no | `worktree` runs in an isolated git worktree; `off` (the default) does not. Absent from the schema entirely when `worktreeIsolation: false` |
420
+ | `inherit_context` | boolean | no | Fork parent conversation into agent |
421
+
422
+ ### `SubagentWorkflow`
423
+
424
+ Run a deterministic script that orchestrates many subagents. Returns a task id immediately; the run continues in the background and notifies on completion.
425
+
426
+ | Parameter | Type | Required | Description |
427
+ | ----------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
428
+ | `script` | string | no | The workflow source. Must begin with `export const meta = { name, description }` |
429
+ | `scriptPath` | string | no | Path to a script file. Takes precedence over `script` and `name` |
430
+ | `name` | string | no | A saved workflow — `<name>.js` in `.pi/workflows/`, `.agents/workflows/` or `<agent dir>/workflows/`, carrying an `export const meta` declaration |
431
+ | `args` | any | no | Passed through to the script as the `args` global, verbatim |
432
+ | `resumeFromRunId` | string | no | Replay an earlier run in this session — its unchanged leading `agent()` calls return their recorded results instead of spawning |
433
+ | `title` / `description` | string | no | Accepted and ignored, as in Claude Code — a workflow is named by its `meta` block |
434
+
435
+ At least one of `script` / `scriptPath` / `name` is required; `scriptPath` wins over `script`, which wins over `name`. Each invocation's script is persisted to the session directory and its path returned, so iterating means editing that file and re-running rather than resending the source. A saved workflow reports its own file instead, so the same loop works on it — project `.pi/workflows/` shadows a same-named global one. Those directories are ordinary folders that may hold other scripts, so only files carrying the `export const meta = { name, description }` declaration are listed or resolved; naming anything else reports that it is not a workflow rather than running it. The check is a regex over the source — nothing in the file is executed to make it, and even a real parse evaluates only the `meta` object literal, in an empty `node:vm` context with a 100ms bound.
436
+
437
+ ```js
438
+ export const meta = {
439
+ name: "auth-audit",
440
+ description: "Find routes missing auth checks, then verify each finding",
441
+ phases: [{ title: "Scan" }, { title: "Audit" }],
442
+ }
443
+
444
+ phase("Scan")
445
+ const listing = await agent(
446
+ "List every route file under src/routes/. One path per line.",
447
+ )
448
+ const files = listing
449
+ .split("\n")
450
+ .map((s) => s.trim())
451
+ .filter(Boolean)
452
+ log("auditing " + files.length + " files")
453
+
454
+ phase("Audit")
455
+ return await pipeline(
456
+ files,
457
+ (file) =>
458
+ agent(`Audit ${file} for missing auth checks.`, { label: `audit:${file}` }),
459
+ (found, file) =>
460
+ agent(`Try to REFUTE this finding about ${file}: ${found}`, {
461
+ label: `verify:${file}`,
462
+ }),
463
+ )
464
+ ```
465
+
466
+ Concurrency is capped at `max(1, min(16, cpus - 2))` — the run's own limit, independent of the session's `maxConcurrent` pool, which its agents do not enter. There are 1000 agents per run and 4096 items per `parallel`/`pipeline` call.
467
+
468
+ **Full guide:** [`docs/workflows.md`](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/workflows.md) — how the model writes the script for you, how to edit and re-run it, how to save one as a reusable named workflow, plus the complete `agent()` option reference, recipes and troubleshooting.
328
469
 
329
470
  ### `get_subagent_result`
330
471
 
@@ -349,9 +490,46 @@ Send a steering message to a running agent. The message interrupts after the cur
349
490
 
350
491
  ## Commands
351
492
 
352
- | Command | Description |
353
- | --------- | --------------------------------- |
354
- | `/agents` | Interactive agent management menu |
493
+ | Command | Description |
494
+ | --------- | -------------------------------------------------------------------------------------------------------- |
495
+ | `/agents` | Interactive agent management menu — agent types, running agents, scheduled jobs, workflow runs, settings |
496
+
497
+ `/agents → Workflows` (shown only when [workflows](#persistent-settings) are on) opens a framed two-pane inspector over a run, with two levels of depth:
498
+
499
+ ```
500
+ audit-src
501
+ Dynamically discover files under src/ and audit each … 1/3 agents · 32s
502
+
503
+ ╭ Phases ──────────┬ Discover · 1 agent ──────────────────────────────────────────────╮
504
+ │ ❯ ✔ Discover 1/1 │ ❯ ✔ discover:src Opus 5 (1M context) · 26.4k tok 25s │
505
+ │ 2 Audit 0/2 │ │
506
+ │ 3 Verify │ │
507
+ │ 4 Synthesize │ │
508
+ ╰──────────────────┴──────────────────────────────────────────────────────────────────╯
509
+ ↑↓ select · ⏎ open · f filter · x stop · esc close · c convo
510
+ ```
511
+
512
+ The overview puts the phases on the left (a phase shows its number until it finishes, then `✔`/`✘`) and the selected phase's agents on the right. `⏎` opens one: the agents move to the left pane and the right becomes that agent's **Prompt**, **Activity** and **Outcome**, with `⏎` now expanding the prompt and `esc` going back a level rather than closing. `↑↓` (or `j`/`k`) move and `f` cycles the state filter, naming it in the pane title. The dialog opens as a centered overlay, like the conversation viewer an agent row opens; the frame sizes itself to what it holds, between six rows and twenty-two, so a three-agent run is not twenty rows of nothing and a two-hundred-agent one scrolls inside the pane. Long titles truncate with `…` rather than tearing it. With more than one workflow in the session it asks which, newest first.
513
+
514
+ The run itself takes five keys, and the footer offers each only while it can actually do something:
515
+
516
+ | Key | What it does |
517
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
518
+ | `x` | Stop the run. Live runs only — a settled one has nothing left to stop |
519
+ | `p` | Pause / resume. Pausing stops _starting_ agents; ones already running are left to finish, because killing model work mid-turn throws away everything it has spent. Held time is subtracted from the run's elapsed clock |
520
+ | `s` | Skip the selected agent: its `agent()` call returns `null`, exactly as a terminal failure does, and the row renders skipped. Offered while the agent is queued or running |
521
+ | `r` | Retry the selected agent: the child is stopped and the same call runs again, so the script's `agent()` promise is still the one waiting and gets the new answer. Running agents only — once a call has settled its value is already the script's, and a re-run would have nowhere to put one. The row then reads `attempt 2 · user retry` |
522
+ | `c` | Open the selected agent's **conversation** — the same live, scrolling viewer a fleet-list row opens, over the dialog, which hides itself underneath and comes back when you close it. The one key here that shows something rather than changing the run, so it works at both levels and on an agent that has already finished; reading what a child actually did is most of why anyone opens the inspector. Offered once the child has a record to open, which excludes a queued agent and one replayed from the resume journal. Records are swept ten minutes after they finish, and the key says so rather than opening an empty viewer |
523
+
524
+ Skipping is immediate for a running agent and for one held at a pause; an agent parked behind the concurrency limit takes its skip when it reaches the front of the queue.
525
+
526
+ ### CLI flags
527
+
528
+ | Flag | Description |
529
+ | ---------------------------------- | -------------------------------------- |
530
+ | `--subagents-workflow-file=<path>` | Run a workflow script at session start |
531
+
532
+ Use the `=` form. The bare `--flag value` spelling consumes the next argument, so `pi --subagents-workflow-file review.js "do the thing"` would take the prompt as the flag's value. Composes with headless mode: `pi -p --subagents-workflow-file=review.js`. With no tool call to attach to, the run renders as a session entry and its result is handed to the model as context for its next turn.
355
533
 
356
534
  The `/agents` command opens an interactive menu:
357
535
 
@@ -359,10 +537,10 @@ The `/agents` command opens an interactive menu:
359
537
  Running agents (2) — 1 running, 1 done ← only shown when agents exist
360
538
  Agent types (6) ← unified list: defaults + custom
361
539
  Create new agent ← manual wizard or AI-generated
362
- Settings ← max concurrency, max turns, grace turns, join mode
540
+ Settings ← max concurrency (background + foreground), max turns, grace turns, join mode
363
541
  ```
364
542
 
365
- - **Running agents** — select one to open its live conversation viewer. While it's still running, press `Enter` to open the steering composer, then `Enter` again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; `Esc` or an empty submit returns), or press `x` (then `x` again to confirm) to stop/abort it — including **background** agents, which a global Esc can't unambiguously target (Esc still stops a blocking foreground `Agent` call). A stopped agent reports its partial output flagged as incomplete, not as a completion.
543
+ - **Running agents** — select one to open its live conversation viewer. While it's still running, press `Enter` to open the steering composer, then `Enter` again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; `Esc` or an empty submit returns), or press `x` (then `x` again to confirm) to stop/abort it — including **background** agents, which a global Esc can't unambiguously target (Esc still stops a blocking foreground `Agent` call). A stopped agent reports its partial output flagged as incomplete, not as a completion. `m` cycles how much of the transcript renders as Markdown — see [Viewer markdown](#persistent-settings).
366
544
  - **Agent types** — unified list with source indicators: `•` (project), `◦` (global), `✕` (disabled). Each row shows the agent's model, and the highlighted agent's full description appears below the list. The model column flags `(unavailable, fallback: inherit)` when a configured model can't be resolved (it would silently inherit the parent model), and shows `(→ provider/id)` when it resolves to a different provider or version than configured. Select an agent to manage it:
367
545
  - **Default agents** (no override): Eject (export as `.md`), Disable
368
546
  - **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
@@ -371,7 +549,7 @@ Settings ← max concurrency, max turns, grac
371
549
  - **Eject** — writes the embedded default config as a `.md` file to project or personal location, so you can customize it
372
550
  - **Disable/Enable** — toggle agent availability. Disabled agents stay visible in the list (marked `✕`) and can be re-enabled
373
551
  - **Create new agent** — choose project/personal location, then manual wizard (step-by-step prompts for name, tools, model, thinking, system prompt) or AI-generated (describe what the agent should do and a sub-agent writes the `.md` file). Any name is allowed, including default agent names (overrides them)
374
- - **Settings** — configure max concurrency, default max turns, grace turns, and join mode at runtime
552
+ - **Settings** — configure max concurrency (background and foreground), default max turns, grace turns, and join mode at runtime
375
553
 
376
554
  ## Graceful Max Turns
377
555
 
@@ -390,9 +568,17 @@ Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
390
568
 
391
569
  ## Concurrency
392
570
 
393
- Background agents are subject to a configurable concurrency limit (default: 10). Excess agents are automatically queued and start as running agents complete. The widget shows queued agents as a collapsed count.
571
+ There are two independent pools.
572
+
573
+ **Background** (`maxConcurrent`, default 10). Excess agents are automatically queued and start as running agents complete. The widget shows queued agents as a collapsed count. Since agents run in the background by default, nearly every spawn takes a slot; the limit was raised from 4 so that ordinary parallel fan-outs don't queue.
574
+
575
+ **Foreground** (`maxConcurrentForeground`, default `0` = unlimited). Off by default, so nothing changes unless you set it. pi dispatches a message's tool calls through `Promise.all`, so several `Agent` calls with `run_in_background: false` in one message have always started at once — this bounds that. Useful mainly with local models, where parallel agents thrash the prompt cache ([#253](https://github.com/tintinweb/pi-subagents/issues/253)). A queued foreground agent appears in `/agents → Running agents` as `queued` and can be stopped there; its `Agent` call says so while it waits and then returns its result normally.
394
576
 
395
- Foreground agents bypass the queuethey block the parent anyway.
577
+ The two are deliberately **not** one limit. A foreground agent blocks the parent anyway the parent could have done that work itself without paying a slot — so charging it to the background pool would let a saturated pool starve the main session.
578
+
579
+ The foreground pool does not cover `resume`: a foreground resume reopens an existing session and never reaches the spawn path, so several blocking resumes in one message can still run at once. A _background_ resume does take a background slot and queues behind them like any other background agent.
580
+
581
+ Nested children and a [workflow](#subagentworkflow)'s agents are outside the pool entirely. A nested child would deadlock behind a parent waiting on it; a workflow already bounds its own fan-out at `max(1, min(16, cpus - 2))`, and counting its agents twice would let one run fill the session's pool and starve everything else.
396
582
 
397
583
  ## Join Strategies
398
584
 
@@ -418,11 +604,12 @@ When on, each subagent spawn's effective model is validated against pi's own `en
418
604
 
419
605
  **Out-of-scope handling depends on source:**
420
606
 
421
- | Model source | Out-of-scope behavior |
422
- | ---------------------------------------------------------- | -------------------------------------------------------------------- |
423
- | Caller-supplied via `Agent({ model: "..." })` or spawn RPC | Hard error returned to the orchestrator, listing allowed models |
424
- | Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
425
- | Parent-inherited (neither set) | Warning toast + parent's model runs |
607
+ | Model source | Out-of-scope behavior |
608
+ | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
609
+ | Caller-supplied via `Agent({ model: "..." })` | Hard error returned to the orchestrator, listing allowed models |
610
+ | Caller-supplied via cross-extension RPC (`subagents:rpc:spawn`, e.g. pi-tasks `TaskExecute`) | Hard error returned to the calling extension, listing allowed models |
611
+ | Pinned in agent frontmatter | Warning toast + the pinned model runs (frontmatter is authoritative) |
612
+ | Parent-inherited (neither set) | Warning toast + parent's model runs |
426
613
 
427
614
  **Design:** `scopeModels` is a guardrail against the orchestrator picking unexpected models at runtime, not a hard policy against user-level config. The "frontmatter is authoritative" guarantee from v0.5.1 still holds for `model:` — caller params can't override frontmatter, and frontmatter pins run even when out of scope (with a visible warning).
428
615
 
@@ -434,34 +621,83 @@ When on, each subagent spawn's effective model is validated against pi's own `en
434
621
 
435
622
  ## Persistent Settings
436
623
 
437
- Runtime tuning values set via `/agents` → Settings (including concurrency, viewer Markdown, model/cost display, background default, and worktree isolation) persist across pi restarts. Two files, merged on load:
624
+ Runtime tuning values set via `/agents` → Settings (max concurrency, max foreground concurrency, default max turns, grace turns, nested depth, fallback agent, default join mode, scheduling on/off, scope models on/off, disable defaults on/off, strict agent files on/off, agent mentions on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off, usage reporting on/off, cost display on/off, model display on/off, viewer markdown off/assistant/all) persist across pi restarts. Two files, merged on load:
438
625
 
439
626
  - **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
440
627
  - **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
441
628
 
442
- **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (background concurrency `10`, foreground concurrency unlimited, background by default enabled, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, worktree isolation enabled, viewer Markdown `assistant`, usage/cost/model widget display disabled, strict agent files disabled, defaults enabled).
629
+ **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `10`, max foreground concurrency `0` = unlimited, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, defaults enabled).
443
630
 
444
- **Foreground concurrency** (`maxConcurrentForeground`, default `0` = unlimited): independently bounds only top-level blocking `Agent` spawns. RPC, other detached spawns, resume, and nested children remain exempt; nested exemption prevents self-deadlock. Queued blocking calls show that they are waiting and remain cancellable.
631
+ **Nested depth** (`maxSubagentDepth`, default `2`): the hard ceiling on [nested delegation](#nested-subagents), counted from the main session (main = 0, its subagents = 1). `0` or `1` disables nesting project-wide regardless of any agent's `allowed_subagents`. Read when a subagent session is built, so a change applies to agents started after it.
445
632
 
446
- **Background by default** (`backgroundByDefault`, default `true`): unqualified top-level `Agent` calls run detached. Set `false` to restore foreground-by-default behavior. Explicit tool or agent-file `run_in_background` values win. Nested calls always default to foreground so a parent cannot finish and stop an uncollected child.
633
+ **Fallback agent** (`fallbackSubagent`, default `general-purpose`): the agent used when a caller-supplied `subagent_type` doesn't resolve to exactly one enabled agent — unknown, disabled, or ambiguous because two agents differ only by case. Name any enabled agent to route those calls there instead, or set `none` for **strict**, fail-closed dispatch: the call is refused with an error listing the available types, and nothing spawns. Strict mode matters most for background and scheduled calls, which would otherwise start executing a substituted agent before the caller learns anything. Also settable from `/agents → Settings → Fallback agent`. The boolean `false` is accepted as a spelling of `none`, because it would otherwise be dropped as the wrong type and silently leave the permissive default in place. Every other value is read as an agent name, so a mistaken `off` fails loudly at dispatch rather than meaning one thing in the settings file and another in the resolver. A fallback agent that is itself unknown or disabled is a misconfiguration and is reported rather than quietly replaced. Note the default is unchanged and stays permissive by design: with `disableDefaultAgents` and no `general-purpose` of your own, an unresolvable type still resolves to a built-in config carrying _all_ tools — set `none` (or name one of your own agents) to close that.
447
634
 
448
- **Worktree isolation** (`worktreeIsolation`, default `true`): set `false` to reject worktree creation on every spawn path and remove the `isolation` parameter and guidance on the next Pi session. `isolation: off` in agent frontmatter vetoes a caller's `worktree` request.
635
+ **Strict agent files** (`strictAgentFiles`, default `false`): when on, an unreadable or unparseable [agent file](#custom-agents) aborts extension load at startup and names the file, instead of being skipped with a warning — so a checked-in `.pi/agents/` can't silently fall through to a same-named agent from another location. Startup only: the mid-session reload that runs on each `Agent` call keeps warning either way, since a bad edit shouldn't kill a session on an unrelated spawn. Also settable from `/agents → Settings → Strict agent files`.
449
636
 
450
- **Usage and cost** (`reportUsage` and `showCost`, both default `false`): `reportUsage` attaches child usage to parent tool results so Pi session stats include delegated work; `showCost` renders Pi's estimated USD totals in subagent surfaces. They are independent.
637
+ **Disable defaults** (`disableDefaultAgents`, default `false`): when on, the three built-in agents (general-purpose, Explore, Plan) are not registered only your project/global custom agents are advertised and spawnable. User-defined agents are unaffected, including ones that override a default by name. The Agent tool's type list updates on the next pi session (the tool schema is registered at startup).
451
638
 
452
- **Model display** (`showModel`, default `false`): adds the effective model and thinking level to running widget rows. Tool results and the conversation viewer show them regardless; when a requested value was overridden or clamped, the UI also shows what was asked.
639
+ **Agent mentions** (`agentMentions`, default `"model"`): whether [`@handle message`](#agent-mentions) at the prompt addresses that subagent instead of the main model — messaging, resuming or starting it — and whether `@` offers agents alongside pi's file completion. `"model"` and `"direct"` differ only in [who starts an agent that isn't running](#starting-a-new-agent): an off-screen clone of this conversation, via a `<system-reminder>` and a real `Agent` call, or this extension, immediately and with no model call. Messaging and resuming are direct in both. `"off"` gates all three actions plus the suggestion list, so `@` means only "attach a file" again and every `@…` prompt reaches the main model verbatim. Toggle via `/agents → Settings → Agent mentions`; applied live. The booleans this setting used to take are still read — `true` as `"model"`, `false` as `"off"`.
453
640
 
454
- **Viewer Markdown** (`viewerMarkdown`, default `"assistant"`): `"off"` wraps all transcript content literally, `"assistant"` renders only assistant text as Markdown, and `"all"` also renders tool results. Press `m` in the viewer to cycle and persist the same setting. Bash output always stays literal.
641
+ **Background by default** (`backgroundByDefault`, default `true`): what an `Agent` call that doesn't say means. On following Claude Code the agent runs detached, the call returns its ID immediately, and a completion notification carries a preview of the result (`get_subagent_result` for the full text). Set `false` to restore the previous behaviour, where an unqualified spawn blocked the turn and returned its output inline. An explicit `run_in_background` on the call, or in an agent file's frontmatter, overrides this in both directions; the setting only decides what "unspecified" means. **Top-level only** — a nested spawn (an agent spawning its own) always defaults to foreground, because a detached child is stopped when its parent settles and has no notification path of its own. Toggle via `/agents → Settings → Background by default`; applied live.
455
642
 
456
- **Strict agent files** (`strictAgentFiles`, default `false`): fail extension startup when any discovered agent file is unreadable or malformed. Enable via `/agents Settings Strict agent files` or set `true` in `subagents.json`. Strictness applies only to startup; reloads before later Agent calls remain tolerant so a file edited incorrectly mid-session is skipped with a warning instead of aborting the call.
643
+ **Remember agents** (`rememberAgents`, default `true`): whether subagents persist their pi session, which is what lets [`@handle`](#agent-mentions) reopen an agent's conversation after its in-memory record has been evicted. Two visible consequences of the default: top-level subagents write a session file, and they nest under the session that spawned them in pi's `/resume`. Agents spawned by another agent are excluded they get no handle, so nothing could reopen their transcript. A custom agent's `persist_session` frontmatter overrides this per agent, in both directions. Toggle via `/agents Settings Remember agents`; with it off, handles expire with their record (roughly ten minutes past completion) and `@explore` then starts a fresh agent rather than resuming — the behaviour before this setting existed.
457
644
 
458
- **Nested depth** (`maxSubagentDepth`, default `2`): the hard ceiling on [nested delegation](#nested-subagents), counted from the main session (main = 0, its subagents = 1). `0` or `1` disables nesting project-wide regardless of any agent's `allowed_subagents`. Read when a subagent session is built, so a change applies to agents started after it.
645
+ **Output transcript** (`outputTranscript`, default `true`): the project/global default for writing each subagent's `.output` transcript. Toggle via `/agents Settings Output transcript`, or set `false` in `subagents.json` to make transcripts opt-in project-wide useful when run transcripts shouldn't sit on disk for backup or DLP tooling to pick up. A custom agent's `output_transcript` frontmatter overrides this per agent. Applied live at spawn time. Governs only the transcript, not `persist_session`, worktree commits, or memory files.
459
646
 
460
- **Fallback agent** (`fallbackSubagent`, default `general-purpose`): the agent used when a caller-supplied `subagent_type` doesn't resolve to exactly one enabled agent unknown, disabled, or ambiguous because two agents differ only by case. Name any enabled agent to route those calls there instead, or set `none` for **strict**, fail-closed dispatch: the call is refused with an error listing the available types, and nothing spawns. Strict mode matters most for background and scheduled calls, which would otherwise start executing a substituted agent before the caller learns anything. Also settable from `/agents Settings Fallback agent`. The boolean `false` is accepted as a spelling of `none`, because it would otherwise be dropped as the wrong type and silently leave the permissive default in place. Every other value is read as an agent name, so a mistaken `off` fails loudly at dispatch rather than meaning one thing in the settings file and another in the resolver. A fallback agent that is itself unknown or disabled is a misconfiguration and is reported rather than quietly replaced. Note the default is unchanged and stays permissive by design: with `disableDefaultAgents` and no `general-purpose` of your own, an unresolvable type still resolves to a built-in config carrying _all_ tools — set `none` (or name one of your own agents) to close that.
647
+ **Worktree isolation** (`worktreeIsolation`, default `true`): whether `isolation: "worktree"` may create a worktree at all. Toggle via `/agents Settings Worktree isolation`, or set `false` in `subagents.json` on a repo where a copy costs too much time or disk. Off, the `Agent` tool's `isolation` parameter is dropped from the schema entirely and the bullet describing it leaves the tool description with it nothing to pass, and no context spent describing it and worktrees are refused on every other path too (agent files, scheduled jobs, cross-extension RPC). The `/agents` agent-file generator stops offering the `isolation:` frontmatter field too, so a generated agent can't bake in a request that would be refused. A requested worktree is downgraded to a normal run rather than failing the call, since declining one is the point; there is deliberately no note on the result, which is exactly why the prose has to go when the parameter does. The refusal applies immediately; the parameter and its prose appear or disappear on the next pi session. See [Turning worktrees off](#turning-worktrees-off).
461
648
 
462
- **Disable defaults** (`disableDefaultAgents`, default `false`): when on, the three built-in agents (general-purpose, Explore, Plan) are not registered only your project/global custom agents are advertised and spawnable. User-defined agents are unaffected, including ones that override a default by name. The Agent tool's type list updates on the next pi session (the tool schema is registered at startup).
649
+ **Report usage to session** (`reportUsage`, default `false`): whether subagent spend is added to _this_ session's own totals. Subagents run in their own pi sessions, so by default pi's footer, statusline and `/cost` count only what the main model spent a session that delegated most of its work reads as nearly free. Turn it on and each `Agent` / `get_subagent_result` / `steer_subagent` result carries the spend accumulated since the last one, which pi folds into `getSessionStats()`; `/cost` attributes it to the **Tools/summaries** bucket. Toggle via `/agents → Settings → Report usage to session`; applied live.
463
650
 
464
- **Output transcript** (`outputTranscript`, default `true`): the project/global default for writing each subagent's `.output` transcript. Toggle via `/agents Settings Output transcript`, or set `false` in `subagents.json` to make transcripts opt-in project-wide useful when run transcripts shouldn't sit on disk for backup or DLP tooling to pick up. A custom agent's `output_transcript` frontmatter overrides this per agent. Applied live at spawn time. Governs only the transcript, not `persist_session`, worktree commits, or memory files.
651
+ Three things worth knowing about the numbers. Every token component is reported, `cacheRead` included — the cached prefix genuinely is re-read and re-billed on every call, and pi counts it the same way for the session's own messages, so withholding it would make a subagent's rows count differently from every other row in one total. (The extension's _own_ token displays still leave it out, which is a different question: there it inflates a reading of how much work was done.) Cost is pi's own per-message figure, priced from the model's listed rates; a model pi has no rates for contributes zero rather than an estimate. And the context-window percentage is untouched: pi derives it from assistant messages alone, so a delegating session's context doesn't appear to fill up faster. Agents that finish in the background have no tool result of their own to ride on, so their spend is carried by the next one you make — the footer catches up on the following call, not the moment they finish.
652
+
653
+ **Show cost** (`showCost`, default `false`): whether the subagent surfaces print an estimated cost beside their token counts — the widget (running _and_ finished lines), [FleetView](#fleetview), the conversation viewer, foreground results, `get_subagent_result`, and completion notifications:
654
+
655
+ ```text
656
+ ├─ ⠹ Explore inspect code · ↻3 · 8.2k token · ~$0.0042 · 4.1s
657
+ ✓ Explore inspect code · ↻8 · 5 tool uses · ~$0.0181 · 12.3s
658
+ ```
659
+
660
+ When several background agents finish together, their notification is topped with the batch total (`3 agents · 45.1k token · ~$0.042`) so the figures don't have to be added up by hand.
661
+
662
+ The `~` marks it as pi's estimate rather than a billed figure. **A cost is shown only when there is one to show:** a model pi has no pricing data for reports zero, and `$0.00` beside its tokens would say the run was measured and found free rather than never measured — so nothing is printed at all, on every surface. For the same reason a real cost too small to render reads `<$0.0001`. Figures keep cents at minimum and four decimals at most (`~$0.0042`, `~$0.05`, `~$1.24`) — rounding everything to cents would print the same number for runs that differed fourfold.
663
+
664
+ Independent of `reportUsage`: this one is what you read, that one is what your session counts. Toggle via `/agents → Settings → Show cost`; applied live.
665
+
666
+ **Show model** (`showModel`, default `false`): whether the widget's running rows name the model driving each agent and the thinking level it is running at:
667
+
668
+ ```text
669
+ ├─ ⠹ Explore inspect code · sonnet 4.6 · thinking: high · ↻3 · 8.2k token · 4.1s
670
+ ```
671
+
672
+ Off by default because the row already carries the description, turns, tool uses, tokens and elapsed time, and every character it gains is one the description loses on a narrow terminal. The other surfaces show the pair either way: the `Agent` tool result names the model beside its tags, and the conversation viewer's `↳` row spells out the canonical `provider/model-id`.
673
+
674
+ Both places report what the run _actually_ used, read back from the child session once pi has resolved its defaults and clamped the level to what the model supports — not what the call asked for. Where those differ, the request is kept beside the effective value rather than dropped, whether pi clamped it or an agent file's frontmatter outranked it:
675
+
676
+ ```text
677
+ ↳ anthropic/claude-haiku-4-5 · thinking: low (asked max) · background
678
+ ```
679
+
680
+ Toggle via `/agents → Settings → Show model`; applied live.
681
+
682
+ **Viewer markdown** (`viewerMarkdown`, default `"assistant"`): how much of the [conversation viewer](#ui)'s transcript is rendered as Markdown rather than shown verbatim.
683
+
684
+ ```text
685
+ off every line literal, as before this setting existed
686
+ assistant assistant text rendered; tool results verbatim and dim (default)
687
+ all tool results rendered too
688
+ ```
689
+
690
+ Scoped rather than all-or-nothing because the two kinds of content have different contracts. Assistant text _is_ Markdown — the model writes it that way, and the viewer was the only surface showing its source. A tool result is whatever bytes the tool produced, and a Markdown pass over one rewrites things that occur constantly in real output: `# section` in a shell script loses its `#`, a `---` line is swallowed as a setext heading, indented output is re-fenced, and `| a | b |` is redrawn as a box-drawing table. Each of those reads as the _tool_ having misbehaved, which is why `all` is opt-in.
691
+
692
+ Two rewrites are suppressed outright rather than left to the mode, because they change _data_ rather than layout: ordered-list markers keep their source numbering (`3) 7) 9)` stays, instead of being renumbered `3. 4. 5.`) and backslash escapes are not normalised.
693
+
694
+ Turn `all` on for tools that genuinely emit Markdown, and off again for a diff or a log. `m` in the viewer cycles the three and persists the choice, so the key and this setting are the same value — the footer shows which is in force as `m raw` / `m md` / `m md+`. Code fences are syntax-highlighted using pi's own Markdown theme — which is also why fenced code is the one part of a result _not_ dimmed under `all`; result prose still is, so the transcript keeps its hierarchy. Applied live; also settable from `/agents → Settings → Viewer markdown`.
695
+
696
+ **Workflows** (`workflowsEnabled`, default `true`): the master switch for scripted workflows. Toggle it via `/agents → Settings → Workflows`, or set it in `subagents.json`. Off, the `SubagentWorkflow` tool is never registered — the model is not told the feature exists and cannot call it, so it costs no tool-spec context — the `/agents → Workflows` entry is hidden, and `--subagents-workflow-file` refuses with a pointer to the setting rather than doing nothing. Read at extension load, so it applies on the next pi session; runs already in flight are left alone.
697
+
698
+ Leaving it unset is not quite the same as `true`. Unset means _auto_: on, unless another extension already provides a workflow tool, in which case this one warns and stands down for the session. Two orchestrators in one tool spec is a worse default than none — the model has to guess which to call and pays for both descriptions to find out — and the extension that was installed on purpose is the one that should survive. Setting `workflowsEnabled` explicitly pins the answer: `true` keeps ours whatever else is loaded, `false` is off regardless.
699
+
700
+ The match is on the exact tool names `Workflow` (Claude Code's) and `SubagentWorkflow` (ours), never a substring, so a `list_workflows` or `github_workflow_run` from some CI integration does not silently take the feature down. The check runs at `session_start` and nowhere earlier, because `getAllTools` throws during extension loading and load order means a check at registration time could not see an extension that has not loaded yet — so the tool is registered first and withdrawn from the active set through `setActiveTools`, which rebuilds the system prompt before any turn runs. When the other extension took the `SubagentWorkflow` name itself, pi's first-registration-wins rule already dropped ours, so there is nothing to withdraw and only the menu and the CLI flag come down.
465
701
 
466
702
  **Tool description** (`toolDescriptionMode`, default `"full"`): which Agent tool description the LLM sees. `"full"` is the rich Claude Code-style prompt (~1,400 tokens with the default agents); `"compact"` is ~75% smaller — one-line agent type list, terse usage notes — for small/local models where tool-spec tokens are expensive. Per-option details stay in the parameter descriptions in every mode (the parameter schema is never customizable). Applies on the next pi session.
467
703
 
@@ -474,7 +710,7 @@ Launch an autonomous agent. Available types:
474
710
  Custom agents live in .pi/agents/ or {{agentDir}}/agents/.
475
711
  ```
476
712
 
477
- Placeholders: `{{typeList}}` (full per-agent descriptions), `{{compactTypeList}}` (first sentence each), `{{agentDir}}`, `{{isolationGuideline}}` (empty when worktrees are disabled), `{{scheduleGuideline}}` (expands with its own leading newline + `-` bullet when scheduling is on — place it directly after your last rule line; empty when scheduling is off). Unknown placeholders are left verbatim with a stderr warning; a missing or empty file falls back to `"full"` with a warning. Note the usual trust umbrella: a project-level file shapes the orchestrator's prompt, same as project agents and extensions do.
713
+ Placeholders: `{{typeList}}` (full per-agent descriptions), `{{compactTypeList}}` (first sentence each), `{{agentDir}}`, `{{isolationGuideline}}` and `{{scheduleGuideline}}` (each expands with its own leading newline + `- ` bullet when the matching feature is on — place them directly after your last rule line; empty when [worktree isolation](#turning-worktrees-off) / scheduling is off). Unknown placeholders are left verbatim with a stderr warning; a missing or empty file falls back to `"full"` with a warning. Note the usual trust umbrella: a project-level file shapes the orchestrator's prompt, same as project agents and extensions do.
478
714
 
479
715
  **Starting point:** copy [`examples/agent-tool-description.md`](examples/agent-tool-description.md) — it reproduces the default full description exactly (a CI test keeps it in sync), so you can trim from a known-good baseline instead of writing from scratch.
480
716
 
@@ -498,21 +734,25 @@ Every project now starts with concurrency 16 and grace 10, without ever touching
498
734
 
499
735
  Agent lifecycle events are emitted via `pi.events.emit()` so other extensions can react:
500
736
 
501
- | Event | When | Key fields |
502
- | ---------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
503
- | `subagents:created` | Background agent registered | `id`, `type`, `description`, `isBackground` |
504
- | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
505
- | `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `durationMs`, `tokens` (lifetime `{ input, output, total }`), `toolUses`, `result` |
506
- | `subagents:failed` | Agent errored, stopped, or aborted (background and foreground) | same as completed + `error`, `status` |
507
- | `subagents:steered` | Steering message sent | `id`, `message` |
508
- | `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
509
- | `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
510
- | `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
511
- | `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | |
512
- | `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
513
- | `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
514
-
515
- `tokens.total` = `input + output + cacheWrite`. `cacheRead` is excluded — each turn's `cacheRead` is the cumulative cached prefix re-read on that one API call, so summing per-message would over-count it. Use `contextUsage.percent` (surfaced as `(NN%)` in the widget) for current context size.
737
+ | Event | When | Key fields |
738
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
739
+ | `subagents:created` | `Agent`-tool background spawn, or a detached resume — **not** cross-extension RPC, scheduler, or `@handle` spawns, which are first seen at `subagents:started` | `id`, `type`, `description`, `isBackground` (always `true`) |
740
+ | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
741
+ | `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `description`, `status`, `durationMs`, `tokens` (display total, `{ input, output, total }` — see the note below), `usage` (the run's spend as a pi `Usage`: token components including `cacheRead`, plus `cost.total` in USD; absent when nothing was spent), `toolUses`, `result` |
742
+ | `subagents:failed` | Agent errored, stopped, or aborted (background and foreground) | identical payload to `subagents:completed` both are built by the same formatter, so `error` and `status` are present on that row too, just empty |
743
+ | `subagents:steered` | Steering message accepted — fires for a _queued_ steer as well as a delivered one | `id`, `message` |
744
+ | `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
745
+ | `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
746
+ | `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
747
+ | `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `{}` (empty object) |
748
+ | `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
749
+ | `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
750
+
751
+ The four agent-lifecycle events `subagents:started`, `:completed`, `:failed`, `:compacted` are emitted for **top-level agents only**. Nested subagents and a workflow's children emit nothing at all; they report through the parent or workflow that owns them.
752
+
753
+ `tokens.total` = `input + output + cacheWrite`. `cacheRead` is excluded — each turn's `cacheRead` is the cumulative cached prefix re-read on that one API call, so summing per-message would over-count it as a measure of work done. Use `contextUsage.percent` (surfaced as `(NN%)` in the widget) for current context size.
754
+
755
+ `usage` answers the other question — what was billed — and so does include `cacheRead`, because the prefix really is re-read and re-charged on every call. It is a pi `Usage`, the same shape pi puts on `ToolResultEvent` and `AssistantMessage`, so `usage.cost.total` is where a listener already expects the money and anything pi adds to `Usage` arrives without a change here. Neither field derives from the other; `tokens` is a view model, `usage` is the data.
516
756
 
517
757
  ## Cross-Extension RPC
518
758
 
@@ -520,13 +760,15 @@ Other pi extensions can spawn and stop subagents programmatically via the `pi.ev
520
760
 
521
761
  All RPC replies use a standardized envelope: `{ success: true, data?: T }` on success, `{ success: false, error: string }` on failure.
522
762
 
763
+ **Full reference:** [`docs/rpc.md`](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/rpc.md) — the complete spawn-option surface (including the fields that are silently stripped), every error string, the completion-notification race, the `Symbol.for("pi-subagents:manager")` registry, and what protocol version `2` does and does not promise. [`tintinweb/pi-tasks`](https://github.com/tintinweb/pi-tasks) is the reference implementation.
764
+
523
765
  ### Discovery
524
766
 
525
767
  Listen for `subagents:ready` to know when RPC handlers are available:
526
768
 
527
769
  ```typescript
528
770
  pi.events.on("subagents:ready", () => {
529
- // RPC handlers are registered — safe to call ping/spawn/stop/consume
771
+ // RPC handlers are registered — safe to call ping/spawn/stop
530
772
  })
531
773
  ```
532
774
 
@@ -566,11 +808,13 @@ pi.events.emit("subagents:rpc:spawn", {
566
808
  requestId,
567
809
  type: "general-purpose",
568
810
  prompt: "Do something useful",
569
- options: { description: "My task", run_in_background: true },
811
+ options: { description: "My task", isBackground: true },
570
812
  })
571
813
  ```
572
814
 
573
- `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a string. Strings use the same tolerant local resolver as the Agent tool; after resolution, both string and object overrides are checked against `scopeModels`. `null` keeps inherit behavior.
815
+ `options` is the manager's spawn-option object, not the `Agent` tool's parameter schema — the background flag is `isBackground`, and the tool's snake_case `run_in_background` is forwarded verbatim and ignored. Every RPC spawn returns its id immediately and runs detached either way; `isBackground: true` is what makes the agent occupy one of the `maxConcurrent` slots (and queue behind them when they are full). It does not affect `subagents:created`, which is never emitted for an RPC spawn at all — the first event you see for one is `subagents:started`. Leaving it unset starts the agent immediately regardless of the limit. `maxConcurrentForeground` never applies here whatever `isBackground` says: it bounds only spawns a caller is blocking on inline, and every RPC spawn is detached. A top-level RPC spawn renders in the widget and FleetView while it runs, with the same live tool activity and turn counter an `Agent`-tool spawn gets only an explicit `isBackground: false` is dropped by the widget's default `background` mode, the way a foreground `Agent` call is. Nested spawns stay hidden from both.
816
+
817
+ `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are resolved against `ctx.modelRegistry` at the RPC boundary, so cross-extension callers can forward serializable values without losing auth context. Resolution is fuzzy, so a bare `"sonnet"` can land on a provider you never named: with [Model Scope](#model-scope) on, an override that resolves outside `enabledModels` is refused with an error envelope listing the allowed models, exactly as a caller-supplied `Agent({ model })` is. `null` means unset — the agent inherits, same as omitting the field.
574
818
 
575
819
  `options.cwd` (absolute path to an existing directory — anything else returns an error envelope; `null` means unset) runs the agent in a different working directory than the parent session. Its tools operate there and the prompt's environment block describes it, but **`.pi` config still loads from the parent session's project** — the target directory's `.pi` extensions never execute, and its agents/skills/settings are not picked up. Combined with `isolation: "worktree"`, the worktree is created _from_ the target directory's repo, the agent works at the equivalent subdirectory inside the copy (a monorepo-package cwd stays scoped to that package), and the resulting `pi-agent-*` branch lands in that repo — the completion message names it. On session end, worktree registrations are pruned in every repo that received one; only a hard crash can leave a stale entry (then: `git worktree prune` in the target repo). Agents with `memory:` keep reading/writing the parent project's memory.
576
820
 
@@ -589,7 +833,7 @@ pi.events.emit("subagents:rpc:stop", { requestId, agentId: "agent-id-here" })
589
833
 
590
834
  ### Consume
591
835
 
592
- Mark a settled agent result as already shown so its completion notification is suppressed:
836
+ Say that an agent's result has been shown to the model, so its completion notification is not delivered on top of it:
593
837
 
594
838
  ```typescript
595
839
  pi.events.emit("subagents:rpc:consume", {
@@ -598,7 +842,7 @@ pi.events.emit("subagents:rpc:consume", {
598
842
  })
599
843
  ```
600
844
 
601
- This is the event-bus equivalent of `get_subagent_result` consuming a result. Running, queued, unknown, and nested agents are refused; the channel is additive and remains outside the protocol-version handshake.
845
+ This is the bus-side half of what `get_subagent_result` does when it returns a result. A caller that joins an agent on `subagents:completed` and reports the result itself should consume it — otherwise the notification lands after the parent has already answered, costing a turn to dismiss. Fire-and-forget is the intended use: the reply carries nothing to act on, and the channel is outside the `subagents:rpc:ping` version handshake, so a caller can send it unconditionally and an older extension that has no handler simply keeps notifying. Consuming a running or unknown agent is refused (`success: false`) and changes nothing — a running agent has no result to have been read, and its notification is still the caller's only signal that it finished.
602
846
 
603
847
  Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
604
848
 
@@ -632,15 +876,30 @@ Set `isolation: worktree` to run an agent in a temporary git worktree:
632
876
  Agent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
633
877
  ```
634
878
 
635
- The agent gets a full, isolated copy of the repository. On completion:
879
+ The agent gets a full, isolated copy of the repository. The worktree is normally removed on completion:
636
880
 
637
- - **No changes:** worktree is cleaned up automatically
638
- - **Changes made:** changes are committed to a new branch (`pi-agent-<id>`) and returned in the result
881
+ - **No changes:** worktree is cleaned up automatically, no branch
882
+ - **Changes made:** changes are committed to a new branch (`pi-agent-<id>`), and the result names the branch and the `git merge` command for it. The branch is the only artifact — the worktree path is gone, so nothing points into it
639
883
  - **Agent committed its own work:** the branch is created at the agent's HEAD, preserving its commits (uncommitted leftovers are committed on top first)
884
+ - **Preservation failed:** the run reports an error and keeps the worktree. Its path is included in the result so the changes can be recovered manually
885
+
886
+ The agent's system prompt names the worktree as an isolated copy and tells it to work only there, even if other instructions name the main checkout — otherwise an inherited parent prompt or a task prompt mentioning the project path walks it straight back out of the copy. This is a directive, not a sandbox: an agent with shell access can still `cd` out, so don't rely on `isolation` alone to protect the main checkout.
887
+
888
+ The automatic preservation commit uses `--no-verify` and `--no-gpg-sign`, so local pre-commit hooks and interactive signing configuration can't block it. The commit is local-only and never pushed; pre-push and server-side hooks still apply. Other Git failures keep the worktree intact and report its recovery path instead of deleting the only copy.
889
+
890
+ If the worktree cannot be created (not a git repo, no commits, or `git worktree add` fails), the `Agent` call fails with a clear error instead of running unisolated — `isolation: "worktree"` is a strict guarantee, not a hint. The call is reported as a failed tool call, not as a subagent that ran and returned that message, so the model doesn't retry it as if the agent had merely reported a problem. Initialize git and commit at least once, or omit `isolation`.
891
+
892
+ A worktree is a _copy_, so the agent cannot see uncommitted or staged changes in the main checkout. Never use it to review a working-tree or staged diff: the agent finds an empty `git diff` and reports nothing wrong.
893
+
894
+ ### Turning worktrees off
640
895
 
641
- The automatic preservation commit uses `--no-verify`, so local pre-commit hooks can't block it — the commit is local-only and never pushed, and pre-push/server-side hooks still apply.
896
+ Three levers, from narrowest to broadest:
642
897
 
643
- If the worktree cannot be created (not a git repo, no commits, or `git worktree add` fails), the `Agent` tool returns a clear error instead of running unisolated — `isolation: "worktree"` is a strict guarantee, not a hint. Initialize git and commit at least once, or omit `isolation`.
898
+ - **Per call** omit `isolation`, or pass `isolation: "off"`. The explicit value exists because some models fill every optional parameter they are offered; with `worktree` as the only legal value they had no way to decline one (#231, #184).
899
+ - **Per agent** — `isolation: off` in an agent file. Frontmatter is authoritative, so this refuses a worktree even when the caller passes `isolation: "worktree"` — the only way to override a caller.
900
+ - **Per project** — `"worktreeIsolation": false` in `subagents.json`. The `Agent` tool's `isolation` parameter disappears from the schema entirely, along with the usage-note bullet that describes it (so it costs the model no context and cannot be passed), and worktree creation is refused on every other path too: agent files, scheduled jobs, and cross-extension RPC. The `/agents` generator also stops offering `isolation:` when writing a new agent file. Use it on a repo large enough that a copy costs real time and disk. The schema and the description are both built at tool registration, so they appear or disappear in the next pi session; the refusal itself takes effect immediately.
901
+
902
+ Schema and prose are gated together on purpose. Leaving the bullet in would teach the model to pass a field that is no longer declared — accepted silently, then dropped — and since a refused worktree carries no note on the result, the model would have every reason to go on reporting a `pi-agent-*` branch that was never created. A custom tool description should use the `{{isolationGuideline}}` placeholder rather than hardcoding the bullet, for the same reason.
644
903
 
645
904
  ## Skill Preloading
646
905
 
@@ -688,29 +947,77 @@ This is useful for creating agents that inherit extension tools but should not h
688
947
  ## Architecture
689
948
 
690
949
  ```
950
+ docs/ # Long-form guides (shipped to npm; README links out to them)
951
+ workflows.md # SubagentWorkflow: writing, editing, saving and re-running scripts
952
+ rpc.md # Cross-extension integration: pi.events, subagents:rpc:*, manager registry
953
+ examples/
954
+ workflows/ # Runnable examples, executed by test/workflow-examples.test.ts
955
+ agent-tool-description.md
956
+ test/ # vitest suite; e2e/ and perf/ subdirectories
691
957
  src/
692
- index.ts # Extension entry: tool/command registration, rendering
958
+ index.ts # Extension entry: tool/command registration, /agents menu, rendering
693
959
  types.ts # Type definitions (AgentConfig, AgentRecord, etc.)
960
+
961
+ # Agent registry
694
962
  default-agents.ts # Embedded default agent configs (general-purpose, Explore, Plan)
963
+ custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, and global agents
695
964
  agent-types.ts # Unified agent registry (defaults + user), tool name resolution
965
+ agent-file-toggle.ts # Locate/edit an agent's .md: enabled: toggle, eject to frontmatter
696
966
  agent-color.ts # Claude Code/Agency Agents name color parsing and badge rendering
967
+
968
+ # Execution
697
969
  agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
698
970
  agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
699
- cross-extension-rpc.ts # RPC handlers for cross-extension spawn/stop/consume via pi.events
971
+ nested-tools.ts # Delegation tools handed to subagents (nested spawn/collect/steer)
972
+ child-context.ts # AsyncLocalStorage flag marking work done for a child session
973
+ abortable.ts # Race a wait against Esc without cancelling the background child
700
974
  group-join.ts # Group join manager: batched completion notifications with timeout
701
- custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, and global agents
975
+ status-note.ts # Honest status note + salvaged partial output for non-normal outcomes
976
+ usage.ts # Token usage shapes, accumulators, session-stats readers
977
+
978
+ # Invocation surface
979
+ invocation-config.ts # Shared tool-parameter schemas (isolation, join, thinking, ...)
980
+ model-resolver.ts # Model resolution: exact provider/modelId with fuzzy fallback
981
+ enabled-models.ts # Read pi's enabledModels settings (project over global)
982
+ model-scope.ts # scopeModels allowlist policy, shared by top-level and nested tools
983
+ mention.ts # `@handle message` grammar: suggestion triggers and send parsing
984
+ mention-clone.ts # Run a mention's turn in a cloned conversation, off the main chat
985
+ cross-extension-rpc.ts # RPC handlers for cross-extension spawn/ping via pi.events
986
+
987
+ # Scheduling
988
+ schedule.ts # SubagentScheduler: cron / +10m / interval / ISO dispatch
989
+ schedule-store.ts # PID-locked, session-scoped, atomic schedule persistence
990
+
991
+ # Context & environment
702
992
  memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
703
993
  skill-loader.ts # Preload skills (Pi-standard + Agent Skills spec layouts)
704
994
  output-file.ts # Streaming output file transcripts for agent sessions
705
995
  worktree.ts # Git worktree isolation (create, cleanup, prune)
706
996
  prompts.ts # Config-driven system prompt builder
707
997
  context.ts # Parent conversation context for inherit_context
998
+ settings.ts # Persistent settings (~/.pi/agent/subagents.json + .pi/subagents.json)
708
999
  env.ts # Environment detection (git, platform)
1000
+
1001
+ workflow/
1002
+ meta.ts # Extract and validate a script's pure-literal `meta` block
1003
+ worker-source.ts # The sandbox: vm context, determinism prelude, script globals
1004
+ runtime.ts # Worker lifecycle, RPC bridge, semaphore, caps, gate/resume
1005
+ progress.ts # Progress event log and every derived view of it (pure)
1006
+ host.ts # WorkflowHost adapter over AgentManager
1007
+ task.ts # local_workflow task record and batched progress updates
1008
+ tool-description.ts # Model-facing description carrying the orchestration patterns
709
1009
  ui/
710
1010
  agent-widget.ts # Persistent widget: spinners, activity, status icons, theming
1011
+ fleet-list.ts # FleetView: navigable agent list below the editor
711
1012
  conversation-viewer.ts # Live conversation overlay for viewing agent sessions
1013
+ viewer-keys.ts # Viewer scroll keys resolved through user keybindings
1014
+ agent-mention.ts # `@` roster (running, resumable, and startable agents) + popup rows
1015
+ schedule-menu.ts # /agents → Scheduled jobs submenu
1016
+ select-item.ts # Collision-safe ctx.ui.select wrapper (numbered rows)
1017
+ workflow-card.ts # Inline workflow card (tool result and session entry)
1018
+ workflow-dialog.ts # /agents → Workflows two-pane inspector
712
1019
  ```
713
1020
 
714
1021
  ## License
715
1022
 
716
- MIT — [tintinweb](https://github.com/tintinweb)
1023
+ MIT — [tintinweb](https://github.com/tintinweb), maintained in this fork by [Herbert Gao](https://github.com/HerbertGao)