# pi-claude-subagents design

## Purpose

Provide a deep Pi module for child-agent orchestration: a small model-facing interface over Fresh sessions, inherited-context workers, parallel dispatch, continuation, persistence, verification, and bounded nesting.

## External seam

```ts
interface AgentOrchestrator {
  launch(request: LaunchRequest, parent: ParentContext): Promise<LaunchReceipt>
  send(target: TaskTarget, message: string): Promise<DeliveryReceipt>
  inspect(target: TaskTarget, options?: InspectOptions): Promise<TaskSnapshot>
  stop(target: TaskTarget): Promise<TaskSnapshot>
}
```

The Pi extension adapter exposes this seam as `Agent`, `SendMessage`, `TaskOutput`, and `TaskStop`.

Embedders additionally get two observable side channels:

- **Child stream events.** Launch and resume forward the child session's `message_update` and assistant `message_end` stream events onto the parent session's Pi event bus (`pi.events.emit`) on the channel `pi-claude-subagents:child-event` (exported as `SUBAGENT_CHILD_EVENT_CHANNEL`) with payload `{ childSessionId, taskId, event }`. `childSessionId` is the live child session id for launches and `sessionManager.getSessionId()` for resumes. Nothing is persisted; with no bus attached or no listener subscribed this is a no-op, and listener/emitter failures are swallowed so they can never affect the child run.
- **Persist-before-return.** A launch writes the running `task.json` and publishes it through the tool update callback before `launchTask` returns, so a crash or reload during startup leaves an honest `running` record that the next session's orphan sweep fails (recovery only resumes `stopped`/`parent_shutdown` records, and startup failures without a child session still delete the task directory).

## Modules

- `src/index.ts` — Pi extension registration, root-session state, tool/command adapters, notifications, interruption recovery
- `src/interrupted.ts` — parent-shutdown candidate selection and notice formatting
- `src/prompts.ts` — dispatch classification and parent/child prompt contracts
- `src/agents.ts` — Pi-native role discovery and parsing
- `src/capabilities.ts` — child tool selection
- `src/lifecycle.ts` — pure invocation phase, budget, accounting, and terminal-cause controller
- `src/runtime.ts` — AgentSession construction, Fork preparation, lifecycle event adaptation, nested execution, thinking diagnostics, worktrees
- `src/tasks.ts` — task persistence, historical normalization, strict target routing, invocation output, retention
- `src/config.ts` — global and trusted-project configuration

## Lifecycle

1. Extension registration defines tools, commands, renderers, and lifecycle handlers.
2. `session_start` restores task metadata for the active root session and runs retention cleanup only when a positive cleanup period is configured.
3. `before_agent_start` injects the current orchestration policy and discovered role listing.
4. `Agent` creates child Pi `AgentSession` instances after awaiting the root-shared FIFO capacity semaphore.
5. Child Pi events feed an invocation-scoped lifecycle controller that owns optional soft turn/tool budgets, selected-tool blocking, wrap-up notices, explicit stop causes, status, and output classification.
6. A configured turn count is a soft wrap-up threshold followed by a grace window (default 1). Continuation beyond `maxTurns + graceTurns` records `partial/turn_budget`.
7. Background completion enters the root session through a Pi follow-up message with exact status and termination kind.
8. `SendMessage` steers a live child or reopens a persisted child session with a fresh invocation boundary.
9. `session_shutdown` records `parent_shutdown`, aborts root-owned live tasks, persists those stopped records, then releases in-memory handles. Pi awaits this handler, so a later reload cannot classify the same tasks as orphans. Because child sessions are persisted, those tasks remain resumable: on the next `session_start` for the same parent session the extension surfaces them once per parent session (`interruptedTaskResume`). `notify` (default) lists recoverable tasks for `SendMessage`; `auto` resumes them through the existing resume path and reports structured success/failure; `off` disables recovery. Empty candidate lists and `off` do not consume eligibility. One-shot tasks and cleaned worktrees are never listed.
10. Parent Stop / tool `AbortSignal` stays attached for the entire blocking `Agent` wait (launch + foreground race). Progress-warning promotion flips the child to supervised background so later parent Stop does not cancel it; only still-blocking foreground children are stopped.

