A pi extension that brings Claude Code-style autonomous sub-agents to pi. Spawn specialized agents that run in isolated sessions — each with its own tools, system prompt, model, and thinking level. Run them in foreground or background, steer them mid-run, resume completed sessions, and define your own custom agent types.
Fork notice: This package is a friendly fork of
tintinweb/pi-subagents, published to npm as@gotgenes/pi-subagents. It carries a small number of patches on top of upstream — peer-dep migration to@earendil-works/pi-*, a post-bindExtensionsactive-tool re-filter, and an<active_agent>system-prompt tag for permission resolution. See Deviations from upstream at the bottom of this README for details.Status: Early release.
https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
-
Claude Code look & feel — same tool names, calling conventions, and UI patterns (
subagent,get_subagent_result,steer_subagent) — feels native - Parallel background agents — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and individual completion notifications
- Live widget UI — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons
-
Conversation viewer — select any agent in
/agentsto open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause) -
Custom agent types — define agents in
.pi/agents/<name>.mdwith YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions - Mid-run steering — inject messages into running agents to redirect their work without restarting
- Session resume — pick up where an agent left off, preserving full conversation context
- Graceful turn limits — agents get a "wrap up" warning before hard abort, producing clean partial results instead of cut-off output
-
Case-insensitive agent types —
"explore","Explore","EXPLORE"all work. Unknown types fall back to general-purpose with a note -
Fuzzy model selection — specify models by name (
"haiku","sonnet") instead of full IDs, with automatic filtering to only available/configured models - Context inheritance — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
- Persistent agent memory — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
- Git worktree isolation — run agents in isolated repo copies; changes auto-committed to branches on completion
-
Skill preloading — inject named skills into agent system prompts, discovered from
.pi/skills/,.agents/skills/, and global locations (Pi-standard<name>/SKILL.mddirectory layout supported) - 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
-
Event bus — lifecycle events (
subagents:created,started,completed,failed,steered,compacted) emitted viapi.events, enabling other extensions to react to sub-agent activity
pi install npm:@gotgenes/pi-subagentsOr load directly for development:
pi -e ./src/index.tsThe parent agent spawns sub-agents using the subagent tool:
subagent({
subagent_type: "Explore",
prompt: "Find all files that handle authentication",
description: "Find auth files",
run_in_background: true,
})
Foreground agents block until complete and return results inline. Background agents return an ID immediately and notify you on completion.
The extension renders a persistent widget above the editor showing all active agents:
● Agents
├─ ⠹ Agent Refactor auth module · ⟳5≤30 · 5 tool uses · 33.8k token (62%) · 12.3s
│ ⎿ editing 2 files…
├─ ⠹ Explore Find auth files · ⟳3 · 3 tool uses · 12.4k token (8%) · 4.1s
│ ⎿ searching…
├─ ⠹ Agent Long-running task · ⟳42 · 38 tool uses · 91.0k token (84% · ↻2) · 2m17s
│ ⎿ reading…
└─ 2 queued
The token field is annotated with two optional signals inside parens:
-
NN%— context-window utilization (color-coded: <70% dim, 70–85% warning, ≥85% error). Omitted when the model has no declaredcontextWindow, or briefly right after compaction. -
↻N— number of times the session has compacted, when > 0. Stays dim; the percent's color carries urgency.
Individual agent results render Claude Code-style in the conversation:
| State | Example |
|---|---|
| Running |
⠹ ⟳3≤30 · 3 tool uses · 12.4k token (8%) / ⎿ searching, reading 3 files…
|
| Completed |
✓ ⟳8 · 5 tool uses · 33.8k token (62%) · 12.3s / ⎿ Done
|
| Wrapped up |
✓ ⟳50≤50 · 50 tool uses · 89.1k token (84% · ↻2) · 45.2s / ⎿ Wrapped up (turn limit)
|
| Stopped |
■ ⟳3 · 3 tool uses · 12.4k token (8%) / ⎿ Stopped
|
| Error |
✗ ⟳3 · 3 tool uses · 12.4k token (8%) / ⎿ Error: timeout
|
| Aborted |
✗ ⟳55≤50 · 55 tool uses · 102.3k token (95% · ↻3) / ⎿ Aborted (max turns exceeded)
|
Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
Background agent completion notifications render as styled boxes:
✓ Find auth files completed
⟳3 · 3 tool uses · 12.4k token · 4.1s
⎿ Found 5 files related to authentication...
transcript: .pi/output/agent-abc123.jsonl
The LLM receives structured <task-notification> XML for parsing, while the user sees the themed visual.
| Type | Tools | Model | Prompt Mode | Description |
|---|---|---|---|---|
general-purpose |
all 7 | inherit |
append (parent twin) |
Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
Explore |
read, bash, grep, find, ls | haiku (falls back to inherit) |
replace (standalone) |
Fast codebase exploration (read-only) |
Plan |
read, bash, grep, find, ls | inherit |
replace (standalone) |
Software architect for implementation planning (read-only) |
The general-purpose agent is a parent twin — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does.
Explore and Plan use standalone prompts tailored to their read-only roles.
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.
Define custom agent types by creating .md files.
The filename becomes the agent type name.
Any name is allowed — using a default agent's name overrides it.
Agents are discovered from two locations (higher priority wins):
| Priority | Location | Scope |
|---|---|---|
| 1 (highest) | .pi/agents/<name>.md |
Project — per-repo agents |
| 2 |
$PI_CODING_AGENT_DIR/agents/<name>.md (default ~/.pi/agent/agents/<name>.md) |
Global — available everywhere |
Project-level agents override global ones with the same name, so you can customize a global agent for a specific project.
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.
---
description: Security Code Reviewer
tools: read, grep, find, bash
model: anthropic/claude-opus-4-6
thinking: high
max_turns: 30
---
You are a security auditor.
Review code for vulnerabilities including:
- Injection flaws (SQL, command, XSS)
- Authentication and authorization issues
- Sensitive data exposure
- Insecure configurations
Report findings with file paths, line numbers, severity, and remediation advice.Then spawn it like any built-in type:
subagent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
All fields are optional — sensible defaults for everything.
| Field | Default | Description |
|---|---|---|
description |
filename | Agent description shown in tool listings |
display_name |
— | Display name for UI (e.g. widget, agent list) |
tools |
all 7 | Comma-separated built-in tools: read, bash, edit, write, grep, find, ls. none for no tools |
extensions |
true |
true to inherit all MCP/extension tools, false to disable |
skills |
true |
Inherit skills from parent. Can be a comma-separated list of skill names to preload (see Skill Preloading for discovery locations) |
memory |
— | Persistent agent memory scope: project, local, or user. Auto-detects read-only agents |
isolation |
— | Set to worktree to run in an isolated git worktree |
model |
inherit parent | Model — provider/modelId or fuzzy name ("haiku", "sonnet") |
thinking |
inherit | off, minimal, low, medium, high, xhigh |
max_turns |
unlimited | Max agentic turns before graceful shutdown. 0 or omit for unlimited |
prompt_mode |
replace |
replace: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). append: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
inherit_context |
false |
Fork parent conversation into agent |
run_in_background |
false |
Run in background by default |
isolated |
false |
No extension/MCP tools, only built-in |
enabled |
true |
Set to false to disable an agent (useful for hiding a default agent per-project) |
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.
subagent tool parameters only fill fields the agent config leaves unspecified.
Launch a sub-agent.
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | The task for the agent |
description |
string | yes | Short 3-5 word summary (shown in UI) |
subagent_type |
string | yes | Agent type (built-in or custom) |
model |
string | no | Model — provider/modelId or fuzzy name ("haiku", "sonnet") |
thinking |
string | no | Thinking level: off, minimal, low, medium, high, xhigh |
max_turns |
number | no | Max agentic turns. Omit for unlimited (default) |
run_in_background |
boolean | no | Run without blocking |
resume |
string | no | Agent ID to resume a previous session |
isolated |
boolean | no | No extension/MCP tools |
isolation |
"worktree" |
no | Run in an isolated git worktree |
inherit_context |
boolean | no | Fork parent conversation into agent |
Check status and retrieve results from a background agent.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id |
string | yes | Agent ID to check |
wait |
boolean | no | Wait for completion |
verbose |
boolean | no | Include full conversation log |
Send a steering message to a running agent. The message interrupts after the current tool execution.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_id |
string | yes | Agent ID to steer |
message |
string | yes | Message to inject into agent conversation |
| Command | Description |
|---|---|
/agents |
Interactive agent management menu |
The /agents command opens an interactive menu:
Running agents (2) — 1 running, 1 done ← only shown when agents exist
Agent types (6) ← unified list: defaults + custom
Create new agent ← manual wizard or AI-generated
Settings ← max concurrency, max turns, grace turns
-
Agent types — unified list with source indicators:
•(project),◦(global),✕(disabled). Select an agent to manage it:-
Default agents (no override): Eject (export as
.md), Disable - Default agents (ejected/overridden): Edit, Disable, Reset to default, Delete
- Custom agents: Edit, Disable, Delete
- Disabled agents: Enable, Edit, Delete
-
Default agents (no override): Eject (export as
-
Eject — writes the embedded default config as a
.mdfile to project or personal location, so you can customize it -
Disable/Enable — toggle agent availability.
Disabled agents stay visible in the list (marked
✕) and can be re-enabled -
Create new agent — choose project/personal location, then manual wizard (step-by-step prompts for name, tools, model, thinking, system prompt) or AI-generated (describe what the agent should do and a sub-agent writes the
.mdfile). Any name is allowed, including default agent names (overrides them) - Settings — configure max concurrency, default max turns, and grace turns at runtime
Instead of hard-aborting at the turn limit, agents get a graceful shutdown:
- At
max_turns— steering message: "Wrap up immediately — provide your final answer now." - Up to 5 grace turns to finish cleanly
- Hard abort only after the grace period
| Status | Meaning | Icon |
|---|---|---|
completed |
Finished naturally |
✓ green |
steered |
Hit limit, wrapped up in time |
✓ yellow |
aborted |
Grace period exceeded |
✗ red |
stopped |
User-initiated abort |
■ dim |
Background agents are subject to a configurable concurrency limit (default: 4). Excess agents are automatically queued and start as running agents complete. The widget shows queued agents as a collapsed count.
Foreground agents bypass the queue — they block the parent anyway.
Runtime tuning values set via /agents → Settings (max concurrency, default max turns, grace turns) persist across pi restarts.
Two files, merged on load:
-
Global:
~/.pi/agent/subagents.json— your machine-wide defaults. Edit by hand; the/agentsmenu never writes here. -
Project:
<cwd>/.pi/subagents.json— per-project overrides. Written by/agents→ Settings.
Precedence: project overrides global on any field present in both.
Missing fields fall back to the hardcoded defaults (max concurrency 4, default max turns unlimited, grace turns 5).
Example — global defaults for a beefy machine:
mkdir -p ~/.pi/agent
cat > ~/.pi/agent/subagents.json <<'EOF'
{
"maxConcurrent": 16,
"graceTurns": 10
}
EOFEvery project now starts with concurrency 16 and grace 10, without ever touching the menu.
Individual projects can still override via /agents → Settings.
Failure behavior: missing file is silent; malformed JSON logs a [pi-subagents] Ignoring malformed settings at … warning to stderr; invalid/out-of-range field values are dropped per-field; write failures downgrade the /agents toast to a warning with (session only; failed to persist).
Agent lifecycle events are emitted via pi.events.emit() so other extensions can react:
| Event | When | Key fields |
|---|---|---|
subagents:created |
Background agent registered |
id, type, description, isBackground
|
subagents:started |
Agent transitions to running (including queued→running) |
id, type, description
|
subagents:completed |
Agent finished successfully |
id, type, durationMs, tokens (lifetime { input, output, total }), toolUses, result
|
subagents:failed |
Agent errored, stopped, or aborted | same as completed + error, status
|
subagents:steered |
Steering message sent |
id, message
|
subagents:compacted |
Agent's session successfully compacted |
id, type, description, reason ("manual" / "threshold" / "overflow"), tokensBefore, compactionCount
|
subagents:settings_loaded |
Persisted settings applied at extension init |
settings (merged global + project) |
subagents:settings_changed |
/agents → Settings mutation was applied |
settings, persisted (boolean — false on write failure) |
tokens.total = input + output + cacheWrite.
cacheRead is excluded — each turn's cacheRead is the cumulative cached prefix re-read on that one API call, so summing per-message would over-count it.
Use contextUsage.percent (surfaced as (NN%) in the widget) for current context size.
Agents can have persistent memory across sessions.
Set memory in frontmatter to enable:
---
memory: project # project | local | user
---| Scope | Location | Use case |
|---|---|---|
project |
.pi/agent-memory/<name>/ |
Shared across the team (committed) |
local |
.pi/agent-memory-local/<name>/ |
Machine-specific (gitignored) |
user |
~/.pi/agent-memory/<name>/ |
Global personal memory |
Memory uses a MEMORY.md index file and individual memory files with frontmatter.
Agents with write tools get full read-write access.
Read-only agents (no write/edit tools) automatically get read-only memory — they can consume memories written by other agents but cannot modify them.
This prevents unintended tool escalation.
Set isolation: worktree to run an agent in a temporary git worktree:
subagent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
The agent gets a full, isolated copy of the repository. On completion:
- No changes: worktree is cleaned up automatically
-
Changes made: changes are committed to a new branch (
pi-agent-<id>) and returned in the result
If the worktree cannot be created (not a git repo, no commits, or git worktree add fails), the subagent tool returns a clear error instead of running unisolated — isolation: "worktree" is a strict guarantee, not a hint.
Initialize git and commit at least once, or omit isolation.
Skills can be preloaded by name and injected into the agent's system prompt:
---
skills: api-conventions, error-handling
---Discovery roots (checked in this order, first match wins):
| Scope | Path | Source |
|---|---|---|
| Project | <cwd>/.pi/skills/ |
Pi-standard |
| Project | <cwd>/.agents/skills/ |
Agent Skills spec |
| User |
$PI_CODING_AGENT_DIR/skills/ (default ~/.pi/agent/skills/) |
Pi-standard |
| User | ~/.agents/skills/ |
Agent Skills spec |
| User | ~/.pi/skills/ |
Legacy (pre-Pi) |
Per root, a skill named foo resolves to the first of:
-
<root>/foo.md— flat file at the top level -
<root>/foo/SKILL.md— directory skill (top-level) -
<root>/*/.../foo/SKILL.md— directory skill, found by recursive descent
Recursion skips dotfile directories and node_modules.
A directory that itself contains a SKILL.md is treated as a single skill — we don't descend into it.
Traversal is byte-order sorted for deterministic resolution across filesystems.
Security: symlinks are rejected at every layer (root, flat file, skill directory, SKILL.md inside a skill directory) — intentional deviation from Pi, which follows symlinks.
Skill names with path-traversal characters (.., /, \, spaces, leading dot, >128 chars) are rejected.
The disallowed_tools frontmatter field has been removed.
Use @gotgenes/pi-permission-system's permission: frontmatter instead — it provides richer semantics (allow/ask/deny vs. binary hide):
# Before (no longer supported)
disallowed_tools: bash
# After
permission:
bash: denyWhen @gotgenes/pi-permission-system is installed, this extension integrates automatically:
-
Per-agent permission policies — define
permission:in agent YAML frontmatter to set allow/ask/deny rules per agent type. The permission system resolves the agent name from the<active_agent>tag in the child system prompt. -
Tool filtering — the permission system's
before_agent_starthandler removes denied tools from the child session before the agent starts. -
ask-state forwarding — when a child session triggers anaskpermission, the prompt forwards to the parent session's UI. The parent approves or denies, and the child resumes. -
Deterministic child detection — this extension publishes
subagents:child:session-createdbeforebindExtensions()fires; the permission system subscribes and registers the child session synchronously, so detection does not rely on env vars or filesystem heuristics.
No configuration is required.
When @gotgenes/pi-permission-system is not installed, the lifecycle events have no subscriber — a harmless no-op.
See docs/architecture/architecture.md for the full architecture document with domain decomposition, Mermaid diagrams, and improvement roadmap.
src/
index.ts # Extension entry: tool/command registration, rendering
runtime.ts # Session-scoped state bag with methods
types.ts # Shared type definitions
settings.ts # Persistent settings (concurrency, turn limits)
config/ # Agent type registry and configuration
agent-types.ts # Unified agent registry (defaults + custom)
default-agents.ts # Embedded default agent configs
custom-agents.ts # Load user-defined agents from .pi/agents/*.md
invocation-config.ts # Per-call merge of tool params + agent config
session/ # Pure session assembly
session-config.ts # Session configuration assembler
prompts.ts # Config-driven system prompt builder
context.ts # Parent conversation context for inherit_context
conversation.ts # Render a session's messages as formatted text
content-items.ts # Shared message content parsing
env.ts # Environment detection (git, platform)
model-resolver.ts # Fuzzy model matching
session-dir.ts # Session directory derivation
lifecycle/ # Agent execution and state tracking
agent-manager.ts # Collection manager + observer wiring
agent.ts # Full execution lifecycle (run, abort, steer, workspace)
create-subagent-session.ts # Assembly factory: session creation, binding, tool filtering
subagent-session.ts # Born-complete child session: turn loop, steer, dispose
child-lifecycle.ts # Child-execution lifecycle event publisher
concurrency-queue.ts # Background agent scheduling
parent-snapshot.ts # Immutable spawn-time parent state
turn-limits.ts # Turn-count policy (normalizeMaxTurns)
workspace.ts # Workspace provider seam
usage.ts # Token usage tracking
observation/ # Progress tracking and notification
record-observer.ts # Session-event stats observer
notification.ts # Completion nudges
notification-state.ts # Notification state tracking
renderer.ts # Notification rendering
service/ # Cross-extension API boundary
service.ts # SubagentsService interface + Symbol.for() accessors
service-adapter.ts # SubagentsService wrapper around AgentManager
tools/ # LLM-facing tools
ui/ # Widget, conversation viewer, /agents menu
This fork carries three divergences from tintinweb/pi-subagents.
Each has a corresponding upstream PR:
-
Peer-dep migration to
@earendil-works/pi-*—peerDependenciesand all imports point at@earendil-works/pi-ai,@earendil-works/pi-coding-agent, and@earendil-works/pi-tui(the active scope on npm) instead of the deprecated@mariozechner/pi-*scope. Also fixes a latent bug whereThinkingLevelwas imported frompi-agent-core(an undeclared transitive dep that breaks under pnpm). Upstream PR: tintinweb/pi-subagents#71. -
Post-
bindExtensionsactive-tool re-filter (src/agent-runner.ts) —runAgentre-runs its active-tool filter aftersession.bindExtensions(...)so theEXCLUDED_TOOL_NAMESrecursion guard applies to extension-registered tools (which join the active set duringbindExtensions). Upstream PR: tintinweb/pi-subagents#72. -
<active_agent>system-prompt tag (src/prompts.ts) —buildAgentPromptprepends<active_agent name="${config.name}"/>to every assembled child system prompt (bothreplaceandappendmodes). Downstream extensions like@gotgenes/pi-permission-systemparse this tag to resolve per-agentpermission:frontmatter inside the child session. Upstream PR: tintinweb/pi-subagents#73. -
Child-execution lifecycle events (
src/lifecycle/child-lifecycle.ts) — the child-session execution lifecycle is published as ordered events onpi.events(subagents:child:spawning,session-created,completed,disposed).session-createdfires synchronously beforebindExtensions()so consumers (e.g.@gotgenes/pi-permission-system) can register the child session before binding proceeds. This inverts the former outboundpermission-bridgepattern (ADR-0002 / [#261]) — the core publishes, consumers subscribe. No upstream equivalent — this feature is specific to the@gotgenesfork.
The upstream vitest suite plus tests added for each patch all pass on every commit.
MIT — tintinweb (upstream) and Chris Lasher (fork)
