@tylerho/pi-subagents

Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.

Packages

Package details

extension

Install @tylerho/pi-subagents from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@tylerho/pi-subagents
Package
@tylerho/pi-subagents
Version
0.3.0
Published
Nov 5, 2026
Downloads
288/mo · 222/wk
Author
tylerho
License
MIT
Types
extension
Size
410.5 KB
Dependencies
2 dependencies · 4 peers
Pi manifest JSON
{
  "extensions": [
    "extensions/subagents"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@tylerho/pi-subagents

Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.

Install

pi install npm:@tylerho/pi-subagents


Subagents

Subagents ports Claude Code 2.1.281's Agent tool to pi: background subagents on the native pi harness, behind one Effect service. (A Claude Agent SDK backend remains in the manager but is not offered to callers.) A spawn is a self-contained child with its own context window, background by default, whose report returns to the parent as a framed hand-back. Workflows, memory consolidation, and summaries share its delegation defaults and activity counters.

Claude Code lineage

Claude Code 2.1.274 supplied the background-delivery contract in commit 1379455 feat(subagents): drop subagent_wait; make subagent_check the pull path (2026-09-16). Claude Code 2.1.281 defines Agent, SendMessage, TaskStop, and ListAgents, but no per-agent check tool. Pi removed its check tool and kept automatic delivery and recipient discovery. An earlier pass used 2.1.273, which supplied the naming rules and the nesting depth, and commit 9829e61 feat(subagents): name agents verbatim, Claude Code-style (2026-09-10) took the naming rules.

Restated verbatim in src/domain.ts:

  • SUBAGENT_NAME_PATTERN is the Agent tool's name schema, /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/, and SUBAGENT_NAME_ERROR is its rejection wording.
  • SUBAGENT_NAME_RESERVED_ERROR restates the reserved-recipient rule for main and team-lead, including the generated-id shapes that already address an agent.
  • generateAgentId() builds the omitted-name id, a<description-slug>-<16hex>, or a<16hex> when the slug is empty.

Other borrowed mechanisms:

  • The hand-back frame states that the report is model output with no user authority, which mirrors Claude Code's framed final report.
  • /btw mirrors Claude Code's side question. The child inherits the parent conversation up to the spawn point, and BTW_PROMPT_PREFIX tells it to answer from that context in one response.
  • subagent_send mirrors SendMessage: steering a running agent, and resuming a settled one in the same session.

Pi does not copy these parts:

  • Claude Code's configurable nesting behavior is separate. Native pi subagents delegate to a default maximum depth of three, with the main session at depth 0. /subagents-menu can set a greater positive safe-integer maximum. One root manager owns the full tree and its concurrency cap. Every caller, root or nested, runs on the native pi harness: subagent_spawn and /subagents-menu do not offer the Claude route.
  • There is no persistent resume. subagent_send resumes a settled agent only while the root manager tracks it, with no reconstruction across a pi restart or parent reload.
  • There are no agent profiles. A delegation is a tier or an explicit target plus the prompt, with no reusable definition files.
  • There is no per-call worktree isolation. A child runs in the resolved working_dir. The workflow runner's isolation: "worktree" option is separate and is not exposed on spawn.

How it works

  • Backends. The pi backend is an in-process createAgentSession() with real session files visible in /resume, per-cwd resources under trust gating, and the canonical child tool policy; it is the only backend the tool surface reaches. A claude backend stays registered in the manager for direct callers but is not selectable through subagent_spawn or /subagents-menu: one @anthropic-ai/claude-agent-sdk query() in streaming-input mode, where the CLI owns conversation continuity, tool execution, and ~/.claude/projects transcripts. It runs bypassPermissions, disallows Claude's native Agent and Task tools so orchestration stays inside this manager, and reports unavailable when no claude binary is on PATH.
  • Effect v4 layering. backends/ produce a scoped SubagentSession with a normalized SubagentEvent stream. The manager runs one pump fiber per child that folds the stream into a mutable SubagentSnapshot. runtime.ts composes the layer into one ManagedRuntime, and index.ts is the async boundary where tool handlers run effects through runTool(). A typed failure becomes a thrown Error, and an AbortSignal interruption throws interruptMessage.
  • Semantic tiers. The parent picks one of three fixed names, fast, standard, or deep. Each is a preset for a pi model and effort in the local config, and an omitted tier means standard. A fourth claude tier is still parsed and resolvable in shared/subagent-models.ts (workflow agents reject it, and the picker's internal list retains it), but it is not exposed to callers. Resolution is pure, through resolveDelegationTarget(), so the same tier map serves subagent spawns and workflow agents.
  • Explicit overrides. model, with optional reasoning_effort, is the one-off override path; the harness is always pi. Without a tier, a model is required (an effort alone is rejected). With a tier, each explicit field replaces that part of the preset: model replaces the preset model, and reasoning_effort alone keeps the preset model and its tier source (so no cost-ceiling check). A new model makes the target explicit. A "provider/model" override splits at the first slash, and a bare model uses the standard tier's provider for the registry lookup. An omitted effort falls back to the standard tier effort. Spawn results name the resolved model and effort.
  • Spawn contract. description is required, a short task label for the UI and the generated-id slug, capped at 200 characters. prompt is required and must be self-contained, because a model-origin child cannot see the parent conversation. name is optional and only controls addressing.
  • Ids and names. Pi stores one agent handle, not a separate name alias and an internal id. A supplied name must match SUBAGENT_NAME_PATTERN, must not normalize to main or team-lead, and must not have the shape of a generated id, or the spawn is rejected. A valid name becomes the id verbatim, with no prefix and no slug, so a parent that sets name: "researcher" passes "researcher" to every later id or ids parameter. An omitted name gets generateAgentId(description), and the parent uses the generated id returned by subagent_spawn. Repeats get a numeric suffix, so fix-login-bug becomes fix-login-bug-2. Extension-generated btw ids use slugifyTitle instead, because btw descriptions are free-form questions. Ids stay unique per parent session and are the handle for subagent_cancel and subagent_send.
  • Foreground and background. run_in_background defaults to true. A background spawn returns at once, and its settled run is delivered later as a subagent-result follow-up message (deliverAs: "followUp", triggerTurn: true) when the parent is idle or on the next agent_settled. A foreground spawn claims its run at spawn time, waits for settlement, and returns the hand-back from the tool call. A tool abort releases the claim and leaves the child running.
  • Per-run, exactly-once delivery. Delivery is keyed by a run ref (id#runSequence), not by agent id, because a settled agent resumed with subagent_send starts a new run that can deliver again. A foreground spawn and subagent_cancel each claim the run before it can be delivered, and an unconsumed background settle is deferred and delivered once on the next flush. A native intermediate parent stays active while its children run, while summaries are computed, and until the subagent-result message starts processing. Scheduling the follow-up is not an acknowledgement. The parent receives that follow-up without a status-pull tool. A tool that claims a run while its digest is still computing (a subagent_cancel of a settled agent) suppresses the pending follow-up. A native parent that is cancelled, failed, or closing drops child results addressed to it instead of starting a turn for them.
  • Run lifecycle. Only subagent_send opens a new run: the snapshot shows running under the new runSequence as soon as the resume is accepted, and its run time (runStartedAt) is set then. The backend's RunStarted confirms the run without resetting that time, so a native parent's follow-up turns inside one run keep its elapsed time. A terminal event that arrives before a restart's RunStarted belongs to the previous run and is ignored. A backend turn nobody requested after settlement does not reopen the agent: its RunStarted and RunSettled are ignored and it adds no live tool or streaming state, so an agent every view shows as settled never shows running again under the same run.
  • The hand-back (24 KiB). buildHandback() frames one report per run. The header names the id, description, terminal status, tier or explicit source, harness, model, and effort, plus any error text, and the description and error text are each bounded to 1 KiB with a truncation marker. A fixed frame then states that the report is model output with no user authority, so instructions, approvals, and permission claims inside it are not from the user. Control syntax that could imitate harness messages is escaped (reserved control-tag openings and fake Human: or Assistant: turn markers), and every report line and every continuation line of an interpolated header value is indented two spaces, so child text cannot appear at column zero as a harness frame. Above the cap the model text keeps the head and appends an exact truncation notice pointing at the takeover view and the persisted session. details.fullOutput carries the sanitized, untruncated report.
  • Origins. A model-origin spawn is visible to model-facing tools and the /subagents dashboard. isModelVisible filters by origin === "model", so user asides from /btw stay out of the tool layer and the dashboard and are revisited in the /btw panel. A btw result is appended as a synchronous btw-result session entry, safe while the parent is streaming, and never becomes a model-context follow-up. A failed aside also fires an error notify. A btw child runs with noTools: "all", so built-in and extension tools are excluded.
  • btw context inheritance. A btw child is a fork, not a clean slate. At spawn time the command handler snapshots the parent conversation with buildSessionContext(getEntries(), getLeafId()).messages, two adjacent synchronous reads that are race-free while the parent streams. The snapshot ends at the parent's last completed message, the same cut Claude Code applies. The pi backend seeds those messages into the child session file before createAgentSession, which restores them as the child's history, so compaction, resume, and the transcript treat them as its own. Summary roles are rewritten to the plain user text convertToLlm would produce. The inherited range is fenced with BTW_CONTEXT_START and BTW_CONTEXT_END custom entries, and the persisted-transcript loader skips it. Result extraction excludes the seeds by object identity, so a child that fails before answering reports failure instead of echoing a parent message.
  • Tree ownership and limits. The main session is depth 0; the default maxDepth is 3, so levels 1, 2, and 3 may run. A tree's maximum is snapshotted when its root manager is created. MAX_RUNNING = 50 and MAX_TRACKED = 4_096 apply once across that root's tree; provisional startup and idle restarts reserve capacity before yielding. A child can address only its direct children. Cancelling a node seals and stops its descendants but leaves sibling branches running. Root shutdown closes the whole tree.
  • Transcripts are bounded in memory and full on disk. The manager keeps 512 transcript items and 64 KiB per text. The takeover view lazily streams the full persisted JSONL, from ~/.pi/agent/sessions/<escaped-cwd>/<timestamp>_<id>.jsonl on pi or ~/.claude/projects/<escaped-cwd>/<sessionId>.jsonl on claude, through the src/persisted/ parsers, with the format auto-detected per record and the file read line by line. mergeTranscripts merges the persisted snapshot with the live transcript, and live items always win, so streamed content never vanishes when it finalizes. The persisted load refreshes once when the run settles. The persisted parsers also keep what the live pipeline bounds: the full pretty-printed tool arguments (args) and the full tool output. ctrl+o reveals both in the takeover view, redacted for secret-shaped values, and mergeTranscripts backfills full arguments onto a live tool call when its persisted item exists. A live tool call the persisted file does not cover yet falls back to its bounded argsPreview.
  • Delegation defaults and the cost ceiling. With no explicit model, a spawn runs the tier's configured target from shared/subagent-models.json, set by /subagents-menu, and never the parent's model. The cost ceiling (disabled by default, set with /subagents-menu, overridden per launch by PI_SUBAGENT_COST_CEILING) applies only on the pi harness and only to an explicit model an agent picked for itself. A tier target, a configured default, and a btw aside are deliberate user choices and are not checked.
  • Child safety rails. childToolPolicy() defaults to non-delegating for workflows and memory workers. A bound native subagent receives spawn/send/cancel/list when its depth allows; at the maximum depth the spawn tool is excluded. Retired subagent_wait and subagent_check, workflow, ask_user_question, enter_worktree, exit_worktree, and code_review remain excluded in every child context. The retained claude backend disallows Claude's native Agent and Task. Every child tool call is wrapped with a 30-minute execution timeout from shared/tool-call-timeout.ts, re-applied on agent_start so tools registered mid-session are covered.
  • Trust gating. A child in the parent's directory inherits the parent's trust decision. An alternate working_dir is trusted only when pi's persisted ProjectTrustStore explicitly trusts it, and unreadable or invalid trust data fails closed. A claude child in an untrusted cwd gets settingSources: ["user"], so an untrusted project's config cannot reconfigure it.
  • Progress line. In the root TUI, createProgressTracker() in src/progress.ts refreshes a one-line status for each running model-origin agent every progressIntervalMs (30 s by default). Each refresh is one fast-tier summarizeReport call with PROGRESS_PROMPT, thinking off and a 40-token cap. Its input is buildProgressNotes(): the task description, the previous status, and one capped line per item from the last 8 transcript items plus in-flight tools, at most 1,500 characters. The full transcript is never sent. An agent whose activityFingerprint() (transcript length plus live tool counts) has not changed since its last refresh gets no call, so an idle or briefly running agent costs nothing. Calls run one at a time, a failed call keeps the previous line, and the line is dropped when the run settles. Tracker state is keyed by run ref, so a resumed run starts fresh.
  • Agent panel. A belowEditor widget (src/ui/task-rail.ts) lists the root's all-descendant read model in Claude Code's panel layout; model-facing list and control tools remain immediate-child scoped. Membership comes from the pure panelRows(snapshots, now) in src/ui/panel-model.ts: model-origin agents only, a running agent always, a done run never (it leaves on its settle event), and error or cancelled runs while now - settledAt is under PANEL_LINGER_MS (30 s). Rows are in tree order: roots by creation time, each followed by its shown descendants with ├/└ connectors, and a running child whose parent left is promoted to a root. Each row shows the pointer, the status glyph (braille spinner in the warning color while running, red ✘ failed, warning ✘ stopped), the agent id in a column of at most 28 cells, the activity (the progress line, else the description; Failed: <first error line> or Stopped once settled), and right-aligned elapsed · ↓ tokens · cost, plus · N queued in the warning color. Elapsed runs to now while running and stops at settledAt. The activity truncates before the stats. panelWindow shows at most 5 rows with ↑ N more and ↓ N more, and keeps the selection on the row that took a departed row's place. The spinner ticks once a second only while something runs, and nextExpiry arms one timer for the next lingering row, so rows clear without other events. Unfocused, the last line is ↓↓ to manage. In the default editor view with empty editor text, a lone down-arrow reaches the editor and a second within 500 ms focuses the panel on its first row; then up and down move without wrapping (up at the top returns focus), Enter opens takeover, x stops a running agent, Esc closes, and any other key returns focus to the editor and reaches it. A focused panel that empties closes itself. Finished agents stay reachable through /subagents. Key release and repeat events are filtered, and inside a modal the gesture yields to the focused component.
  • Lifecycle and cross-extension signals. Native child sessions bind to their manager by SessionManager identity before extension hooks run. A persisted subagent-ancestry entry has only serializable identity; reopening it without its live controller disables delegation. The in-memory tree is not reconstructed after an extension reload or a separate transcript open; shutdown closes the old tree, and the next root manager uses the current depth config. A completion gate keeps a parent active through direct-child work, digest generation, scheduled hand-backs, and actual message processing. agent_settled flushes deferred results. A child shutdown revokes its own subtree and subscriptions without disposing the root runtime; root shutdown disposes the full tree. The manager publishes its running count with reportRunning("subagents", n) at transition points, which summaries reads to gate recaps. Both backends publish child spend through shared/child-cost.ts: pi adds each assistant message's usage.cost.total, and claude adds only the positive delta of the result message's total_cost_usd, so a resumed run is not double-counted. The expanded footer reads that accumulated total, and the takeover header renders the snapshot's own usage.costUsd through formatCost.

API

Tools

subagent_spawn spawns on a tier preset or with an explicit override. Parameters: description (string, required, max 200 chars), prompt (string, required), name (optional string: addressable handle that becomes the id verbatim for every id-based tool), tier (optional "fast" | "standard" | "deep", omitted means standard), working_dir (optional string, must exist and be a directory, default parent cwd), run_in_background (optional boolean, default true), model (optional string, required for an explicit override: "provider/model-id" or a bare id), reasoning_effort (optional "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"). The tool schema has a top-level object with optional target fields, as DeepSeek requires. Explicit fields override the matching part of a tier preset; without a tier a model is required, and the harness is always pi. Results identify the resolved values. A background spawn returns text like Spawned fix-login-bug "fix login bug" (preset "standard" → harness="pi", model="deepseek/deepseek-v4-pro", reasoning_effort="high"). plus the tail telling the parent to report what it launched and to say the agent is still running if the user asks. A foreground spawn returns the same header plus Waited for it; the report follows. and the run's hand-back. details: { id, description, cwd, harness, tier, model, effort }. Rejections: the 50-agent cap (ConcurrencyLimitError), an invalid or reserved name, an explicit effort without a model, a bad working_dir, an unavailable configured or requested model, and an over-ceiling explicit model on pi.

{
  "description": "fix login bug",
  "prompt": "Refactor src/util.ts: split the two exported functions into separate files, run the test suite, report the diff stat.",
  "name": "fix-login-bug",
  "tier": "standard"
}

subagent_send steers a running subagent or resumes a settled one in the same parent session. Parameters: id (string: the supplied name, or the generated id returned by subagent_spawn), message (string). While the agent runs the message is a course correction to the active run. When the agent is settled the call starts another turn in the same child session as a new runSequence and returns the Resumed acknowledgement. A steer returns Steered <id> (run N). The transcript shows the first line of message as a one-line preview, so the message should open with a self-contained sentence saying what it is about. A resumed turn counts against the concurrency cap, and a backend rejection rolls back the restart reservation and the snapshot's previous status. A send to an agent with a subagent_cancel in flight is rejected until the cancel reports. details: { id, runSequence, restarted }. An unknown or pruned id fails through the shared unknown-id error, and a SendError surfaces to the model and inline in the takeover view.

subagent_cancel aborts running subagents. Parameters: ids (array of string: supplied names or generated ids). The call claims each run before interrupting (a 5 s graceful session.interrupt with a force-close fallback that settles first, so the stream-ended fallback cannot misreport), waits for settlement, and reports Cancelled <id> "<description>". or <id> "<description>" was already <status>. followed by each run's hand-back. Cancelled (and cancelled: true) means the run settled as cancelled; a run that finished or failed on its own while the interrupt was in flight reports was already done or was already error, partial output included. It consumes the deferred automatic delivery for every listed run, settled or just cancelled, and the combined report is capped at 48 KiB. details: { results: [{ id, description, status, cancelled }] }. Partial session transcripts stay on disk.

subagent_list finds the ids of tracked model-origin direct children for subagent_send or subagent_cancel. It is not a completion poll. No parameters. One describeSubagent() line per model-origin child, in the form id [status] "description" (tier, backend: model, token occupancy, elapsed, cwd), or No subagents. when empty. details: { subagents: [{ id, description, harness, status, tier }] }. The root dashboard and task rail show all descendants.

Commands

All three are TUI-only. In a non-TUI mode a command notifies when it has a UI and returns.

Command Args Behavior
/btw optional prompt text Spawns a pi-harness child with origin: "btw" and a description from deriveBtwTitle (first non-empty prompt line, 60 chars max), then opens the BtwPanel bottom dock. The panel lists past asides as /btw <question> lines above the selected aside's answer: left and right switch asides, up and down or j and k scroll, n asks a new question through ctx.ui.input, c copies the answer, ctrl+t toggles reasoning (hidden by default), Esc closes. With no args and history it opens on the newest aside, and with no history it goes straight to the question input. The child runs without tools on a session file seeded with the parent conversation, and its result arrives as a btw-result entry, not a model follow-up.
/subagents none Opens the fullscreen dashboard of all model-origin descendants in the root tree, with btw asides excluded. j/k or the arrow keys move the selection, Enter takes over, and x aborts a running agent.
/subagents-menu none Shows all four tier mappings, cost ceiling, maximum depth, and summary settings. Select a tier to choose its model and effort. Each completed edit saves immediately and returns to the refreshed menu. Cancelling an edit leaves the setting unchanged and returns to the menu; Esc in the main menu closes it. Validation and save errors notify and return to the menu.

The menu replaces /subagent-model, /subagent-cost, and /subagent-depth. It lists the three pi tiers; each uses the curated model list with prices and ceiling markers, then the model's supported thinking levels. The Claude tier is not shown because the route is disabled, but its stored config entry is preserved. The cost ceiling accepts a positive amount or off and shows the effective launch override when it differs from the saved value. Depth requires a positive safe integer and applies to new root managers only.

Summary settings include enabled, input cap, raw report cap, skip threshold (all three in characters), and timeout (milliseconds). Numeric settings require positive safe integers, except the skip threshold permits zero. Empty numeric input cancels. All configuration files remain local-only.

Events

  • session_start resolves native delegation by session id and reads persisted ancestry from the active branch. A child without its live binding fails closed. It captures the ExtensionContext; in root TUI mode it registers the all-descendant task rail through setWidget("subagent-task-rail", …, { placement: "belowEditor" }) and the raw onTerminalInput handler for the down double-tap gesture.
  • agent_settled runs flushResults(), which drains the deferred-result queue and delivers each entry as a subagent-result follow-up message. Native parents acknowledge its run ref at message_start; a send call returning does not count as the report being processed.
  • session_shutdown clears sessionContext, removes the task-rail widget, unsubscribes the raw-input handler, resets the rail and the result queue, then either revokes a bound child's descendants or disposes its owned root runtime. Child session_shutdown hooks are emitted with a 5 s bound.

Renderers

  • subagent_spawn calls in one parent response cycle form one collapsed tree, even when tool results split them across assistant messages. The cycle is keyed to the nearest preceding user message, so the next user message starts a separate group. The first call row shows Running N agents… while any spawn is pending, then N background agents launched with the ↓↓ select management hint and one ├/└ description row per call. Failed calls use the error color. Sibling call and result rows are hidden while collapsed. Expanded mode shows each call and its full result independently. A single spawn keeps its own compact call and result rows, split so neither repeats the other: the call row carries only the description, and the result row carries the id, tier, harness, and model. Expanded results add the working directory and reasoning effort; the model-facing spawn text is not echoed, since it is guidance for the parent model and already in the tool result.
  • registerMessageRenderer("subagent-result") renders a settled follow-up in Claude Code's completion format, ⏺ Agent "<description>" finished · <elapsed> · <cost> (failed: <first error line> or was stopped otherwise), with the dot in the status color and the id standing in for a missing description. Collapsed it adds the one-line digest. It does not display digest-call cost or token counts. Expanded it shows details.fullOutput capped at rawCharCap, with a notice that points to takeover and, when known, the persisted session file. The framed report lives in details, and the message content stays the model-facing hand-back.
  • registerEntryRenderer("btw-result") renders /btw answers as session entries. Collapsed it shows the by the way · description · answered/failed header and a /btw to reopen hint. Expanded it shows the full markdown body.
  • subagent_send uses a compact result-only row after the call completes, so the call and result do not repeat the message preview. The row shows ⏵ id · steered/resumed (run N). Expanded mode shows the full message and resume guidance when relevant. Errors show the error text.
  • subagent_cancel renders one line per result (Cancelled id "description" or id "description" was already status) and puts the per-run hand-backs behind expansion, capped at rawCharCap.
  • Token occupancy is rendered as a compact token count such as 40k, without a context-capacity denominator or percentage. Run cost remains visible where the snapshot provides it.

Config

  • <agentDir>/subagent-nesting.json holds { "maxDepth": 3 } by default. Missing or malformed values use 3; valid values are positive safe integers. /subagents-menu writes it atomically. The file and its temporary writes are local-only and excluded from packages and private backup candidates. It does not change model tiers.
  • extensions/shared/subagent-models.json holds the persisted version 2 config, { version: 2, costCeiling, tiers: { fast: { harness: "pi", provider, model, effort }, standard, deep, claude: { harness: "claude", model, effort } } }. The file is local-only (gitignored and excluded from published packages), so a fresh checkout runs on DEFAULT_DELEGATION_CONFIG with every pi tier on deepseek/deepseek-v4-pro at high, the claude tier on sonnet at high (parsed but not selectable), and no ceiling. A legacy version 1 { costCeiling, pi, claude } file still loads: pi seeds fast, standard, and deep, claude seeds the claude tier, and the ceiling carries over. Migration happens in memory, and the file is rewritten as version 2 only after a save. Read through loadDelegationConfig(), written atomically (temp file plus rename) by /subagents-menu through saveDelegationConfig().
  • The env var PI_SUBAGENT_COST_CEILING is a per-launch override of the file value, in USD/Mtok output, with "off" disabling the ceiling.
  • <agentDir>/subagent-summary.json is the optional local config for the settle-time digest and the progress line: { enabled, inputCharCap, rawCharCap, skipUnderChars, timeoutMs, progressEnabled, progressIntervalMs }. Every field falls back on its own, so a missing or corrupt file means the defaults. enabled defaults to true, inputCharCap to 8000, rawCharCap to 4000, skipUnderChars to 300, timeoutMs to 15000 (also bounding each progress call), progressEnabled to true, and progressIntervalMs to 30000. The menu saves it atomically through saveSummaryConfig(). The file and its temporary writes are local-only and gitignored. A fresh checkout runs on the defaults.

Exports

  • index.ts: default (pi: ExtensionAPI) => void, and consumeReturnedRuns().
  • domain.ts: BACKEND_NAMES, SUBAGENT_NAME_PATTERN, SUBAGENT_NAME_ERROR, SUBAGENT_NAME_RESERVED_ERROR, validateSubagentName, generateAgentId, REASONING_EFFORTS, runRefOf, latestText, formatElapsed, types BackendName, SubagentOrigin, ReasoningEffort, SubagentStatus, ParentContext, SpawnTask, SubagentMeta, SubagentSnapshot, formatAncestry, TranscriptPart, TranscriptItem, LiveToolState, QueuedMessage, RunOutcome, SubagentEvent, SubagentRunRef, SubagentUsage, and tagged errors SpawnError, BackendUnavailableError, ConcurrencyLimitError, SendError.
  • backend.ts: SubagentBackend, SubagentSession, BackendCapabilities, BackendRegistry.
  • backends/pi.ts: piBackend, piChildToolOptions, toSeedMessage, seedInheritedBtwContext, SeedableMessage. backends/claude.ts: claudeBackend, contextOccupancyTokens, costDelta. backends/stub.ts: makeStubBackend, StubProfile. In contextOccupancyTokens, the per-request assistant-message usage is the occupancy, and the result message's whole-run usage aggregate must never be used as one.
  • manager.ts: SubagentManager, SubagentManagerLive, MAX_RUNNING, MAX_TRACKED, slugifyTitle, SubagentManagerShape, SubagentOwner, SubagentReadModel, CancelResult, SendResult, SpawnOptions.
  • runtime.ts: createSubagentRuntime({ maxDepth? }), SubagentRuntime, runTool().
  • nesting-config.ts: DEFAULT_MAX_DEPTH, parseNestingConfig, loadNestingConfig, saveNestingConfig, parseDepthArgument, runNestingDepthCommand. delegation.ts: NativeDelegationBinding, createNativeDelegationBinding, registerNativeDelegation, resolveNativeDelegation, serializeAncestry.
  • completion.ts: createCompletionGate, CompletionGate, and DeliveryReservation. The gate tracks SDK turn activity, immediate-child work, and run-ref delivery until message processing.
  • handback.ts: HAND_BACK_MAX_BYTES (24 KiB), buildHandback({ snapshot, output }) returning { modelText, fullOutput, truncated }.
  • prompt.ts: the SUBAGENT_*_TOOL_DESCRIPTION constants, the *_PARAMETER_DESCRIPTIONS maps, SUBAGENT_SPAWN_PROMPT_SNIPPET, SUBAGENT_SPAWN_PROMPT_GUIDELINES, buildNestingPrompt, buildSubagentSpawnResult, describeSubagent, delegationLabel. This is the single source of model-facing strings.
  • by-the-way.ts: deriveBtwTitle, isModelVisible, BTW_TITLE_MAX_LENGTH, BTW_PROMPT_PREFIX, BTW_CONTEXT_START, BTW_CONTEXT_END, isBtwContextStart, isBtwContextEnd. result-delivery.ts: runRefKey, acknowledgeDeliveredResult, createDeferredResultDelivery. format.ts: formatContextTokens, formatCompactTokens, formatCost, capChars, truncationNotice, ContextUtilization. summarize.ts: summarizeReport, shouldSummarize, makeSummarizeDeps, fastSummaryTarget, buildSummarizeRequest, SUMMARY_TIMEOUT_MS, SUMMARY_MAX_TOKENS, SUMMARY_PROMPT, SummarizeResult, SummarizeDeps, SummarizeTarget. progress.ts: createProgressTracker, buildProgressNotes, activityFingerprint, cleanProgressLine, PROGRESS_PROMPT, PROGRESS_MAX_TOKENS, PROGRESS_NOTES_CHAR_CAP, ProgressTracker. summary-config.ts: loadSummaryConfig, parseSummaryConfig, saveSummaryConfig, summaryConfigPath, DEFAULT_SUMMARY_CONFIG, SummaryConfig.
  • ui/: openSubagentPicker, openSubagentTakeover, reconcileDashboardSelection, configuredKeys, statusGlyph, statusWord, takeoverHeader, fitTakeoverHints, DashboardSelection (takeover.ts). BRAILLE_SPINNER_FRAMES, spinnerGlyph, StatusLike, statusGlyph, statusWord (activity-row.ts). SpawnCallArgs, SpawnDetails, SpawnBatchCall, SpawnResult, SpawnGroupMember, SpawnGroupView, SpawnGroupCoordinator, createSpawnGroupCoordinator (spawn-group.ts). completionHeaderText, digestLineText (result-row.ts). openBtwPanel, BtwPanelResult (btw-panel.ts). TaskRailController, railRows, createTaskRail, DOUBLE_TAP_MS (task-rail.ts). inPanel, panelRows, nextExpiry, panelWindow, PANEL_LINGER_MS, PANEL_MAX_ROWS, PanelRow, PanelWindow (panel-model.ts). firstLine, sendRowText, sendCallRowText, sendExpandedText, rowComponent (send-row.ts). findFocusedComponent, isDefaultEditorFocused (focus.ts). buildTranscriptLines, buildBtwAnswerLines, sanitizeText, mergeTranscripts (transcript.ts). pickTierConfig, pickEffort, applyTierSelection, tierTargetLabel, TierPickerUi, TierPickerDeps, TierPickerResult (model-picker.ts). runSubagentsMenu, SubagentsMenuUi, SubagentsSettingsStore (settings-menu.ts).
  • persisted/: readPersistedTranscript (lazy async generator), loadPersistedTranscript, detectSessionFormat, SessionFormat (transcript.ts). parsePiEntry (pi.ts). ClaudeParser and createClaudeParser (claude.ts), plus the shared parseJsonLine, safeJsonPreview, safeJsonArgs, previewOf, textOf, isRecord.

Examples

  1. Delegate on a tier and keep working. subagent_spawn { description: "errors survey", prompt: "Read docs/errors.md and list the three most common failure modes with their codes, as a bullet list.", name: "errors-survey", tier: "fast" } returns a background receipt, and the report arrives later as a subagent-result follow-up. The parent keeps working or ends its turn when the result is all it needs. The final report arrives as a follow-up without polling subagent_list or sending progress requests.
  2. User-requested override. When the user names a specific model, subagent_spawn { description: "todo sweep", prompt: "In this repo, find every TODO and categorize by owner file; write the result to TODOS.md.", name: "todo-sweep", model: "deepseek/deepseek-v4-pro", reasoning_effort: "medium" }. Omit model and reasoning_effort unless the user named them, because a tier already carries the configured model and effort.
  3. Steer or resume. subagent_send { id: "errors-survey", message: "Also note which codes are retryable." } steers the run while it works. If the agent has settled, the same call starts a new run in that child session, and the next report is delivered like a spawn.
  4. User aside. The user types /btw what changed in the subagent API in this update?. A child inherits the conversation up to that point and answers while the main agent keeps working. The answer appears as a one line by the way · … session entry, reopened in the /btw panel or expanded inline.
  5. Manage. /subagents opens the dashboard of model-origin subagents (j/k or the arrow keys select, x aborts a running agent, Enter takes over). In takeover, the view shows a two-line identity and status header, a fixed-height transcript, and a composer. User messages use the parent message surface, assistant text uses pi Markdown, and tool states use the parent tool colors. Thinking starts as a short preview with a ctrl+t hint. Tools use a compact A1 layout with one call line and one result or state line. ctrl+o reveals the full redacted tool arguments and output from the persisted transcript, ctrl+t toggles thinking, up/down or pgup/pgdn scroll, ctrl+c aborts a running child, the interrupt binding closes the view, and typing at the input line steers or continues it. The view follows new output until the user scrolls up. /btw with no args opens the aside panel (left and right switch questions, n asks a new one, c copies the answer). /subagents-menu changes a tier's model and effort.