@juvio15/pi-subagents

Multi-agent coordination tools for Pi

Packages

Package details

extension

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

$ pi install npm:@juvio15/pi-subagents
Package
@juvio15/pi-subagents
Version
0.2.0
Published
Aug 3, 2026
Downloads
47/mo · 47/wk
Author
juvio15
License
Apache-2.0
Types
extension
Size
676.9 KB
Dependencies
0 dependencies · 5 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

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

README

pi-subagents

This native Pi extension adds Codex-style multi-agent coordination tools to Pi extension/runtime 0.83.0: spawn, message, follow up, wait, interrupt, list, report, and lifecycle metrics for a tree of child agents, with budgets, compaction, roles, limits, and session persistence.

pi-subagents is standalone. It requires only Pi core 0.83.0 and no other extension. An optional Codex conversion package can add tools to child sessions when it is loaded — see docs/architecture.md for the compat contract; pi-subagents never depends on the conversion package or on the pi-chimera compat extension.

Install

Published users install with:

pi install npm:@juvio15/pi-subagents

Try the package without a permanent install:

pi -e npm:@juvio15/pi-subagents

Then run /subagents to open the agent list. Use shift+alt+left and shift+alt+right to move through agents.

Local development

  1. Run npm install.
  2. Run pi -e ./src/index.ts.

Tools

The extension registers these tools:

  • spawn_agent
  • send_message
  • followup_task
  • wait_agent
  • interrupt_agent
  • list_agents
  • list_models
  • get_agent_report
  • chimera_stats
  • schema_hypothesize
  • schema_verify
  • schema_commit
  • schema_abort

list_models returns the session /scoped-models set only. The spawn_agent model field description lists those scoped model ids. get_agent_report pages the persisted full report of a completed child. chimera_stats returns derived lifecycle metrics for the agent tree.

Schema enforcement (evidence-gated mutation)

The schema tools let a child agent commit declared file operations only when typed evidence passes:

  • schema_hypothesize stores an evidence set (file_sha256, file_contains, or trusted_command) as an active hypothesis.
  • schema_verify runs the evidence and fails closed: a hypothesis with missing or non-confirmed evidence never receives a certificate. Success issues a random token bound to a 30-second window.
  • schema_commit applies the declared write/edit/delete operations under the certificate. Before images of every declared path are captured first; a failed operation or postcondition restores them and reports rolled_back.
  • schema_abort closes a hypothesis and revokes its certificate.

Records persist in the mesh store under schema/hypothesis/<id>, schema/certificate/<hash>, and schema/workspace.

Talk-while-they-work (operator communication)

While the tree is active and the root is orchestrating, typed interactive input that does not start with / is delivered to the root's mailbox as a user envelope instead of steering the running turn — no interrupt, children keep running. The root's next wait_agent returns a distinct Mailbox message received result with a bounded preview (from, kind, text), and the full envelope is delivered at that wait boundary so the orchestrator can act on it in the same turn and keep waiting.

  • /subagents tell <target> <message> delivers a message to any agent (task name, path, or nickname) without interrupting anything; envelopes are authored as the root so child authority rules hold.
  • /subagents steer <text> is the deliberate escape hatch: it forces the host path (steer), so a pending wait_agent returns Wait interrupted by new input. and the text reaches the orchestrator as a steer message.
  • Typing !<text> also forces the host path (the ! prefix is stripped).
  • /-prefixed input is never routed — unknown commands must reach the host.
  • Routing is off when the tree is empty or the root is idle (input passes to the host normally). Set userInputRouting: "host" in the config to disable routing entirely.
  • User envelopes persist like every other mailbox item, so nothing is lost across turns or session restores.

Keyboard

While the tree is active, a lone Esc press (debounced 60 ms to ignore escape-sequence prefixes such as arrow keys) interrupts every running child agent. The observer does not consume the key, so Pi's native cancel-streaming still fires. Disable the behavior with haltOnEscape: false in the config.

Check the package

Run npm run check.

Run npm run smoke for the installed Pi load check.

Run npm run smoke:cross-provider for the DeepSeek parent-and-child tool projection check. This command requires configured DeepSeek authentication.

The smoke check uses RPC, print, and JSON modes. The TUI commands use the same extension entry point.

Configuration

Create .pi/subagents.json in a trusted project when you must change a limit or define a role. All fields are optional.

{
  "maxResidentThreads": 4,
  "maxActiveChildTurns": 3,
  "waitAgentEnabled": true,
  "userInputRouting": "mailbox",
  "defaultChildInstructions": "Follow the project child-agent policy.",
  "defaultChildModel": "deepseek/deepseek-v4-flash",
  "defaultChildReasoning": "max",
  "autoCompactHistory": true,
  "maxChildTurnRetries": 2,
  "budgetLimit": 0,
  "haltOnEscape": true,
  "timeReminderIntervalSeconds": 0,
  "roles": {
    "reviewer": {
      "description": "Review code and report defects.",
      "model": "deepseek/deepseek-v4-flash",
      "reasoning": "max",
      "instructions": "Review the change. Do not edit files."
    }
  }
}
Setting Default Description
maxResidentThreads 4 Resident child sessions (includes the root).
maxActiveChildTurns 3 Concurrent child turns.
waitAgentEnabled true Register wait_agent.
userInputRouting "mailbox" Route operator input to the root mailbox while orchestrating; "host" disables routing.
maxLogicalAgents 64 Logical agents in the tree.
mailboxCapacity 64 Messages per mailbox.
maxMessageChars 8000 One task message at most.
autoCompactHistory true Compact child histories when they exceed historyCapacity.
maxChildTurnRetries 2 Transient prompt-failure retries.
budgetLimit 0 Weighted-token budget; 0 = unlimited.
haltOnEscape true A lone Esc while children are running interrupts all active child agents (TUI only).
timeReminderIntervalSeconds 0 Queue a bounded time note into long-running turns.
roles built-ins Custom role definitions (scout/implementation/specialist are reserved).