## Dispatch

### Fresh

A named role receives:

- its role prompt
- the complete task brief
- selected Pi skills
- child runtime boundary
- task working directory
- resolved Pi model and thinking level

### Fork

The synthetic `fork` type uses Pi `SessionManager` to create a branch from the current persisted leaf. It carries:

- parent conversation entries
- effective parent system prompt
- parent model
- parent thinking level
- resolved sibling tool-call placeholders

### Resume

A resumable role reopens its persisted `session.jsonl`, reuses recorded budgets and trust, resolves capabilities from the current role definition and current parent inventory, appends a new instruction, and appends new output to the task artifact. Historical effective-tool snapshots are refreshed rather than treated as permanent authority.

### Background

Interactive and RPC launches default to background. Print and JSON launches complete synchronously. Completion is represented as a task notification with status, summary, output file, result, error, usage, and retained-worktree metadata when applicable. Background completion is automatic; normal orchestration does not poll `TaskOutput`.

Notifications are buffered in the extension's notifier owner — Pi cannot revoke an already enqueued message — and flushed as a single `deliverAs: "steer"` send at the parent's next tool gap: arrivals while the parent is streaming wait for that parent turn's `turn_end` (tool results complete, before the next request) so same-turn arrivals merge into one steer; arrivals while the parent is idle flush on a coalesced microtask that triggers one merged turn. A completion replaces a still-pending warning for the same execution, warnings never revive after a terminal result, `TaskOutput` returning a final result drops that invocation's queued notification, and the warning that releases a foreground Agent wait is carried by that tool call's result instead of a second notification. Invocation generations keep late callbacks from a superseded execution (for example after a `SendMessage` resume) from settling or re-notifying the current one.

Background completion and progress-warning sends run on paths no runner handler guards (task-promise finalization and child `turn_end`). They go through a shared send helper that swallows exactly Pi's stale-extension-ctx contract (`This extension ctx is stale after session replacement or reload.`): after a parent session replacement or reload the task record is already persisted, so the notification is dropped rather than thrown into an unguarded background promise. Every other send error stays observable; there is no global `unhandledRejection` handling and no reuse of the old ctx to write the new session.

## Prompt architecture

The parent and child contracts are source-backed behavioral reconstructions adapted to Pi. They preserve architecture-relevant native constraints, including non-duplication, `Never delegate understanding`, no polling or fabricated background results, strict prompt-level read-only role instructions, adversarial verification, and direct Fork execution. Claude-specific identity, paths, configuration, memory, hooks, permissions, and MCP syntax are excluded.

The parent contract covers:

- parent ownership of understanding, synthesis, decisions, and delivery
- early specialist routing for open-ended, cross-module, context-heavy, or path-uncertain work
- immediate one-call fan-out for two or more genuinely independent questions
- two-to-three-way non-overlapping parallel investigation
- research-before-dependent-implementation sequencing
- implementation delegation beyond a couple of edits when isolation, validation, or intermediate output makes it worthwhile
- direct Pi tools for known-file reads, specific searches, two-or-three-known-file inspection, and small edits
- complete Fresh briefs and concrete post-research implementation instructions
- inherited-context Fork directives without recap, peeking, polling, or prediction
- continuation based on useful retained state
- completion-driven background workflow
- mandatory recurring progress supervision for normal work, with task-specific first and recurring checkpoints chosen from scope and risk
- hard budgets retained only as explicit custom-agent/runtime unattended policy
- independent adversarial verification without waiting for the user and parent spot-checking
- bounded, restrained nested delegation

