oira666_pi-subagent
v0.1.14
Published
Subagent extension for Pi coding agent. Delegate tasks to specialized agents.
Downloads
1,519
Maintainers
Readme
Pi Subagent
Delegate tasks to specialized subagents with configurable context modes (spawn / fork).
Install
pi install npm:oira666_pi-subagentOr via git:
pi install git:github.com/gee666/pi-subagent.gitRemove
pi remove npm:oira666_pi-subagentHow It Works
The extension supports two execution modes, selectable via PI_SUBAGENTS_MODE.
Execution mode: subprocess (default)
Each 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.
- 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 (adds ~200–500 ms per subagent)
- Requires the
pibinary onPATH
Execution mode: sdk
Each subagent runs as an in-process AgentSession created via the pi SDK — no new process is spawned.
- No spawn overhead — sessions start in milliseconds
- No temp files for system prompts or fork snapshots
- Concurrency through the Node.js event loop (fine for I/O-bound LLM work)
- All sessions share the same memory and event loop
Context modes
spawn (default) — Child receives only the task string. Best for isolated work, lower cost.fork — Child receives a snapshot of the current session context + task. Best for follow-up work.
The main agent receives only the final text output from subagents (no tool calls, no reasoning).
Tool Call Shape
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }], "mode": "spawn" }Multiple tasks run in parallel:
{
"tasks": [
{ "agent": "code-writer", "task": "Draft the implementation" },
{ "agent": "code-reviwer", "task": "Review the plan" }
],
"mode": "fork"
}Each task supports agent, task, and optional cwd.
Bundled Agents
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
Defining Agents
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.Frontmatter Fields
| 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).
Execution Mode
| Env Var | Default | Values | Description |
| --------------------- | ------------ | -------------------- | -------------------------------------- |
| PI_SUBAGENTS_MODE | subprocess | subprocess / sdk | How subagent sessions are created |
# Run subagents as in-process SDK sessions (faster startup, no spawn overhead)
PI_SUBAGENTS_MODE=sdk pi
# Run subagents as isolated subprocess pi instances (default, full isolation)
PI_SUBAGENTS_MODE=subprocess piComparison
| | subprocess | sdk |
|---|---|---|
| Session isolation | Full OS-level | Shared memory/event loop |
| Startup overhead | ~200–500 ms per agent | ~10–50 ms per agent |
| Parallelism | True OS parallelism | Event-loop concurrency |
| Fork mode context | Temp JSONL file | Temp JSONL file (same format) |
| Depth tracking | Env vars in child process | Closure parameters |
| pi binary required | Yes | No |
| Crash isolation | Yes — subprocess crash is contained | No — exception bubbles up |
Delegation Guards
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)Parallel Limits
| Env Var | Default | Description |
| -------------------------------- | ------- | ---------------------------------------- |
| PI_SUBAGENT_MAX_PARALLEL_TASKS | 16 | Max tasks per single call |
| PI_SUBAGENT_MAX_CONCURRENCY | 8 | Max subagents running simultaneously |
Agent Discovery
| 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. |
CLI Argument Proxying
Note: CLI argument proxying only applies to
subprocessmode. Insdkmode, subagents inherit provider and model configuration directly from the calling session's model registry and API keys from environment variables, so no forwarding is needed.
In subprocess mode, all 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.
Programmatic Usage (JSON RPC)
When running pi programmatically with --mode rpc (or --mode json), the stream contains
tool_result_end events whenever the agent completes a subagent 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.
Stream event shape
tool_result_end
└── message
├── role: "toolResult"
├── toolName: "subagent"
├── toolCallId: string
├── isError: boolean
├── content: [{ type: "text", text: "<final output>" }]
└── details: SubagentDetailsSubagentDetails object
interface SubagentDetails {
// Execution metadata
mode: "single" | "parallel"; // one task vs multiple parallel tasks
delegationMode: "spawn" | "fork"; // context mode used
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, "subagent": 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
}Important notes on stats
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"subagent"call itself. You can use the"subagent"count to see how many nested delegations an agent spawned.
Annotated example JSON
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": "subagent",
"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,
"subagent": 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,
"subagent": 1
},
"aggregatedUsage": {
"input": 2180,
"output": 615,
"cacheRead": 940,
"cacheWrite": 120,
"cost": 0.0079,
"contextTokens": 0,
"turns": 3
},
"aggregatedToolCalls": {
"read": 3,
"bash": 2,
"edit": 1,
"subagent": 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,
"subagent": 1
},
"messages": [
"... full conversation history of code-writer (includes the nested subagent tool_result) ..."
]
}
]
}
}
}Collecting stats across an entire session
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 === "subagent" 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" &&
event.message?.toolName === "subagent" &&
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 (whether subprocess or SDK-mode).
create-subagent Skill
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.
Attribution
Inspired by vaayne/agent-kit and mariozechner/pi-mono.
License
MIT