waitAgentEnabled defaults to true. Set it to false only when the host cannot safely wait for child activity.

Project configuration loads only in a trusted Pi project. An untrusted project uses safe defaults and ignores .pi/subagents.json roles and limits. The legacy .pi/pi-chimera.json name is not read — see the split notes in docs/architecture.md.

Native orchestration

pi-subagents provides embedded runtime behavior instead of a packaged skill. The primary agent remains available while it integrates results.

Delegate only substantial, independent work. Give each child a narrow, non-overlapping scope.

Built-in roles request low reasoning for scout, medium for implementation, and high for specialist.

pi-subagents uses Pi model data to clamp these requests. Built-in roles have no parent history. All three roles are leaf roles.

Reasoning

Built-in roles request low, medium, or high reasoning. pi-subagents clamps role reasoning to the child model's supported levels. An explicit reasoning_effort the model does not support is a hard error. spawn_agent returns the effective model and reasoning, and completion notices print those values.

Scouts use read-only tools. Existing custom roles remain supported.

defaultChildInstructions applies to children without role instructions. Role instructions have higher priority.

When the root exposes the composite exec or exec_command tool, pi-subagents projects it to Pi built-in child tools. Scout restrictions still remove write and shell tools.

The root owns child input. Headless modes register the same tools, commands, and shortcuts.

UI handlers return without action when ctx.hasUI is false. The live picker shows child transcripts in a read-only view.

Handoff

/subagents handoff <provider/model> [task] arms a trajectory-preserving model handoff for the root session. The default mode is in-place: the first settled child result after arming switches the root model to the target with the pi extension API setModel and queues a hidden continuation so the root keeps working on the armed task under the new model.

The boundary fires once per arming. Errored or interrupted children never fire it. A failed switch (unavailable model, missing authentication, or a host without setModel) reports a clear error and the root stays idle.

Trajectory mode (a separate executor session forked from the root's branch) is out of scope for this port; the continuation prompt preserves the task trajectory across the in-place switch. The arm state is not durable: a session restart clears it. The command requires a provider/model target and accepts a bounded task (20 000 characters).

Limits

The defaults allow four resident threads. This count includes the root. Three child turns can run at the same time.

The extension keeps at most 64 logical agents. It keeps 64 messages in one mailbox and 64 messages in one child history. One task message has at most 8,000 characters.

Long child final answers are truncated with [truncated] at that 8,000-character bound. Thinking content is capped at 2,000 characters per part so the report text keeps the remaining budget (a typical final answer retains ~5,000+ characters of text). This is an intentional safety limit.

Completed child answers are also persisted out of band as full reports (up to 64 KiB per answer, newest 16 per agent). The completion notice carries a report object; when available is true, get_agent_report pages through the full text (offset = next_offset until next_offset is null). Check complete before claiming an answer is lossless: over-cap answers keep their stored prefix, report both lengths, and say complete: false. Reports survive child eviction and session restore. Legacy turns with no persisted report return available: false and must be rerun.

Task names have at most 64 characters. A path has at most eight child segments. Project configuration can lower a limit. It cannot raise a hard cap.

Budgets and retries

The tree has one shared spend budget. budgetLimit sets the limit in weighted tokens. The default 0 means unlimited. Set budgetSamplingWeight and budgetPrefillWeight to weight output and input tokens. budgetReminderAt sets the reminder threshold. The default is 10% of the limit. A crossed threshold delivers one bounded reminder to the child mailbox. Exhaustion rejects new child turns. Cumulative spend is persisted in the session log after every accounted turn and restored on reload when budgetLimit is unchanged; changing the limit starts a fresh accounting period.

A child turn retries transient prompt failures. maxChildTurnRetries defaults to 2. The delay before retry n is 250 ms * 2^(n-1). Progress shows "Reconnecting… n/max" during the backoff. Abort, interrupt, budget, and steering errors do not retry.

When a child history exceeds historyCapacity, the control plane compacts it. It keeps the original task envelope, adds one bounded rollup line for the dropped region, and keeps the newest messages. autoCompactHistory defaults to true. Set it to false for the old tail-only trim.

timeReminderIntervalSeconds queues a bounded "Current time" message into a long-running child turn. The default 0 disables it. The message waits for the next message boundary. It never interrupts a streaming turn.

Child instructions include a bounded agent inventory. The inventory lists the parent and live siblings as - <path>: <nickname> lines. It is bounded to 32 lines and 2,000 characters.

Pi steering compatibility

Direct RPC steer does not abort the active Pi tool signal. The extension uses ctx.hasPendingMessages() to detect it.

A running wait_agent checks this query every 25 milliseconds. Run npm run test:gap to verify this behavior.

Documentation