Child contracts define role, scope, evidence, instructed access, delegation depth, and handoff shape. Read-only role constraints are prompt-only: all bundled roles may receive full tools and unrestricted Bash, so runtime capability availability does not replace the role instruction.

The hidden `pi-subagent-agent-listing` reminder is context, so it is injected only when the current branch does not already carry the same listing after the latest compaction: first run of a session, after a compaction removed it, or when the listing content changed (agent/config edits). Reload/resume/fork sessions are covered by scanning the persisted branch. The parent policy appended to the system prompt is re-derived every run because it does not accumulate context.

## Lifecycle semantics

Each launch and resume creates a new invocation controller:

```text
starting → working → final_handoff → terminal
```

Every root invocation uses positive `warning_turns` and `warning_interval_turns`. The runtime fallback and general recommendation are 40/25; callers override them only when assignment scope or risk materially warrants a different documented range. Tasks-array children inherit the top-level values unless their risk differs materially. Every invocation then follows its chosen recurring supervision schedule. Separately, long-running child prompts require concise visible stage notes after meaningful phases and at least every 8–12 tool-using turns, giving the parent fresher progress without turning warnings into a noisy heartbeat. A child that would continue at its chosen first checkpoint emits a structured warning to the root parent; later warnings recur at its chosen interval. Finishing at a checkpoint emits no warning. Warnings are persisted and deduplicated across reload/resume, do not abort or restrict the child, and are not termination kinds. A warning is not a failure, timeout, or proof of a stall; preview text can be stale or empty during active thinking/tool work, and a repeated preview is marked unchanged rather than presented as new progress. The root parent decides directly from the warning's counters and latest stage note: continue while current turns or tool counters advance, use `SendMessage` when supported, and reserve `TaskStop` for explicit cancellation, dangerous/duplicate work, or repeated fresh evidence that useful progress stopped; `TaskOutput` is for explicit user status requests, interruption recovery, or missing detail, not a mandatory checkpoint step. A foreground launch that reaches its first checkpoint is promoted to supervised background execution so the blocked parent regains control; that first checkpoint travels in the Agent tool result, and the eventual completion is delivered as a root notification at the parent's next tool gap.

Turn and tool budgets remain optional advanced unattended policy. When configured, `maxTurns` is a soft wrap-up threshold with `graceTurns` (default 1). At the threshold the runtime sends one constructive wrap-up instruction; tools remain available during grace so the child can finish a coherent step. Continuation past `maxTurns + graceTurns` becomes `partial/turn_budget`. `maxToolCalls` is the hard threshold of an optional tool budget; after it is reached, only tools in `toolBudgetBlock` (default `read`, `grep`, `find`, `ls`) are blocked. A valid final report after selected-tool blocking completes normally; only a missing usable report becomes `partial/tool_budget`. Timeout remains `partial/timeout` when a timer is configured. Explicit parent stop becomes `stopped/manual_stop`, parent shutdown becomes `stopped/parent_shutdown`, and startup/provider failures become `failed`.

Tool counters distinguish model requests, admitted executions, and blocks. Compatibility `toolCalls` equals executed calls. Pi `stopReason: aborted` is evidence, not the authoritative task status; explicit lifecycle causes take precedence.

Output extraction is invocation-scoped. An empty aborted terminal message cannot reuse an older progress sentence from the same or a restored session.

Thinking records retain requested and effective levels plus a clamp reason. Runtime respects Pi's effective model capabilities and does not pretend an unsupported level was accepted.

Task routing is strict: exact UUID, unique UUID prefix, then unique name. Ambiguity returns candidate task IDs.

## Role discovery

Roles are loaded from:

1. package `agents/`
2. `${getAgentDir()}/agents/`
3. trusted project `.pi/agents/` directories along the repository path

Later and closer scopes replace earlier definitions with the same name.

## Child capability model

The child coding runtime consists of Pi coding tools:

```text
read bash edit write grep find ls
```

Selection pipeline:

1. intersect with the active parent inventory;
2. apply role `disallowedTools`;
3. apply the role `tools` selection;
4. add the child `Agent` adapter when nesting is enabled for the role and depth.

Bundled roles use `tools: "*"`, so Explore, Plan, verification, general-purpose, Fresh, and Fork workers receive all available parent builtin tools, including `bash`, `edit`, and `write`. Bash is unrestricted. `readonly` and `shellPolicy` remain accepted and persisted as compatibility/descriptive metadata, but they do not automatically remove tools or inspect commands. Explicit custom `tools`/`disallowedTools` inventories remain effective.

The selected names are passed to `createAgentSession({ tools })`. The nested `Agent` adapter is supplied through `customTools`.

## Skills and context

`DefaultResourceLoader` discovers Pi skills under the parent's effective trust state. Selected role skills are preloaded into the child role prompt. Fresh roles use explicit role and task context. Fork workers carry the effective parent system prompt and branch entries.

## Nested execution

Nested agents execute synchronously inside their direct parent AgentSession and return a normal tool result. This gives the direct parent the evidence needed for consolidation while preserving root-context isolation.

- root-shared task quota
- maximum depth 5
- persisted ancestry
- named child roles
- root-level inherited-context branching

## Trust and safety

The runtime deliberately trusts child roles with the capabilities selected by their role inventory. Bundled roles request full available tools and unrestricted Bash; Explore, Plan, and verification remain non-modifying only by strict prompt instruction. This is not a security sandbox. Use OS permissions, containers, VMs, or equivalent process isolation for untrusted models or tasks.

### Model fallback

An agent may declare a `fallbackModel` (frontmatter, global `subagents.fallbackModel`, or per-agent override). The launch builds an ordered model chain from the resolved primary reference plus a distinct fallback. Two independent layers use it: model resolution falls back to the declared fallback when the primary cannot be found or authenticated, and the runtime launch loop retries once with the fallback when a child fails on its first model call before performing any work (0 turns, no output, no tool calls) — the transient provider-outage case such as a 503 auth error. Retry rebuilds the child lifecycle and usage baseline; budgets, warnings, stop semantics, and the foreground-release race are unchanged.

Runtime-enforced controls are:

- explicit custom role tool selection and parent-inventory intersection
- mandatory recurring root-parent progress supervision with persisted checkpoints (default/general recommendation 40/25)
- concise child stage notes after meaningful phases and at least every 8–12 tool-using turns
- foreground-to-supervised-background promotion at the first warning
- optional task timeout for explicit unattended policy
- optional soft turn budgets with grace
- optional tool budgets with selected-tool blocking
- root-shared FIFO concurrency semaphore
- trust propagation to child SettingsManager and ResourceLoader
- atomic task metadata writes
- parent-visible output bounded by bytes and lines, with full artifact retention
- opt-in age-based retention cleanup
- partial-output preservation
- clean-checkout worktree creation

## Persistence

Task records are durable from the moment a launch returns (persist-before-return; see External seam).

```text
<getAgentDir()>/pi-claude-subagents/<root-session-id>/<task-id>/
  task.json
  output.md
```

Child session JSONL files live in Pi's standard session catalogue (`<getAgentDir()>/sessions/<project>/...`, normally `~/.pi/agent/sessions/<project>/...`), not under the task artifact directory. Fresh children are created in the parent session file's directory with `parentSession` set to the absolute parent session path when available; otherwise they use Pi's default catalogue for the child cwd. Fork children remain at the path returned by `createBranchedSession()` (also in the parent catalogue) with the branched `parentSession` header. `TaskRecord.sessionFile` stores that external standard path for resume and tooling. Age-based task cleanup removes only task metadata/output directories and must not delete standard child sessions.

Task records include ancestry, depth, role, status, termination kind, model, requested/effective thinking and clamp diagnostics, warning schedule/checkpoint state, optional hard budgets, requested/executed/blocked usage, trust, output paths, worktree metadata, and lifecycle timestamps.
