13 KiB
Subagent presets and background agents design
Date: 2026-04-12 Status: approved for planning
Summary
Evolve pi-subagents from generic foreground-only child runs into a preset-driven delegation package with three tools:
subagentfor single or parallel foreground runsbackground_agentfor detached process-backed runsbackground_agent_statusfor polling detached-run status and counts
Named customization comes back as markdown presets, but without bundled built-in roles. Presets live in:
- global:
~/.pi/agent/subagents/*.md - project: nearest
.pi/subagents/*.mdfound by walking up from the currentcwd
Project presets override global presets by name. Calls must name a preset. Per-call overrides are limited to model; prompt and tool access come only from the preset.
Current state
Today the package has:
- one
subagenttool withtask | tasks | chain - no background-run registry or polling tool
- no preset discovery
- no wrapper support for preset-owned prompt/tool restrictions
- prompt templates that still assume
chain
The old role system was removed entirely, including markdown discovery, --tools, and --append-system-prompt wiring. This change brings back the useful customization pieces without reintroducing bundled roles or old role-specific behavior.
Goals
- Remove
chainmode fromsubagent. - Keep foreground single-run and parallel-run delegation.
- Add named preset discovery from global and project markdown directories.
- Let presets define appended prompt text, built-in tool allowlist, and optional default model.
- Add
background_agentas a process-only detached launcher. - Add
background_agent_statusso the parent agent can poll one run or many runs. - Track background-run counts and surface completion through UI notification plus a visible session message.
- Preserve existing runner behavior for foreground runs and keep runner-specific changes minimal.
Non-goals
- No bundled built-in presets (
scout,planner,reviewer,worker, etc.). - No markdown discovery from
.pi/agents. - No inline prompt or tool overrides on tool calls.
- No background tmux mode.
background_agentalways uses the process runner. - No automatic follow-up turn when a background run finishes.
- No attempt to restrict third-party extension tools beyond what Pi CLI already supports.
Chosen approach
Add a small preset layer and a small background-run state layer on top of the current runner/wrapper core.
- foreground
subagentbecomes preset-aware and loseschain - wrapper/artifacts regain preset-owned prompt/tool support
- background runs reuse process-launch mechanics but skip
monitorRun()in the calling tool - extension-owned registry watches detached runs, persists state to session custom entries, updates footer status text, emits UI notifications, and injects visible completion messages into session history
This keeps most existing code paths intact while restoring customization and adding detached orchestration.
Public API design
subagent
Supported modes:
Single mode
Required:
presettask
Optional:
modelcwd
Parallel mode
Required:
tasks: Array<{ preset: string; task: string; model?: string; cwd?: string }>
Notes:
- each parallel item names its own preset
- there is no top-level default preset
- there is no top-level required model
Removed:
chain
Model resolution order per run:
- call-level
model - preset
model - error: no model resolved
background_agent
Single-run only.
Required:
presettask
Optional:
modelcwd
Behavior:
- always launches with the process runner, ignoring tmux config
- returns immediately after spawn request
- returns run handle metadata plus counts snapshot
- does not wait for completion
background_agent_status
Purpose:
- let the main agent poll background runs and inspect counts
Parameters:
runId?— inspect one runincludeCompleted?— defaultfalse; when omitted, only active runs are listed unlessrunIdis provided
Returns:
- counts:
running,completed,failed,aborted,total - per-run rows with preset, task, cwd, model info, timestamps, artifact paths, and status
- final result fields when a run is terminal
Preset design
Discovery
Load presets from two sources:
- global:
join(getAgentDir(), "subagents") - project: nearest ancestor directory containing
.pi/subagents
Merge order:
- global presets
- project presets override global presets with the same
name
No confirmation gate for project presets.
File format
Each preset is one markdown file with frontmatter and body.
Required frontmatter:
namedescription
Optional frontmatter:
modeltools
Body:
- appended system prompt text
Example:
---
name: repo-scout
description: Fast repo exploration
model: github-copilot/gpt-4o
tools: read,grep,find,ls
---
You are a scout. Explore quickly, summarize clearly, and avoid implementation.
Preset semantics
modelis the default model when the call does not provide one.toolsis optional.- omitted
toolsmeans normal child-tool behavior (no built-in tool restriction) - when
toolsis present, pass it through Pi CLI--tools, which limits built-in tools only - prompt text comes only from the markdown body; no inline prompt override
Runtime design
Foreground subagent execution
src/tool.ts becomes preset-aware.
For each run:
- discover presets
- resolve the named preset
- normalize explicit
modeloverride against available models if present - normalize preset default model against available models if used
- compute effective model from call override or preset default
- pass runner metadata including preset, prompt text, built-in tool allowlist, and model selection
Parallel behavior stays the same apart from:
- no
chain - each task resolving its own preset/model
- summary lines identifying tasks by index and/or preset, not old role names
Background execution
background_agent launches the wrapper via process-runner primitives but returns immediately.
Flow:
- resolve preset + effective model
- create run artifacts
- spawn wrapper process
- register run in extension background registry as
running - append persistent session entry for the new run
- start detached watcher on that run’s
result.json/events.jsonl - return handle metadata and counts snapshot
Background runs are process-only even when normal subagent foreground runs are configured for tmux.
Background registry
The extension owns a session-scoped registry keyed by runId.
Stored metadata per run:
runIdpresettaskcwdrequestedModelresolvedModel- artifact paths
- timestamps
- terminal result fields when available
- status:
running | completed | failed | aborted
The registry also computes counts:
runningcompletedfailedabortedtotal
Persistence and reload behavior
Persist background state with session custom entries.
Custom entry types:
pi-subagents:bg-run— initial launch metadatapi-subagents:bg-update— later status/result transitions
On session_start, rebuild the in-memory registry by scanning ctx.sessionManager.getEntries().
For rebuilt runs that are still non-terminal:
- if
result.jsonalready exists, ingest it immediately - otherwise reattach a watcher so completion still updates counts, notifications, and session messages after reload/resume
Notification and polling design
Completion notification
When a detached run becomes terminal:
- update registry and counts
- append
pi-subagents:bg-update - update footer status text, e.g.
bg: 2 running / 5 total - emit UI notification if UI is available
- inject a visible custom session message describing completion
The completion message must not trigger a new agent turn automatically.
Polling
The parent agent polls with background_agent_status.
Typical use:
- ask for current running count
- list active background runs
- inspect one
runId - fetch terminal result summary after notification arrives
Wrapper and artifact design
Artifact layout
Keep run directories under:
.pi/subagents/runs/<runId>/
Keep existing files and restore the removed prompt artifact when needed:
meta.jsonevents.jsonlresult.jsonstdout.logstderr.logtranscript.logchild-session.jsonlsystem-prompt.md
Metadata
Keep existing generic bookkeeping and add preset-specific fields:
presetpresetSourcetoolssystemPromptsystemPromptPath
Do not reintroduce old bundled-role concepts or role-only behavior.
Child wrapper
src/wrapper/cli.mjs should again support:
--append-system-prompt <path>when preset prompt text exists--tools <csv>when presettoolsexists
Keep:
PI_SUBAGENTS_CHILD=1- github-copilot initiator behavior based on effective model
- best-effort artifact appends that must never block writing
result.json - semantic-completion exit handling
Extension registration behavior
Keep existing model-registration behavior for model-dependent tools:
- preserve current available-model order for schema enums
- do not mutate available-model arrays when deduping cache keys
- re-register when model set changes
- do not re-register when model set is the same in different order
- if the first observed set is empty, a later non-empty set must still register
- skip tool registration entirely when
PI_SUBAGENTS_CHILD=1
Register:
subagentbackground_agentbackground_agent_status
background_agent_status does not need model parameters, but registration still follows the package’s existing model-availability gate for consistency.
Prompt and documentation design
Rewrite shipped prompts so they no longer mention chain mode.
They should instead describe repeated subagent calls or subagent.tasks parallel calls, for example:
- inspect with a preset
- turn findings into a plan with another preset
- implement or review in separate follow-up calls
README and docs should describe:
- preset directories and markdown format
background_agentbackground_agent_status- background completion notification behavior
- background count tracking
- process-only behavior for detached runs
- built-in-tool-only semantics of preset
tools
They should continue to avoid claiming bundled built-in roles.
File-level impact
Expected new files:
src/presets.tssrc/presets.test.tssrc/background-registry.tssrc/background-registry.test.tssrc/background-schema.tssrc/background-tool.tssrc/background-tool.test.tssrc/background-status-tool.tssrc/background-status-tool.test.ts
Expected modified files:
index.tssrc/schema.tssrc/tool.tssrc/models.tssrc/runner.tsand/orsrc/process-runner.tssrc/artifacts.tssrc/process-runner.test.tssrc/extension.test.tssrc/artifacts.test.tssrc/wrapper/cli.mjssrc/wrapper/cli.test.tssrc/wrapper/render.mjssrc/wrapper/render.test.tssrc/prompts.test.tsREADME.mdprompts/*.mdAGENTS.md
Expected removals:
src/tool-chain.test.ts
Testing plan
Implementation should follow TDD.
New coverage
Add tests for:
- preset discovery from global and project directories
- project preset override by name
- required preset selection in single and parallel mode
- model resolution from call override vs preset default
- error when neither call nor preset supplies a valid model
- chain removal from schema and runtime
- detached background launch returning immediately
- background registry counts
- session-entry persistence and reload reconstruction
- completion notification emitting UI notify + visible session message without auto-turn
- polling one background run and many background runs
Preserved coverage
Keep regression coverage for:
- child sessions skipping subagent tool registration when
PI_SUBAGENTS_CHILD=1 - no tool registration when no models are available
- later registration when a non-empty model list appears
- no re-registration for the same model set in different order
- re-registration when the model set changes
- github-copilot initiator behavior
- best-effort artifact logging never preventing
result.jsonwrites - effective model using the resolved model when requested/resolved differ
Risks and mitigations
Risk: preset tools sounds broader than Pi can enforce
Mitigation:
- document that preset
toolsmaps to Pi CLI--tools - treat it as a built-in tool allowlist only
- keep
PI_SUBAGENTS_CHILD=1so this package never re-registers its own subagent tools inside child runs
Risk: detached watcher state lost on reload
Mitigation:
- persist launch/update events as session custom entries
- rebuild registry on
session_start - reattach watchers for non-terminal runs
Risk: background notifications spam the user
Mitigation:
- emit one terminal notification per run
- keep footer count compact
- require explicit polling for detailed inspection
Risk: larger extension entrypoint
Mitigation:
- keep preset discovery, registry/state, and tool definitions in separate focused modules
- keep runner-specific logic in existing runner files with minimal changes