pi-subagents gives pi a real subagent runtime.
Pi is a minimal coding harness. It gives you extensions, skills, prompts, and packages, then stays out of your way. Subagents are not built into core on purpose. They belong in a package where the behavior is explicit and replaceable.
This package is that layer.
It began as a fork of HazAT/pi-interactive-subagents. HazAT built the original async subagent extension and deserves full credit for the foundation. This fork, edxeth/pi-subagents, keeps the original idea, then pushes it much closer to Claude Code CLI territory: explicit sync/async launches, background workers, wait/join/detach ownership, stricter runtime control, and better separation between interactive and autonomous agents.
If you want the short version: this package gives pi a more complete subagent model, with explicit control over how child agents are launched, observed, synchronized, and resumed.
2026-04-16_16-46-05.mp4
pi install git:github.com/edxeth/pi-subagents- named subagents instead of ad-hoc prompt blobs
- async execution by default, with graceful parent turn stops after async launches
- sync execution when the parent truly needs to wait
- interactive foreground children and headless background children
- explicit
wait,join, anddetachsemantics - resumable child sessions through
caller_pingandsubagent_resume - child outputs delivered to the parent via final assistant message (plus built-in
write/readfor large payloads) - frontmatter that controls runtime, not just personality
- a live widget so you can see what your children are doing
That is the difference between “subagents exist” and “subagents are usable.”
By default, child sessions are launched with the normal pi command or, when possible, the same bundled executable as the parent process. If your pi runtime is wrapped by another launcher, opt in explicitly with:
PI_SUBAGENT_PI_COMMAND="tia pi" tia piThis makes both interactive subagents and resumed sessions launch through tia pi without changing behavior for users who run stock pi. The value is parsed as command words, so quoted paths are supported:
PI_SUBAGENT_PI_COMMAND="'/path with spaces/tia' pi" piA scout, a reviewer, and an implementation worker are not the same thing.
Sometimes you want a child in a pane so you can watch it think. Sometimes you want it headless. Sometimes the parent should keep moving. Sometimes the parent must stop and wait. Sometimes the child should die with the parent. Sometimes it should survive.
Most tools flatten all of that into vague marketing words. This package does not. It exposes the actual runtime model.
That is why it has more than one mode. Not because complexity is fashionable. Because the problem is real.
Every subagent is a named agent definition.
By default, children launch async: the parent does not wait, and results arrive later. Use sync when the parent must wait before continuing.
Other runtime choices are separate: interactive pane vs background process, auto-exit vs manual close, session seeding, and parent-shutdown behavior.
Agents live in either of these places:
.pi/agents/in the project~/.pi/agent/agents/globally
Project-local agents override global ones with the same name.
This package leans heavily on frontmatter. Agent files are not just prompt wrappers. They are runtime declarations.
| Field | Default | What it does | When to use it |
|---|---|---|---|
name |
filename | Agent name used by agent: "..." |
Always set it explicitly if you care about stable naming |
description |
unset | Human-readable description; also used for ambient routing | Write one short, sharp line if you want the parent to discover and route to it well |
enabled |
true |
Hides the agent from discovery and blocks launch when false |
Disable agents without deleting them |
model |
pi default | Sets the model for that agent | Pin a model when the role needs a specific speed/quality tradeoff |
thinking |
model default | Sets pi thinking level | Raise it for scouts/reviewers, lower it for cheap utility agents |
cwd |
parent cwd | Default working directory for the child | Use for role directories, monorepo packages, or project-specific specialists |
extensions |
unset | Comma-separated extension allowlist for child launch; if unset, child loads all extensions | Use to keep child agents off extensions |
tools |
all |
Built-in pi tools only: all, none, or a comma-separated subset of read, bash, edit, write, grep, find, ls |
Omit or set all for normal built-ins, set none to disable built-ins while keeping extension/protocol tools, or list only what the agent needs |
deny-tools |
unset | Denies specific child-session tools by name | Use for surgical restrictions without rewriting the whole tool set |
skills |
unset | Auto-loads one or more named skills from a comma-separated list | Use when an agent always needs the same external guidance |
no-context-files |
false |
Disables automatic AGENTS.md / CLAUDE.md discovery for spawned child sessions |
Use only when you want a clean child run without project context injection |
no-session |
false |
Launches the child with pi --no-session so no persistent child JSONL session is stored; inherited context uses a temporary session file that is removed after completion |
Use for disposable children that do not need resume, caller ping, or persistent lineage bookkeeping |
auto-exit |
false |
Child exits automatically after a normal completion | Best for autonomous agents, especially background scouts and reviewers |
system-prompt |
task-body routing | append uses --append-system-prompt; replace uses --system-prompt |
Use replace for hard role isolation, append when you want to preserve more surrounding context |
session-mode |
lineage-only |
Session seeding mode: standalone, lineage-only, or fork |
Use the default lineage-only for a clean child that is still recorded as descended from the parent, standalone only for a clean unrelated child, and fork when the child needs the full parent context branch |
spawning |
false |
Allows or denies subagent-spawning tools | Set true only for coordinators that should launch other subagents |
async |
true |
true: parent does not wait. false: parent waits. |
Use async: false only when the parent needs the result before continuing |
mode |
interactive |
interactive pane or background headless child |
Use background for autonomous work; keep interactive when visibility matters |
timeout |
unset | Background timeout in seconds | Use only for background agents that should never run forever |
session-mode controls how much of the parent session is seeded into the child.
When omitted, it defaults to lineage-only because subagents are usually causally related to the parent even when they should not inherit the full transcript.
From the model's point of view, the main difference is simple: standalone and lineage-only both start the child without the parent transcript, while fork copies the parent context branch into the child.
The difference between standalone and lineage-only is runtime bookkeeping, not model memory:
lineage-onlystarts a clean child session that records it descended from the parent session. Use this default when session trees, resume/debugging, artifact attribution, or orchestration history should show where the child came from without paying to copy the conversation.standalonestarts the same kind of clean child, but does not tie it to the parent by lineage metadata. Use this only when the child is genuinely unrelated to the parent session.forkstarts a child with the parent context branch copied in. Use this when the child needs the prior conversation, decisions, or files already discussed by the parent.
In short: standalone is a clean unrelated child, lineage-only is a clean related child, and fork is a related child with inherited context.
For nested subagent launches, parent always means the immediate spawning session. If a child agent has spawning: true and launches another agent, the grandchild is recorded below that child in lineage:
top-level session
└── child session
└── grandchild session
This is true for lineage-only even though the grandchild receives a clean model context. isolated context describes memory inheritance, not lineage rendering. Use standalone only when you do not want the lineage link.
Parent catalogs show the memory boundary next to each agent: isolated context or forked context. The work stays the same; only the handoff changes. isolated context starts a fresh child chat, so write a self-contained task with the objective, relevant facts/files, constraints, and expected output. forked context continues this conversation on a new branch, so give the goal, boundary, and expected output without re-explaining everything.
no-session: true is separate from session-mode: it disables persistent child history by launching pi with --session <ephemeral-file> --no-session. With session-mode: fork, the extension seeds that ephemeral file the same way it seeds a normal fork, so the child sees inherited context as session history rather than as prompt text. With session-mode: lineage-only, there is no persistent child session to attach lineage metadata to; when combined with no-session: true, it is treated like fork so the child still inherits parent context and the ephemeral file is removed after completion.
You only need to define extensions for agents that should be restricted.
If an agent does not set extensions, the child session behaves exactly as before and loads all extensions available to that child environment.
When you do set extensions, treat it as a comma-separated allowlist of normal pi -e extension sources.
The child session starts with --no-extensions and then adds only the listed sources back.
This allowlist is only for user-visible extension sources. The subagent protocol helper is injected separately so child sessions can still finish, ping the parent, and hand off artifacts. Do not list that helper in extensions; it is not an agent setting or an installable extension source.
Use local paths for local extensions:
---
name: reviewer
extensions: .pi/extensions/my-safe-ext, ~/.pi/agent/extensions/other-ext
---Use source prefixes for package or remote sources:
---
name: reviewer
extensions: npm:@foo/bar, git:github.com/user/repo
---Important details:
- Local paths do not need
npm:orgit:prefixes. - Package and remote sources should keep their normal
npm:,git:,https:, orssh:prefixes. - Bare names such as
subagentsare treated as paths, not package lookups. If you mean an installed or remote package, use its full source string.
- Scout / reviewer / analyzer:
mode: background,auto-exit: true - Interactive specialist:
mode: interactive, usually noauto-exit - Coordinator agent:
spawning: true - Sync gatekeeper:
async: false - Monorepo role agent:
cwd: ./packages/... - Locked-down worker: narrow
tools, maybedeny-tools, maybeextensions
If you want a concrete example of the style this package is built for, look at this scout agent:
That gist shows the intended shape: sharp role, explicit contract, minimal ambiguity.
A child can run in one of two ways.
The child gets its own pane or surface.
Use this when you want visibility, live steering, or just prefer seeing the work happen.
Supported backends:
The child runs headlessly as its own pi -p process.
Use this for scouts, reviewers, analyzers, and other autonomous workers that do not need a pane.
- Async: parent does not wait; result arrives later by steer.
- Sync: parent waits before continuing. Set
async: false, or usesubagent_wait/subagent_joinlater.
After an async launch, the parent should only continue with clearly non-overlapping work. If the next step would duplicate the child’s task, it should stop and wait for the steer result.
Successful async launches request graceful tool-batch termination, so Pi returns to the user or waits for steer delivery instead of making another autonomous parent LLM call immediately after the launch.
PI_SUBAGENT_DISABLE_COORDINATOR_ONLY_TURN=1 disables only that runtime stop request. This is an advanced escape hatch for users who intentionally want the parent agent free to continue after spawning async children. The ownership rule still applies: the parent should only continue with clearly non-overlapping work, and child results may arrive as steer messages while the parent keeps working.
Because there is no single sane policy for all agents.
A codebase scout should usually run autonomously and get out of the way. A reviewer may need sync behavior. A long-running background worker may need a timeout. An interactive child may need to stay open after user takeover instead of auto-exiting. A parent may want to cancel one child and abandon another.
This package exposes those differences instead of pretending they are the same thing.
That is the whole philosophy.
There are three main ways a child finishes.
Best for autonomous workers. The child exits when its turn finishes normally.
Best for manual-lifecycle agents that should close themselves only after they decide the assigned task is complete.
subagent_done is a child-session tool. The child calls it after its final assistant message to close the child session and return control to the parent. That last assistant message becomes the result summary delivered to the caller.
The parent normally does not call subagent_done. Parent sessions use subagent, subagent_wait, subagent_join, subagent_detach, or subagent_resume to manage children. subagent_done is the child's side of the lifecycle contract.
subagent_done is available only when it is useful. It is hidden for auto-exit: true agents because those agents already close after a normal completion. For auto-exit: false agents, it is available by default: manual lifecycle means the child does not close automatically after every normal turn, not that only the human can close it. If you want an interactive child that can only be ended by operator/process shutdown, deny this tool with deny-tools.
Best when the child needs help from the parent.
A child can stop, send a message upward, and hand back a resumable session file. The parent can answer and resume that exact session later. That gives you a real feedback loop instead of a dead end.
The child's final assistant message is its output. The extension forwards it to the parent via steer — no file writes needed. For very large outputs, use pi's built-in write tool with an explicit path and mention the path in the closing message.
PI_ARTIFACT_PROJECT_ROOT sets the internal scratch directory for task delivery files and launch scripts. You don't normally need to touch it.
Top-level sessions can receive a hidden catalog of available named subagents built from agent descriptions.
That lets the parent model know which specialists exist and how much context each one gets, without spamming visible history. Child sessions do not get this catalog. Agents without descriptions remain launchable, but they are omitted from the ambient routing hint.
If you do not want that behavior, disable it.
These are the ones worth knowing.
PI_SUBAGENT_MUX— force the mux backend:cmux,tmux,zellij, orweztermPI_CODING_AGENT_DIR— override the global pi agent config rootPI_SUBAGENT_DISABLE_AMBIENT_AWARENESS— disable the hidden top-level subagent catalogPI_SUBAGENT_DISABLE_COORDINATOR_ONLY_TURN— opt out of the default graceful parent turn stop after async launches; set to1only when you want the parent free to continue after spawning childrenPI_SUBAGENT_DISABLE_SESSION_TITLES— disable automatic child session titles such as[scout agent] Auth flow reconnaissancePI_ARTIFACT_PROJECT_ROOT— override the internal scratch directory root; default is~/.pi/history/<project>/artifacts/<session-id>/.... You don't normally need to touch this.PI_SUBAGENT_SHELL_READY_DELAY_MS— override the interactive shell startup delay before sending a child command (default500)PI_SUBAGENT_ENABLE_SET_TAB_TITLE— opt in to registering theset_tab_titletoolPI_SUBAGENT_RENAME_TMUX_WINDOW— opt in to tmux window renamingPI_SUBAGENT_RENAME_TMUX_SESSION— opt in to tmux session renaming
These are normally set by the extension itself, but they matter if you are reading the codebase or debugging behavior:
PI_DENY_TOOLSPI_SUBAGENT_EXTENSIONSPI_SUBAGENT_NAMEPI_SUBAGENT_AGENTPI_SUBAGENT_PARENT_SESSIONPI_SUBAGENT_SESSIONPI_SUBAGENT_SURFACEPI_SUBAGENT_SESSION_TITLEPI_SUBAGENT_AUTO_EXIT
PI_SUBAGENT_ALLOW_LIVE_WINDOWSPI_SUBAGENT_LIVE_MODELPI_SUBAGENT_KEEP_E2E_TMPPI_SUBAGENT_LIVE_LOCK_PATH
The package adds two useful pieces of UI.
The parent session gets a live subagent widget above the editor showing running children, elapsed time, activity, and basic context usage. Every subagent tool call requires a concise title; the widget shows that title instead of a raw task/prompt preview.
Each child session also gets its own tools widget so you can see what is available and what is denied. Child session titles use [<agent> agent] <title>.
That sounds minor until you start juggling several agents. Then it stops sounding minor.
There are ordinary tests and live end-to-end smoke tests.
npm test
PI_SUBAGENT_ALLOW_LIVE_WINDOWS=1 npm run test:e2e-live
PI_SUBAGENT_ALLOW_LIVE_WINDOWS=1 npm run test:e2e-live-blocking
PI_SUBAGENT_ALLOW_LIVE_WINDOWS=1 npm run test:e2e-live-mix-blocking
npm run test:e2e-live-deny-tools
npm run test:e2e-live-tools
npm run test:e2e-live-extensions
npm run test:e2e-live-stop-after-turnThe live tests are intentionally gated so they do not spray terminal windows all over your machine by accident.
- upstream foundation: HazAT/pi-interactive-subagents
- this fork: edxeth/pi-subagents
MIT