Delegate tasks to specialized subagents.
pi install npm:oira666_pi-subagentOr via git:
pi install git:github.com/gee666/pi-subagent.gitpi remove npm:oira666_pi-subagentEach subagent runs as a separate pi process — fully isolated memory, its own model/tool loop.
Processes are spawned via the operating system and communicate through JSON-line stdout.
Subagent sessions are persisted separately under a sessions-subagents directory (a sibling of Pi's normal sessions directory), so they can be resumed without mixing into the main session list.
- Full OS-level isolation — a crashed subagent cannot affect the parent
- True parallel execution across all CPU cores
- Each subprocess boots a fresh Node.js runtime
- Uses the same Pi CLI entrypoint as the parent process when available
Each subagent receives only the task string. The main agent in turn receives only the final text output from subagents (no tool calls, no reasoning).
The delegation tool is called subagents (older sessions may contain the
legacy name subagent, which is still recognized when reading history):
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }] }Multiple tasks run in parallel:
{
"tasks": [
{ "agent": "code-writer", "task": "Draft the implementation" },
{ "agent": "code-reviwer", "task": "Review the plan" }
]
}Each task supports agent (the agent type to spawn) and task.
Three fallback agents ship with the extension (used when no user/project agents are configured):
code-writer— implementation and refactoringcode-reviwer— code review and risk findingcode-architect— technical design and approach selection
Create Markdown files with YAML frontmatter:
- User agents:
~/.pi/agent/agents/*.md - Env agents:
$PI_CODING_AGENT_DIR/agents/*.md(whenPI_CODING_AGENT_DIRis set) - Project agents:
.pi/agents/*.md(may prompt for confirmation — seePI_SUBAGENT_CONFIRM_PROJECT_AGENTS)
Agent discovery priority (highest wins on name collision): project > env > user. Built-in agents are only used as a fallback when all three locations are empty.
---
name: writer
description: Expert technical writer
model: anthropic/claude-3-5-sonnet
thinking: low
tools: read,write
---
You are an expert technical writer focused on clarity and conciseness.| Field | Required | Default | Description |
|---|---|---|---|
name |
Yes | — | Agent identifier used in tool calls |
description |
Yes | — | What the agent does (shown to the main agent) |
model |
No | Pi default | Override model, e.g. anthropic/claude-3-5-sonnet |
thinking |
No | Pi default | off, minimal, low, medium, high, xhigh |
tools |
No | read,bash,edit,write |
Comma-separated built-in tools |
Available tools: read, bash, edit, write.
The Markdown body becomes the agent's system prompt (appended to Pi's default, not replacing it).
Depth and cycle guards prevent runaway recursive delegation.
| Config | Default | Description |
|---|---|---|
--subagent-max-depth / PI_SUBAGENT_MAX_DEPTH |
3 |
Max delegation depth (0 disables delegation) |
--subagent-prevent-cycles / PI_SUBAGENT_PREVENT_CYCLES |
true |
Block same agent in delegation chain |
pi --subagent-max-depth 2 # one nested level
pi --subagent-max-depth 0 # disable delegation entirely
pi --no-subagent-prevent-cycles # allow cycles (not recommended)| Env Var | Default | Description |
|---|---|---|
PI_SUBAGENT_MAX_PARALLEL_TASKS |
30 |
Max tasks per single call |
PI_SUBAGENT_MAX_CONCURRENCY |
8 |
Max subagents running simultaneously |
Subagent tool calls and live activity lines render a dim hh:mm:ss timestamp
(call start, per-subagent run start, and each live log entry).
In the interactive TUI the extension publishes the combined total usage line
(parent + all subagents, recursively) via Pi's normal ctx.ui.setStatus()
status line. Pi renders all extension statuses on the same footer status line.
While a subagents tool call is running, mid-stream steering input can be broadcast to one or more child agents. The extension uses Pi's InputEvent.streamingBehavior metadata when available, so idle prompts and queued follow-ups continue to the parent normally; only true steer inputs open the broadcast routing prompt.
Subagent subprocesses save sessions in sessions-subagents. When a main Pi session is resumed and its latest branch contains an unfinished subagents tool call (aborted, errored, or closed by Pi's synthetic unfinished-tool error), the extension can resume that delegation from the saved subagent sessions.
The same detection also runs after navigating the session tree in the TUI (Esc navigation): if you jump back to a point whose branch ends in an unfinished subagents call, the extension offers to resume those subagents from their saved sessions.
- TUI mode asks: Resume subagents?
- Non-UI modes (
pi -p, JSON/RPC) resume automatically. - Already-finished subagents are reused as completed; unfinished ones continue from their own saved sessions.
- Nested subagents use the same mechanism recursively.
| Env Var | Default | Description |
|---|---|---|
PI_SUBAGENT_RESUME_PROMPT |
true |
Set to false to suppress the TUI yes/no prompt and auto-resume. |
PI_SUBAGENT_DISABLE_RESUME |
false |
Set to true to disable automatic subagent resume detection entirely. |
Note: crash-resume covers subagents calls only. An interrupted resume_subagents call is not replayed automatically — the model can simply issue it again, since names stay valid (see below).
Every subagent run is assigned a unique, durable name derived from its agent
type plus a per-type counter — code-writer-01, code-writer-02,
code-reviewer-01, ... The name is returned together with the subagent's
results and shown in the TUI tree.
The resume_subagents tool continues named subagents with a new task while
preserving their full previous context:
{ "resumes": [{ "subagent": "code-writer-01", "task": "Now also update the tests." }] }Naming is deliberately unambiguous: agent (in subagents) selects an agent
type to spawn; subagent (in resume_subagents) addresses an already-run
subagent instance by its unique name.
- All resumes in one call run in parallel.
- Names are unique within one delegation tree (everything spawned from one top-level session) and are persisted in a registry file under the subagent session root, so they survive restarts: you can resume a subagent in a later session of the same conversation.
- The registry location and the session's ownership identity are stored in the
session itself (a custom metadata entry). Pi assigns resumed/branched
sessions a new internal session id, but the persisted identity (plus a
parentSessionancestor-walk fallback for sessions created before it existed) keeps the whole tree's names alive across process restarts — for the top-level session and every nested subagent alike. - Ownership & forks: the agent that spawned a subagent (its owner) resumes the original session. A parent may pass names to its own subagents (in their task text); when a child resumes a name created by an ancestor, it transparently gets a private fork of that subagent (a copy of its session), so the owner's copy is never polluted by the child's continuation. Each child gets exactly one fork per name and keeps reusing it on subsequent resumes. Fork session locations are persisted too.
- Concurrent resumes of the same target are rejected (in-process and cross-process via crash-tolerant registry markers), because two processes continuing the same session file would corrupt it.
- If the original agent definition file has been removed, the resume still works: the registry remembers the agent's model/tool restrictions and the session itself carries the context.
| Env Var | Default | Description |
|---|---|---|
DISABLE_RESUMABLE_SUBAGENTS |
false |
Set to true/on/1 to disable resumable subagents entirely: no names are allocated, the resume_subagents tool is not registered, and the system prompt omits the feature. |
PI_SUBAGENT_NAMES_FILE |
(internal) | Path of the shared name registry, propagated to child processes so the whole delegation tree allocates unique names. |
| Env Var | Description |
|---|---|
PI_CODING_AGENT_DIR |
Base path for an additional agents directory ($PI_CODING_AGENT_DIR/agents/*.md). Agents here override user agents but are overridden by project agents. Built-in agents are only used when user, env, and project locations all yield zero agents. |
Flags passed to the parent pi process are forwarded to subagent child
processes, so they inherit the same provider, API key, model, and other runtime settings. Flags the
extension manages itself are blocked from being forwarded.
Always forwarded verbatim:
| Flag(s) | Purpose |
|---|---|
--provider |
AI provider |
--api-key |
API key |
--system-prompt |
Base system prompt override |
--session-dir |
Session storage directory |
--models |
Model cycling list |
--skill, --no-skills/-ns |
Skill loading |
--prompt-template, --no-prompt-templates/-np |
Prompt templates |
--theme, --no-themes |
Themes |
--verbose |
Verbose startup output |
| Unknown/custom flags | Forwarded with heuristic value detection |
Forwarded as fallback (agent frontmatter overrides if set):
| Flag | Overridden by |
|---|---|
--model |
model: in agent frontmatter |
--thinking |
thinking: in agent frontmatter |
--tools / --no-tools |
tools: in agent frontmatter |
Never forwarded (managed by the extension itself):
--mode, -p/--print, --session/--no-session, --continue, --resume,
--append-system-prompt, --offline, --extension/-e, --no-extensions/-ne,
--subagent-max-depth, --subagent-prevent-cycles, --export, --list-models,
--help, --version.
When running pi programmatically with --mode rpc (or --mode json), the stream contains
tool_result_end events whenever the agent completes a subagents tool call. The details field
of these events carries the full stats for that delegation — including recursive usage and tool
call counts from all subagents in the tree.
tool_result_end
└── message
├── role: "toolResult"
├── toolName: "subagents"
├── toolCallId: string
├── isError: boolean
├── content: [{ type: "text", text: "<final output>" }]
└── details: SubagentDetails
interface SubagentDetails {
// Execution metadata
mode: "single" | "parallel"; // one task vs multiple parallel tasks
delegationMode: "spawn"; // always "spawn" (kept for backward-compatible serialization)
projectAgentsDir: string | null; // path to .pi/agents/ dir if used
// Individual agent results (one per task)
results: SingleResult[];
// ── Stats summary (own + all descendants, recursively) ──────────────────
aggregatedUsage: UsageStats; // token counts and cost, full tree
aggregatedToolCalls: ToolCallCounts; // { toolName: callCount }, full tree
// ── Per-agent breakdown ──────────────────────────────────────────────────
usageTree: UsageTreeNode[]; // one root node per result
}
interface SingleResult {
agent: string; // agent name
agentSource: "user" | "project" | "builtin" | "unknown";
task: string; // task string passed to this agent
exitCode: number; // 0 = success, >0 = error, -1 = still running
messages: Message[]; // full conversation history of the subagent
stderr: string;
usage: UsageStats; // this agent's OWN token usage only
toolCalls: ToolCallCounts; // this agent's OWN tool calls only
model?: string;
stopReason?: string; // "end_turn" | "error" | "aborted" | ...
errorMessage?: string;
}
interface UsageStats {
input: number; // input tokens
output: number; // output tokens
cacheRead: number; // cache read tokens
cacheWrite: number; // cache write tokens
cost: number; // total cost in USD
contextTokens: number; // snapshot: last context window size (not summed in aggregates)
turns: number; // number of assistant turns
}
// toolName → call count, e.g. { "bash": 5, "read": 3, "subagents": 1 }
type ToolCallCounts = Record<string, number>;
interface UsageTreeNode {
agent: string;
task: string;
ownUsage: UsageStats; // only this agent's turns
ownToolCalls: ToolCallCounts; // only this agent's tool calls
aggregatedUsage: UsageStats; // ownUsage + all children recursively
aggregatedToolCalls: ToolCallCounts; // ownToolCalls + all children recursively
children: UsageTreeNode[]; // one node per nested subagent invocation
}SingleResult.usageandSingleResult.toolCallscover only that one agent's own work — not its children. Children run in separate processes; their tokens never appear in the parent's usage.aggregatedUsage/aggregatedToolCallsonSubagentDetails(and on eachUsageTreeNode) are the correct totals to use when you want the cost or tool call count for an entire delegation subtree.contextTokensis a point-in-time snapshot of the context window size at the last turn of that agent. It is not summed in aggregated stats (it would be meaningless as a cross-process sum).toolCallsincludes all tool calls an agent made, including the"subagents"call itself. You can use the"subagents"count to see how many nested delegations an agent spawned.
The scenario below: main agent delegates to code-writer, which does some file work and then
delegates to code-reviwer before finishing.
{
"type": "tool_result_end",
"message": {
"role": "toolResult",
"toolName": "subagents",
"toolCallId": "toolu_01XYZ",
"isError": false,
"content": [
{
"type": "text",
"text": "Feature implemented and reviewed. Added validation logic in auth.ts and updated the test suite."
}
],
"details": {
"mode": "single",
"delegationMode": "spawn",
"projectAgentsDir": null,
"aggregatedUsage": {
"input": 2180,
"output": 615,
"cacheRead": 940,
"cacheWrite": 120,
"cost": 0.0079,
"contextTokens": 0,
"turns": 3
},
"aggregatedToolCalls": {
"read": 3,
"bash": 2,
"edit": 1,
"subagents": 1
},
"usageTree": [
{
"agent": "code-writer",
"task": "Implement the auth feature and have it reviewed",
"ownUsage": {
"input": 1380,
"output": 365,
"cacheRead": 540,
"cacheWrite": 120,
"cost": 0.0058,
"contextTokens": 2840,
"turns": 2
},
"ownToolCalls": {
"read": 1,
"bash": 1,
"edit": 1,
"subagents": 1
},
"aggregatedUsage": {
"input": 2180,
"output": 615,
"cacheRead": 940,
"cacheWrite": 120,
"cost": 0.0079,
"contextTokens": 0,
"turns": 3
},
"aggregatedToolCalls": {
"read": 3,
"bash": 2,
"edit": 1,
"subagents": 1
},
"children": [
{
"agent": "code-reviwer",
"task": "Review the auth implementation in auth.ts",
"ownUsage": {
"input": 800,
"output": 250,
"cacheRead": 400,
"cacheWrite": 0,
"cost": 0.0021,
"contextTokens": 1450,
"turns": 1
},
"ownToolCalls": {
"read": 2,
"bash": 1
},
"aggregatedUsage": {
"input": 800,
"output": 250,
"cacheRead": 400,
"cacheWrite": 0,
"cost": 0.0021,
"contextTokens": 0,
"turns": 1
},
"aggregatedToolCalls": {
"read": 2,
"bash": 1
},
"children": []
}
]
}
],
"results": [
{
"agent": "code-writer",
"agentSource": "builtin",
"task": "Implement the auth feature and have it reviewed",
"exitCode": 0,
"stopReason": "end_turn",
"model": "claude-opus-4-5",
"stderr": "",
"usage": {
"input": 1380,
"output": 365,
"cacheRead": 540,
"cacheWrite": 120,
"cost": 0.0058,
"contextTokens": 2840,
"turns": 2
},
"toolCalls": {
"read": 1,
"bash": 1,
"edit": 1,
"subagents": 1
},
"messages": [
"... full conversation history of code-writer (includes the nested subagent tool_result) ..."
]
}
]
}
}
}If you are consuming the JSON stream programmatically and want to track the total cost and tool
usage across all subagent work in a session, listen for every tool_result_end event where
message.toolName === "subagents" (or the legacy "subagent" in old sessions) and sum message.details.aggregatedUsage across them.
let totalCost = 0;
const totalToolCalls = {};
for await (const line of jsonLines) {
const event = JSON.parse(line);
if (
event.type === "tool_result_end" &&
["subagents", "subagent", "resume_subagents"].includes(event.message?.toolName) &&
event.message?.details
) {
const { aggregatedUsage, aggregatedToolCalls } = event.message.details;
totalCost += aggregatedUsage.cost;
for (const [tool, count] of Object.entries(aggregatedToolCalls)) {
totalToolCalls[tool] = (totalToolCalls[tool] ?? 0) + count;
}
}
}Note: if you also track the main agent's own usage from message_end events, make sure not to
double-count the subagent costs there — the main agent's own token usage (from its own message_end
events) does not include subagent work.
If you want the agent to create new subagent definition files for itself, install the create-subagent skill. Once installed, the agent will know how to scaffold new .md agent files in the right location with correct frontmatter.
Inspired by vaayne/agent-kit and mariozechner/pi-mono.
MIT