@herbertgao/pi-subagents 0.22.0-local.1 → 0.22.1-local.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +142 -45
- package/docs/rpc.md +17 -7
- package/package.json +4 -4
- package/src/agent-color.ts +11 -2
- package/src/agent-manager.ts +111 -59
- package/src/agent-runner.ts +135 -117
- package/src/cross-extension-rpc.ts +41 -14
- package/src/index.ts +120 -24
- package/src/nested-tools.ts +8 -0
- package/src/prompts.ts +31 -0
- package/src/ui/agent-widget.ts +3 -2
- package/src/ui/conversation-viewer.ts +1 -1
- package/src/ui/fleet-list.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.1-local.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#344](https://github.com/HerbertGao/pi-extensions/pull/344) [`1feb356`](https://github.com/HerbertGao/pi-extensions/commit/1feb356ae779427d033499d4e922579ec8247619) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Replay subagents onto upstream 0.22.1. Other extensions can steer a running agent over `subagents:rpc:steer`, and every agent reports per-message `subagents:usage` until `subagents:disposed`. A resumed run gets a fresh turn budget, a subagent inherits the parent session's thinking level, and an append-mode agent no longer repeats the parent's skills and working directory. Unconsumed results and a failed worktree cleanup still stay recoverable.
|
|
8
|
+
|
|
9
|
+
## 0.22.0-local.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#306](https://github.com/HerbertGao/pi-extensions/pull/306) [`935acc3`](https://github.com/HerbertGao/pi-extensions/commit/935acc38af54c76b6924bb48a0b09d2c59e4997f) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Label a run with its own `name` in the agent widget, the expanded agent block, the fleet list, the conversation header, and the Agent tool row, keeping its type's badge color. `agency_dispatch` now reports per-run duration, tool uses, and tokens in both its text and its structured result, and reports none of them for a run handed off to the background.
|
|
14
|
+
|
|
3
15
|
## 0.22.0-local.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -11,11 +11,12 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
|
|
14
|
-
- **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 10) and
|
|
14
|
+
- **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 10) and completion notifications that reach the parent at its next boundary, several at once when they finish together
|
|
15
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`
|
|
16
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`
|
|
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))
|
|
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). In pi's fullscreen mode it is a borderless, full-terminal observer with mouse support. 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. Press `b` on a running foreground `Agent` call to hand that run to the background (see [Sending a foreground agent to the background](#sending-a-foreground-agent-to-the-background)). Stop a still-running agent by pressing `x` (then `x` again to confirm) — steer and stop 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
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
|
|
19
|
+
- **Package-shipped agents** — a pi package can declare its own subagents (and workflow scripts) with `"pi": { "subagents": { "agents": ["./agents"] } }` in its `package.json`, so `pi install` is the whole setup: no copying files into `.pi/agents/` on every machine or container start. Only packages pi has actually installed are read (never `node_modules`), package definitions rank below every agent you wrote, and `/agents` shows them read-only with Eject and Disable. Narrow or switch it off with `packageAgents` / `packageWorkflows`. **[Full guide](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/packages.md)**
|
|
19
20
|
- **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
21
|
- **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
22
|
- **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)**
|
|
@@ -30,9 +31,9 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
|
|
|
30
31
|
- **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)
|
|
31
32
|
- **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
|
|
32
33
|
- **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
|
|
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
|
|
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)**
|
|
35
|
-
- **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results
|
|
34
|
+
- **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) and a per-message spend ledger (`subagents:usage`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
|
|
35
|
+
- **Cross-extension RPC** — other pi extensions can spawn, stop, steer, and join subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`, `subagents:rpc:steer`). 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
|
+
- **Schedule subagents** — pass `schedule` to the `Agent` tool to fire on cron / interval / one-shot. Session-scoped jobs with PID-locked persistence; results arrive as the same completion notification as a manual background agent; manage via `/agents → Scheduled jobs`
|
|
36
37
|
- **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`
|
|
37
38
|
|
|
38
39
|
## Install
|
|
@@ -70,7 +71,19 @@ Agent({
|
|
|
70
71
|
})
|
|
71
72
|
```
|
|
72
73
|
|
|
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.
|
|
74
|
+
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; while it blocks, `ctrl+b` hands it to the background (see below).
|
|
75
|
+
|
|
76
|
+
### Sending a foreground agent to the background
|
|
77
|
+
|
|
78
|
+
Press `ctrl+b` while a foreground `Agent` call is running to hand it to the background, as in Claude Code. The same run keeps going — same session, in-flight turn, transcript and worktree — and the blocked call returns the agent ID with the same result a `run_in_background: true` spawn gets. Completion then arrives as the normal background notification, naming the original tool call. After 2 seconds the running call's row shows `(ctrl+b to run in background)`.
|
|
79
|
+
|
|
80
|
+
- `ctrl+b` hands off every running foreground `Agent` call at once. To hand off just one, press `b` in its conversation viewer (FleetView or `/agents → Running agents`).
|
|
81
|
+
- The key is claimed only while such a call is running and the prompt editor has focus; the rest of the time — and in dialogs or the viewer's steering composer — `ctrl+b` is still pi's cursor-left. With FleetView disabled the extension cannot see which component has focus, so it claims the key whenever a call is running.
|
|
82
|
+
- One hint shows per batch of parallel calls, on the earliest one still running.
|
|
83
|
+
- Esc in the parent no longer reaches a handed-off agent. Stop it from `/agents → Running agents` with `x`.
|
|
84
|
+
- It is one-way: there is no way back to foreground.
|
|
85
|
+
- Other tool calls in the same assistant message still hold the model's next turn until they finish; pi runs a message's tool calls as one batch.
|
|
86
|
+
- Not offered for a foreground call still queued for a slot, a foreground `resume`, a workflow's agents or nested children. There is no automatic timeout.
|
|
74
87
|
|
|
75
88
|
### Scheduling
|
|
76
89
|
|
|
@@ -92,7 +105,7 @@ Schedule formats:
|
|
|
92
105
|
- **One-shot relative** — `"+10m"`, `"+2h"`, `"+1d"`. Fires once at that future time.
|
|
93
106
|
- **One-shot absolute** — full ISO timestamp, e.g. `"2026-12-25T09:00:00.000Z"`.
|
|
94
107
|
|
|
95
|
-
When a schedule fires, the spawn runs in background and its completion notification arrives
|
|
108
|
+
When a schedule fires, the spawn runs in background and its completion notification arrives the same way as a manually-spawned background agent's — your parent agent reasons about the result the same way.
|
|
96
109
|
|
|
97
110
|
Schedules are **session-scoped**: they reset on `/new` and restore on `/resume`. List and cancel via `/agents → Scheduled jobs` (creation is the `Agent` tool's job — there is no parallel manual-create wizard). Storage at `<cwd>/.pi/subagent-schedules/<sessionId>.json` with PID-based file locking for cross-instance safety.
|
|
98
111
|
|
|
@@ -139,7 +152,24 @@ While subagents are running, a Claude Code-style navigable list renders **below*
|
|
|
139
152
|
↓ 3 more
|
|
140
153
|
```
|
|
141
154
|
|
|
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`.
|
|
155
|
+
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. On a running foreground `Agent` call, press `b` to hand that run to the background; the hint only shows where the handoff is available. 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`.
|
|
156
|
+
|
|
157
|
+
### Fullscreen conversation observer
|
|
158
|
+
|
|
159
|
+
In fullscreen mode (pi's default), opening an agent from FleetView, `/agents`, or the workflow inspector fills the terminal. Regular mode keeps the centered floating viewer and keyboard controls. Neither mode replaces the main session or pauses either agent. Escape returns to the previous view; while composing, the first Escape cancels the draft.
|
|
160
|
+
|
|
161
|
+
In pi's `/settings → TUI mode → fullscreen`, the observer supports:
|
|
162
|
+
|
|
163
|
+
- Mouse-wheel scrolling. Scrolling up pauses automatic following; reaching the bottom resumes it.
|
|
164
|
+
- A single-column scrollbar using pi's `│` track, `┃` thumb, and theme colors. The thumb becomes `█` while hovered or dragged. Track clicks page; holding and moving also drags.
|
|
165
|
+
- pi's `/settings → Fullscreen scrollbar` preference applies to the observer: `auto` appears on scrolling or hovering and hides after one second of inactivity; `always` reserves a column and stays visible even when the conversation fits; `hidden` removes the rail and its mouse target. The observer reads the main session's settings when opened, without changing them.
|
|
166
|
+
- Clickable footer controls for steering, stopping, Markdown mode, and closing. Stopping still takes two presses, as with `x`.
|
|
167
|
+
- Keyboard navigation and steering as before. Mouse scrolling also works while composing a steering message.
|
|
168
|
+
- The bottom divider stays pinned above the footer (and steering composer when open). While scrolled away, a centered `[ ↓ N new messages · Ctrl+End ]` indicator within the divider counts new conversation messages, not streaming updates. Before any arrive, it reads `[ ↓ Jump to latest message · Ctrl+End ]`. Click it or press `Ctrl+End` to resume following and clear the count, without losing an open steering draft.
|
|
169
|
+
|
|
170
|
+
Observer mouse events are consumed, including at scroll limits, so they do not scroll, select, or click the main view underneath. Closing restores focus without editing the main prompt. The main conversation can still advance as its agent runs.
|
|
171
|
+
|
|
172
|
+
This is an initial implementation. Mouse text selection, copy-on-select, link activation, and switching agents without leaving the observer are not implemented. pi's global shortcuts remain host-owned: its fullscreen search searches the main transcript, not the observer. The scrollbar, jump indicator, and mouse controls apply only to fullscreen mode; regular mode retains the original viewer.
|
|
143
173
|
|
|
144
174
|
### Agent mentions
|
|
145
175
|
|
|
@@ -245,7 +275,7 @@ Group completions render each agent as a separate block. The LLM receives struct
|
|
|
245
275
|
| `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | `replace` (standalone) | Fast codebase exploration (read-only) |
|
|
246
276
|
| `Plan` | read, bash, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
|
|
247
277
|
|
|
248
|
-
The `general-purpose` agent is a **parent twin** — it receives the parent's
|
|
278
|
+
The `general-purpose` agent is a **parent twin** — it receives the parent's system prompt plus a sub-agent context bridge, so it follows the same rules the parent does. The parent's skill list, cwd and extension sections are left out: the subagent's own session adds them for itself, so its skills follow its `skills` setting. Explore and Plan use standalone prompts tailored to their read-only roles.
|
|
249
279
|
|
|
250
280
|
Default agents can be **ejected** (`/agents` → select agent → Eject) to export them as `.md` files for customization, **overridden** by creating a `.md` file with the same name (e.g. `.pi/agents/general-purpose.md`), or **disabled** per-project with `enabled: false` frontmatter.
|
|
251
281
|
|
|
@@ -253,13 +283,14 @@ Default agents can be **ejected** (`/agents` → select agent → Eject) to expo
|
|
|
253
283
|
|
|
254
284
|
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.
|
|
255
285
|
|
|
256
|
-
Agents are discovered from
|
|
286
|
+
Agents are discovered from four locations (higher priority wins):
|
|
257
287
|
|
|
258
288
|
| Priority | Location | Scope |
|
|
259
289
|
| ----------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
260
290
|
| 1 (highest) | `.pi/agents/<name>.md` | Project — pi's config dir; authoritative, and where `/agents` writes |
|
|
261
291
|
| 2 | `.agents/agents/<name>.md` | Project — the shared cross-tool `.agents` workspace (same convention as `.agents/skills/`) |
|
|
262
292
|
| 3 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/<name>.md`) | Global — available everywhere |
|
|
293
|
+
| 4 (lowest) | Paths declared by an installed pi package | Package — see [Shipping agents in a pi package](#shipping-agents-in-a-pi-package) |
|
|
263
294
|
|
|
264
295
|
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.
|
|
265
296
|
|
|
@@ -309,7 +340,7 @@ All fields are optional — sensible defaults for everything.
|
|
|
309
340
|
| `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
341
|
| `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
|
|
311
342
|
| `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`
|
|
343
|
+
| `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). Workflow scripts are the exception: as in Claude Code, only their `agent()` option decides. `none`, `no`, and `false` are accepted spellings of `off` |
|
|
313
344
|
| `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
345
|
| `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
|
|
315
346
|
| `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
|
|
@@ -323,7 +354,11 @@ All fields are optional — sensible defaults for everything.
|
|
|
323
354
|
| `isolated` | `false` | Hermetic specialist mode: forces `extensions: false` + `skills: false` + drops `ext:` selectors. Only built-in tools. Distinct from `isolation: worktree` (filesystem) |
|
|
324
355
|
| `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
|
|
325
356
|
|
|
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.
|
|
357
|
+
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. Other spawn paths differ per field:
|
|
358
|
+
|
|
359
|
+
- `isolated` holds on every spawn path, including `@handle` mentions, workflows and [cross-extension RPC](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/rpc.md).
|
|
360
|
+
- `isolation` holds on every path except workflows: as in Claude Code, only the script's `agent()` option decides there.
|
|
361
|
+
- `inherit_context` applies only to `Agent` tool and nested spawns, including default-mode `@handle` mentions, which spawn through the `Agent` tool. As in Claude Code, where only a fork inherits the conversation, every other spawn starts fresh unless its caller asks: workflow children and scheduled runs never inherit, and an RPC caller opts in with `inheritContext`.
|
|
327
362
|
|
|
328
363
|
**Forgiving `model:` resolution.** A `model:` pin is matched against pi's model registry tolerantly, so cosmetic id variations don't silently drop the agent back to the parent's model: `.` and `-` are treated as equivalent in version numbers (`claude-haiku-4.5` ≡ `claude-haiku-4-5`), a trailing `-YYYYMMDD` date stamp is optional (`anthropic/claude-haiku-4-5-20251001` matches an undated registry id and vice-versa), and a `provider/modelId` whose named provider doesn't carry that model retries the bare id against every provider. Precedence is **exact → fuzzy under the named provider → same model under any provider → unavailable**, so an exact match always wins and dated snapshots aren't conflated. If nothing resolves, the pin can't run and the agent inherits the parent model — `/agents → Agent types` flags this case as `(unavailable, fallback: inherit)` and shows the resolved target `(→ provider/id)` when resolution lands on a different provider or version than configured. (This is distinct from [Model Scope](#model-scope) enforcement, which matches the `enabledModels` allowlist by _exact_ entry.)
|
|
329
364
|
|
|
@@ -363,9 +398,11 @@ tools: "*, ext:mcp/search" # built-ins plus one extension tool
|
|
|
363
398
|
|
|
364
399
|
extensions: false # no extensions load
|
|
365
400
|
extensions: [mcp] # only mcp loads
|
|
401
|
+
extensions: [builtin:mcp, builtin:codemode] # pi's built-in MCP + codemode
|
|
366
402
|
extensions: ["*", "/abs/foo.ts"] # all defaults plus one path-loaded extension
|
|
367
403
|
|
|
368
404
|
exclude_extensions: pi-notify # everything except pi-notify (with extensions: true)
|
|
405
|
+
exclude_extensions: builtin:mcp # everything except pi's built-in MCP
|
|
369
406
|
|
|
370
407
|
# Specialist: load one extension, expose only one of its tools, keep built-ins
|
|
371
408
|
extensions: [mcp]
|
|
@@ -379,6 +416,8 @@ A few rules the examples don't make obvious:
|
|
|
379
416
|
- `extensions:` is the sole loading authority. `ext:foo` in `tools:` narrows what surfaces; it can't load `foo` on its own. Mismatches fire `extension-error:…` warnings.
|
|
380
417
|
- 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.
|
|
381
418
|
- Extension names match case-insensitively (`[Mcp]` = `[mcp]`); tool names in `ext:foo/bar` stay case-sensitive.
|
|
419
|
+
- pi's built-in extensions (`builtin:mcp`, `builtin:codemode`, `builtin:tool-search`) load in subagents as they do in the main session, so with `extensions: true` **every subagent starts its own set of `mcp.json` servers**, which stay up until the subagent's session is disposed (on quit, or ~10 minutes after it finishes). Address them by the full `builtin:` name — bare `[mcp]` matches a third-party extension named `mcp`, not the built-in — and narrow their tools with `ext:builtin:mcp/<tool>`. MCP tools are reached through codemode (or `tool_search` for `deferred` servers), so an `ext:` allowlist must name that bridge too, as pi's `--tools read,codemode,'mcp__*'` does: `tools: "read, ext:builtin:codemode, ext:builtin:mcp/mcp__radius__search"`. Opt out per agent with `exclude_extensions: builtin:mcp`, or everywhere with `-builtin:mcp` in pi's `extensions` setting; starting pi with `--no-mcp` keeps MCP out of subagents too, and `--no-extensions` keeps all three built-ins out. MCP tools stay undeclared until codemode or `tool_search` reaches them, as in the main session, and a codemode script can only call tools inside the agent's scope. Out-of-scope tools are blocked but can still appear in codemode's `searchTools()` and `tool_search` results.
|
|
420
|
+
- Subagents inherit the parent session's **project trust**: in a project you did not trust, a subagent loads none of its `.pi` extensions or settings and never starts the servers in its `.pi/mcp.json`. A subagent spawned into another directory (RPC `cwd`) treats that directory as untrusted.
|
|
382
421
|
- 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.
|
|
383
422
|
- 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.
|
|
384
423
|
- 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.
|
|
@@ -398,6 +437,40 @@ A few rules the examples don't make obvious:
|
|
|
398
437
|
|
|
399
438
|
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
439
|
|
|
440
|
+
### Shipping agents in a pi package
|
|
441
|
+
|
|
442
|
+
A pi package can ship subagents the same way it ships skills, so a reusable agent travels with the package that needs it instead of being copied into every project's `.pi/agents/` ([#109](https://github.com/tintinweb/pi-subagents/issues/109)). Declare the paths in the package's `package.json`:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"name": "my-subagents",
|
|
447
|
+
"keywords": ["pi-package"],
|
|
448
|
+
"pi": {
|
|
449
|
+
"subagents": {
|
|
450
|
+
"agents": ["./agents"],
|
|
451
|
+
"workflows": ["./workflows"]
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Users get the agents by installing the package with pi — no copy step:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
pi install npm:my-subagents # or git:…, or a local path; -l for project scope
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Four rules define the feature:
|
|
464
|
+
|
|
465
|
+
- **Declaration is required.** Pi falls back to convention directories (`skills/`, `prompts/`, …) for a package with no `pi` key at all; this extension never does. A package carrying an `agents/` folder for some other tool contributes nothing until it names it — the author's half of the agreement, where installing is yours.
|
|
466
|
+
- **Only pi's own packages count.** Discovery reads `packages[]` from pi's `settings.json`, global and project, through pi's package manager. `node_modules` is never scanned, and a package listed only by an untrusted project stays invisible until you trust it.
|
|
467
|
+
- **Package agents rank last.** Any `.pi/agents/`, `.agents/agents/` or global file of the same name wins, so a package can offer a `reviewer` but never take the name away from you. Same tier pi gives package-provided skills, and Claude Code plugin agents.
|
|
468
|
+
- **They are read-only in `/agents`,** badged `▪`, offering _Eject_ (an editable copy that shadows the original) and _Disable_ (an `enabled: false` stub that does the same).
|
|
469
|
+
|
|
470
|
+
`packageAgents` and `packageWorkflows` in [`subagents.json`](#persistent-settings) default to `true`; set either to `false` or to a list of package names to narrow it. Installing a package is already the trust decision — pi runs that package's `extensions/` and puts its `skills/` in the system prompt with no further prompt, so a declared `.md` agent is strictly less privileged than what it already runs.
|
|
471
|
+
|
|
472
|
+
**Full guide:** [`docs/packages.md`](https://github.com/HerbertGao/pi-extensions/blob/master/packages/pi-subagents/docs/packages.md) — manifest syntax and the four accepted spellings, `!` exclusions, the development loop, naming, precedence, and troubleshooting.
|
|
473
|
+
|
|
401
474
|
## Tools
|
|
402
475
|
|
|
403
476
|
### `Agent`
|
|
@@ -413,8 +486,8 @@ Launch a sub-agent.
|
|
|
413
486
|
| `model` | string | no | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`). Resolved tolerantly (`.`/`-` and a trailing date stamp interchangeable) with provider fallback |
|
|
414
487
|
| `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
|
|
415
488
|
| `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
|
|
489
|
+
| `run_in_background` | boolean | no | Defaults to `true`; `false` blocks and returns the result inline, until [`ctrl+b`](#sending-a-foreground-agent-to-the-background) hands it to the background |
|
|
490
|
+
| `resume` | string | no | Agent ID to resume a previous session. The resumed run gets a fresh `max_turns` budget |
|
|
418
491
|
| `isolated` | boolean | no | No extension/MCP tools |
|
|
419
492
|
| `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
493
|
| `inherit_context` | boolean | no | Fork parent conversation into agent |
|
|
@@ -509,7 +582,7 @@ Send a steering message to a running agent. The message interrupts after the cur
|
|
|
509
582
|
↑↓ select · ⏎ open · f filter · x stop · esc close · c convo
|
|
510
583
|
```
|
|
511
584
|
|
|
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
|
|
585
|
+
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 in regular mode; 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
586
|
|
|
514
587
|
The run itself takes five keys, and the footer offers each only while it can actually do something:
|
|
515
588
|
|
|
@@ -540,8 +613,8 @@ Create new agent ← manual wizard or AI-generated
|
|
|
540
613
|
Settings ← max concurrency (background + foreground), max turns, grace turns, join mode
|
|
541
614
|
```
|
|
542
615
|
|
|
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
|
|
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:
|
|
616
|
+
- **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). On a live foreground run, press `b` to hand the same session and in-flight work to the background without stopping or restarting it — the blocked `Agent` call returns the agent ID and the run notifies on completion like any background agent. 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, but no longer reaches one that has been handed off — `x` here does). 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).
|
|
617
|
+
- **Agent types** — unified list with source indicators: `•` (project), `◦` (global), `▪` ([package](docs/packages.md)), `✕` (disabled), explained by a legend naming only the badges actually on screen. Each row shows the agent's model, and the highlighted agent's full description appears below the list — prefixed with the declaring package's name for a `▪` agent, since the badge alone doesn't say _which_ package. 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:
|
|
545
618
|
- **Default agents** (no override): Eject (export as `.md`), Disable
|
|
546
619
|
- **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
|
|
547
620
|
- **Custom agents**: Edit, Disable, Delete
|
|
@@ -559,6 +632,8 @@ Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
|
|
|
559
632
|
2. Up to 5 grace turns to finish cleanly
|
|
560
633
|
3. Hard abort only after the grace period
|
|
561
634
|
|
|
635
|
+
A resumed agent gets a fresh budget: `max_turns` is counted from the resume prompt, resolved as for a spawn — the agent file's `max_turns`, else the resume call's, else the limit the agent was spawned with, else the default.
|
|
636
|
+
|
|
562
637
|
| Status | Meaning | Icon |
|
|
563
638
|
| ----------- | ----------------------------- | ---------- |
|
|
564
639
|
| `completed` | Finished naturally | `✓` green |
|
|
@@ -580,17 +655,18 @@ The foreground pool does not cover `resume`: a foreground resume reopens an exis
|
|
|
580
655
|
|
|
581
656
|
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.
|
|
582
657
|
|
|
658
|
+
Sending a foreground agent to the background moves its slot: it gives up the foreground slot (so a queued blocking `Agent` call can start) and takes a background one. If the background pool is already full it runs above capacity rather than being suspended, and queued background work waits until the count drops back under the limit.
|
|
659
|
+
|
|
583
660
|
## Join Strategies
|
|
584
661
|
|
|
585
|
-
When background agents complete, they notify the main agent. The **join mode**
|
|
662
|
+
When background agents complete, they notify the main agent. As in Claude Code, a notification reaches a busy parent after its current batch of tool calls, before its next model call — running tools are not interrupted — and everything that finished during that batch arrives as one message. An idle parent gets a turn about 200 ms after a completion, together with anything else that finished in that window. If you interrupt the parent (Esc), what was waiting is kept and delivered once the interrupted turn has stopped, starting a turn — as Claude Code does. The **join mode** decides whether a result waits for its siblings. It applies only to background agents.
|
|
586
663
|
|
|
587
|
-
| Mode | Behavior
|
|
588
|
-
| ----------------- |
|
|
589
|
-
| `smart` (default) |
|
|
590
|
-
| `
|
|
591
|
-
| `group` | Force grouping even when spawning a single agent. Useful when you know more agents will follow. |
|
|
664
|
+
| Mode | Behavior |
|
|
665
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
666
|
+
| `smart` (default) | No waiting: each result is delivered at the parent's next boundary, as in Claude Code. `async` is accepted as an older name for it. |
|
|
667
|
+
| `group` | The background agents spawned in one parent turn report together, in one notification once all of them have finished. Fewer parent turns, at the cost of an early result waiting for the slowest sibling. |
|
|
592
668
|
|
|
593
|
-
**Timeout behavior:**
|
|
669
|
+
**Timeout behavior (`group`):** a 30-second timeout starts after the first agent of a group completes. If not all agents finish in time, a partial notification is sent with completed results and remaining agents continue with a shorter 15-second re-batch window for stragglers.
|
|
594
670
|
|
|
595
671
|
**Configuration:**
|
|
596
672
|
|
|
@@ -621,7 +697,7 @@ When on, each subagent spawn's effective model is validated against pi's own `en
|
|
|
621
697
|
|
|
622
698
|
## Persistent Settings
|
|
623
699
|
|
|
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:
|
|
700
|
+
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, package agents on/off, package workflows 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:
|
|
625
701
|
|
|
626
702
|
- **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
|
|
627
703
|
- **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
|
|
@@ -634,6 +710,8 @@ Runtime tuning values set via `/agents` → Settings (max concurrency, max foreg
|
|
|
634
710
|
|
|
635
711
|
**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`.
|
|
636
712
|
|
|
713
|
+
**Package agents / package workflows** (`packageAgents` and `packageWorkflows`, both default `true`): whether agents and workflow scripts declared by installed pi packages are loaded, and from which packages. `true` admits every declaring package, `false` none, and a list admits only the packages it names — matched case-insensitively against the unscoped short name (`@acme/tools` → `tools`), the full package name, or the settings source string. The two are separate so package agents can be accepted while package workflow scripts are refused; an agent is markdown handed to a model, a workflow is `.js` this extension executes. Toggle both from `/agents → Settings`; an allowlist is hand-written in `subagents.json`, and toggling the row replaces it (the toast names what it replaced). See [Shipping agents in a pi package](#shipping-agents-in-a-pi-package).
|
|
714
|
+
|
|
637
715
|
**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).
|
|
638
716
|
|
|
639
717
|
**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"`.
|
|
@@ -734,21 +812,23 @@ Every project now starts with concurrency 16 and grace 10, without ever touching
|
|
|
734
812
|
|
|
735
813
|
Agent lifecycle events are emitted via `pi.events.emit()` so other extensions can react:
|
|
736
814
|
|
|
737
|
-
| Event | When
|
|
738
|
-
| ---------------------------- |
|
|
739
|
-
| `subagents:created` | `Agent`-tool background spawn,
|
|
740
|
-
| `subagents:started` | Agent transitions to running (including queued→running)
|
|
741
|
-
| `subagents:completed` | Agent finished successfully (background and foreground)
|
|
742
|
-
| `subagents:failed` | Agent errored, stopped, or aborted (background and foreground)
|
|
743
|
-
| `subagents:steered` | Steering message accepted — fires for a _queued_ steer as well as a delivered one
|
|
744
|
-
| `subagents:
|
|
745
|
-
| `subagents:
|
|
746
|
-
| `subagents:
|
|
747
|
-
| `subagents:
|
|
748
|
-
| `subagents:
|
|
749
|
-
| `subagents:
|
|
750
|
-
|
|
751
|
-
|
|
815
|
+
| Event | When | Key fields |
|
|
816
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
817
|
+
| `subagents:created` | `Agent`-tool background spawn, a detached resume, or a foreground `Agent` call handed to the background (after its `started`) — **not** cross-extension RPC, scheduler, or `@handle` spawns, which are first seen at `subagents:started` | `id`, `type`, `description`, `isBackground` (always `true`) |
|
|
818
|
+
| `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
|
|
819
|
+
| `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` |
|
|
820
|
+
| `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 |
|
|
821
|
+
| `subagents:steered` | Steering message accepted from any path (`steer_subagent`, `@handle`, the viewers, `subagents:rpc:steer`) — fires for a _queued_ steer as well as a delivered one | `id`, `message` |
|
|
822
|
+
| `subagents:usage` | Every assistant message any agent spends on — top-level, nested and workflow children alike, live while it runs | Always: `id`, `type`, `description`, `depth`, `usage` (that one message's spend as a pi `Usage`; never emitted for a message that spent nothing). Omitted when unknown, never `undefined`: `model` (`provider/id`) and `thinking` (the effective level) until the session has reported them; `requestedThinking`, only when the agent did not get the level it asked for; `parentAgentId`, nested agents only; `workflowId`, a workflow's agents only |
|
|
823
|
+
| `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
|
|
824
|
+
| `subagents:disposed` | End of `session_shutdown`, after every agent is stopped and every child session has shut down — no `subagents:usage` can follow it. A ledger flushing in its own `session_shutdown` may run before the agents are aborted; flush again here, **synchronously** (`writeFileSync`, not `fs.promises`): `emit` does not await listeners and pi calls `process.exit` once the shutdown handlers return, so nothing after a listener's first `await` is guaranteed to run | `{}` |
|
|
825
|
+
| `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
|
|
826
|
+
| `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
|
|
827
|
+
| `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `{}` (empty object) |
|
|
828
|
+
| `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
|
|
829
|
+
| `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
|
|
830
|
+
|
|
831
|
+
The four agent-lifecycle events — `subagents:started`, `:completed`, `:failed`, `:compacted` — are emitted for **top-level agents only**; nested subagents and a workflow's children report through the parent or workflow that owns them. `subagents:usage` is the one event that is not scoped, on purpose: it is a ledger, not a lifecycle. It fires once per message for every agent, so summing `usage.cost.total` across it gives what this session's subagents spent, each message counted exactly once under the agent that spent it — unlike the ancestor records, which fold a nested child's spend into every parent. It fires regardless of `reportUsage`, which decides only whether the parent session's own totals include the spend.
|
|
752
832
|
|
|
753
833
|
`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
834
|
|
|
@@ -842,7 +922,22 @@ pi.events.emit("subagents:rpc:consume", {
|
|
|
842
922
|
})
|
|
843
923
|
```
|
|
844
924
|
|
|
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
|
|
925
|
+
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 still reaches a parent that has already seen the result, costing a model call 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.
|
|
926
|
+
|
|
927
|
+
### Steer
|
|
928
|
+
|
|
929
|
+
Deliver a message to a running agent you spawned, when something it needs arrives mid-run (the bus-side half of `steer_subagent`):
|
|
930
|
+
|
|
931
|
+
```typescript
|
|
932
|
+
pi.events.emit("subagents:rpc:steer", {
|
|
933
|
+
requestId: crypto.randomUUID(),
|
|
934
|
+
agentId: "agent-id-here",
|
|
935
|
+
message:
|
|
936
|
+
"A second check failed on the same head; include it before you push.",
|
|
937
|
+
})
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Same ownership guard as stop; `Agent is not running` once the agent has settled. A successful steer emits `subagents:steered`.
|
|
846
941
|
|
|
847
942
|
Reply channels are scoped per `requestId`, so concurrent requests don't interfere.
|
|
848
943
|
|
|
@@ -896,7 +991,7 @@ A worktree is a _copy_, so the agent cannot see uncommitted or staged changes in
|
|
|
896
991
|
Three levers, from narrowest to broadest:
|
|
897
992
|
|
|
898
993
|
- **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.
|
|
994
|
+
- **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. Workflow `agent()` calls are the exception: as in Claude Code, only the script's option decides there.
|
|
900
995
|
- **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
996
|
|
|
902
997
|
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.
|
|
@@ -960,7 +1055,8 @@ src/
|
|
|
960
1055
|
|
|
961
1056
|
# Agent registry
|
|
962
1057
|
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
|
|
1058
|
+
custom-agents.ts # Load user-defined agents from .pi/agents/, .agents/agents/, global agents, and pi packages
|
|
1059
|
+
package-resources.ts # Agents/workflows declared by installed pi packages (pi.subagents manifest)
|
|
964
1060
|
agent-types.ts # Unified agent registry (defaults + user), tool name resolution
|
|
965
1061
|
agent-file-toggle.ts # Locate/edit an agent's .md: enabled: toggle, eject to frontmatter
|
|
966
1062
|
agent-color.ts # Claude Code/Agency Agents name color parsing and badge rendering
|
|
@@ -971,7 +1067,8 @@ src/
|
|
|
971
1067
|
nested-tools.ts # Delegation tools handed to subagents (nested spawn/collect/steer)
|
|
972
1068
|
child-context.ts # AsyncLocalStorage flag marking work done for a child session
|
|
973
1069
|
abortable.ts # Race a wait against Esc without cancelling the background child
|
|
974
|
-
|
|
1070
|
+
completion-outbox.ts # Deliver completion notifications at the parent's next boundary, coalesced
|
|
1071
|
+
group-join.ts # `group` join mode: one notification per turn's batch, with timeout
|
|
975
1072
|
status-note.ts # Honest status note + salvaged partial output for non-normal outcomes
|
|
976
1073
|
usage.ts # Token usage shapes, accumulators, session-stats readers
|
|
977
1074
|
|
|
@@ -982,7 +1079,7 @@ src/
|
|
|
982
1079
|
model-scope.ts # scopeModels allowlist policy, shared by top-level and nested tools
|
|
983
1080
|
mention.ts # `@handle message` grammar: suggestion triggers and send parsing
|
|
984
1081
|
mention-clone.ts # Run a mention's turn in a cloned conversation, off the main chat
|
|
985
|
-
cross-extension-rpc.ts # RPC handlers
|
|
1082
|
+
cross-extension-rpc.ts # ping/spawn/stop/consume/steer RPC handlers over pi.events
|
|
986
1083
|
|
|
987
1084
|
# Scheduling
|
|
988
1085
|
schedule.ts # SubagentScheduler: cron / +10m / interval / ISO dispatch
|