@qcts33/pi-herdr-subagents

Subagents for pi running in herdr

Packages

Package details

extension

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

$ pi install npm:@qcts33/pi-herdr-subagents
Package
@qcts33/pi-herdr-subagents
Version
0.2.0
Published
Aug 30, 2026
Downloads
423/mo · 423/wk
Author
qcts33
License
MIT
Types
extension
Size
326.2 KB
Dependencies
0 dependencies · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./pi-extension/subagents/index.ts"
  ]
}

Security note

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

README

@qcts33/pi-herdr-subagents

Subagents for pi running exclusively in herdr. Spawn, orchestrate, and manage ephemeral child runtimes in dedicated herdr tabs or panes. They run in the background by default, with optional foreground execution when the parent needs the result before continuing.

How It Works

Call subagent() to run a child in its own terminal pane. It returns immediately by default; set run_in_background: false to wait for the child and receive its result directly. A live widget above the input shows all tracked agents with their projected state — for example starting, active, waiting, interrupted, stalled, running, or finalizing. The header summarizes active (processing) vs open (not processing). When every tracked subagent is open, the border switches to amber. Background results are steered back into the main session as async notifications.

╭─ Subagents ──────────────────── 1 active · 1 open ─╮
│ 00:23  Explore: Auth (explore)     active · bash 7m │
│ 00:45  Explore: DB (explore)            waiting 2m │
╰────────────────────────────────────────────────────╯

For parallel execution, just call subagent multiple times — they all run concurrently:

subagent({ name: "Explore: Auth", agent: "explore", task: "Analyze auth module" });
subagent({ name: "Explore: DB", agent: "explore", task: "Map database schema" });
// Both return immediately, results steer back independently

// Foreground call: wait for the result before continuing
subagent({ name: "Implementer", task: "Make the change", run_in_background: false });

Development

Run unit tests and lint locally:

npm test
npm run lint

Run the real end-to-end suite from inside herdr with an explicit test model and thinking level:

PI_TEST_MODEL="deepseek/deepseek-v4-flash" PI_TEST_THINKING=high PI_TEST_TIMEOUT=180000 npm run test:integration

The full suite launches real Pi sessions and can take several minutes. PI_TEST_TIMEOUT is the per-test timeout in milliseconds; use at least 180000 for the lifecycle suite. The integration harness deliberately uses temporary workspace paths containing spaces; on native Windows it drives the parent through a PowerShell script and Node-based fixtures so the same tests work from PowerShell or cmd.exe panes. Set PI_TEST_WINDOWS_CWD to an existing drive-letter or UNC directory to enable the optional native Windows cwd test.

PI_TEST_MODEL and PI_TEST_THINKING select the runtime for both the parent Pi sessions and their subagents (children inherit the parent runtime by default). Defaults are deepseek/deepseek-v4-flash and high. PI_TEST_THINKING accepts off, minimal, low, medium, high, xhigh, or max. Use an exact registry model ID for PI_TEST_MODEL; do not append a thinking-level suffix such as :low.

Install

Install the package from npm:

pi install npm:@qcts33/pi-herdr-subagents

This project does not install or load HazAT/pi-interactive-subagents automatically.

Releases are published manually from a clean main branch; for authentication, versioning, and troubleshooting see RELEASING.md.

Start herdr, then run pi inside it:

herdr
pi

herdr is the only supported terminal environment. The extension requires HERDR_ENV=1, the herdr CLI, and Herdr's native agent API (agent start, agent prompt, agent get, and agent send-keys). Pi is the only supported child agent kind; there is no pane-command fallback.

Native agent startup waits for Pi readiness before delivering the task. If startup is slow, set PI_SUBAGENT_AGENT_START_TIMEOUT_MS (default 30000, maximum 300000). Every Fresh Ephemeral Child runtime uses Pi's --no-session mode and explicitly loads the completion extension, which records Completion evidence and acknowledges cancellation after descendant drain; Pi's configured Child extensions remain enabled alongside it. Completion, activity, cancellation, and launch-prompt data live only in short-lived operation artifacts under the OS temporary directory.

Subagent tabs and panes are created without stealing keyboard focus. Native agent operations target child panes by explicit ID, so focus and command delivery are independent. Every child uses the same automatic Completion lifecycle; this is independent of terminal focus.

What's Included

Extensions

Subagents — 2 main-session tools, plus 1 subagent-only tool:

Tool Description
subagent Spawn a sub-agent; background by default or foreground with run_in_background: false
subagent_cancel Cancel a running sub-agent: terminate, reclaim its herdr pane, and report the cancellation

Built-in Subagent Types

Exactly two built-in subagent types exist; they are fixed in the extension and cannot be extended or overridden:

Type Default runtime Role
explore Parent Read-only codebase search and analysis — maps files, patterns, conventions
general-purpose Parent Complex multi-step tasks requiring exploration and action — writes code, runs tests

Both types inherit the parent model and thinking level by default. The orchestrating agent can override either field for a specific task using an exact authenticated model ID and a supported Pi thinking level. Prefer changing thinking before changing models.


Subagent Execution Modes

1. Agent calls subagent()          → returns immediately ("started")
2. Sub-agent runs in herdr pane    → widget shows live status
3. User keeps chatting             → main session fully interactive
4. Sub-agent finishes              → result steered back as a normal completion/failure
5. Main agent processes result     → continues with new context

For a foreground call (run_in_background: false), the parent tool call stays pending while the child runs:

1. Agent calls subagent()          → call remains pending
2. Sub-agent runs in herdr pane    → widget shows live status
3. Sub-agent finishes              → result returns from the tool call
4. Main agent processes result     → continues with the child summary

Multiple background subagents run concurrently — each steers its result back independently as it finishes. The live widget above the input tracks every agent still in flight:

╭─ Subagents ──────────────────── 1 active · 2 open ─╮
│ 01:23  Explore: Auth (explore)       active · write 7m │
│ 00:45  General (general-purpose)           stalled 4m │
│ 00:12  Explore: DB (explore)               starting…   │
╰─────────────────────────────────────────────────────────╯

Completion messages render with a colored background and are expandable with Ctrl+O to show the full summary. Completed rows are removed from the widget as soon as their result is delivered or suppressed.

In-progress status updates

The widget projects each sub-agent from a process + turn lifecycle:

  • Herdr agent inspection is the coarse authority for whether the child process is present and whether Herdr reports it as idle, working, blocked, or done.
  • Child activity snapshots enrich the label with Pi-only detail (tool name, streaming, etc.) when available.
  • Child Pi runtimes do not create a transcript. The Completion sidecar carries the final summary and runtime details; operation artifacts are not Pi sessions.

Projected labels include:

  • starting — launched; pane/activity confirmation is still settling
  • active — processing work (agent turn, provider request, streaming, or tool execution)
  • blocked — Herdr reports the child as blocked
  • waiting — turn finished; the process is intentionally open for more input or another stage
  • interrupted — the current turn was cancelled (Escape / a running subagent_cancel); the row is not treated as active processing while the cancellation drain finishes
  • stalled — pane inspection is unhealthy long enough that the parent can no longer trust the run
  • running — fallback while the native Pi process is present but detailed turn state is not yet known
  • finalizing — completion was observed and delivery is in progress; the process elapsed timer freezes here
  • draining — a launch reservation or result handoff still owns the parent before its provisional settled boundary is visible
  • self-settled · draining — this parent loop has ended provisionally while owned descendants, launch reservations, or result handoffs remain
  • cancelling · draining — cancellation won the lifecycle cutover and descendant termination is still being supervised

The widget header counts active vs open:

  • activeactive, starting, running, or blocked
  • open — everything else still tracked (waiting, interrupted, stalled, finalizing, …)

When activeCount === 0 (every tracked row is open), the border uses an amber accent. Process elapsed time (MM:SS on the left) freezes when the process reaches finalizing/completed/failed. Cancellation does not freeze that process clock; the interrupted state shows its own duration on the right while the pane remains open during the drain.

A fixed internal watchdog marks a run as stalled when pane inspection fails or the pane disappears without a completion sidecar; valid long-running active or waiting states do not become stalled just because time passes. When a run enters stalled or recovers from it, the parent agent receives a steer message so it can react. All other status transitions stay in the widget only.

By default, subagents publish Completion evidence at Pi's agent_settled boundary. The parent closes the child surface, delivers the result, and receives stalled/recovered notifications when supervision needs attention. Every new attempt is Fresh and independent. A Completion destination is invoked at most once per operation; if the parent rejects the handoff, the extension reports a parent-side delivery failure and does not retry it. A failed Fresh attempt is terminal; if the work is still needed, start a new Fresh subagent call and provide the necessary context in its task. /reload preserves the parent-local registry, nested ownership, pending handoffs, and drain state, while a new parent process does not recover old outcomes. Existing operation records, claims, lineage metadata, and stale Completion artifacts are ignored rather than migrated.

Configuration

Status display is enabled by default. To disable it, create config.json in the extension directory:

{
  "status": {
    "enabled": false
  }
}

Subagent model and thinking selection is done per call: omit both to inherit the parent runtime, or pass an exact authenticated provider/model-id and a supported Pi thinking level on the tool call.

config.json is gitignored so local overrides don't get committed.


Spawning Subagents

// Named agent with defaults from agent definition or config.json
subagent({ name: "Explore", agent: "explore", task: "Analyze the codebase..." });

// Read-only investigation with the built-in explore type
subagent({ name: "Researcher", agent: "explore", task: "Investigate the API rate limits" });

// Custom working directory
subagent({ name: "Designer", agent: "general-purpose", cwd: "agents/game-designer", task: "..." });

Parameters

Parameter Type Default Description
name string required Display name (shown in widget and pane title)
task string required Task prompt for the sub-agent
run_in_background boolean true* false waits for and returns the result; true returns immediately and steers the result. Omit to preserve background compatibility.
agent string Built-in subagent type: 'explore' or 'general-purpose'
model string parent Exact authenticated provider/model-id; omit to inherit the parent
thinking string parent level Pi thinking level (off through max); omit to inherit the parent
systemPrompt string Append to system prompt
skills string Comma-separated skill names
tools string Comma-separated tool names
cwd string Working directory for the sub-agent (see Role Folders)

Cancelling a running subagent

Use subagent_cancel to terminate a running Pi-backed subagent:

subagent_cancel({ id: "abcd1234" });
// or
subagent_cancel({ name: "Scout" });

This is a full cancellation, not a turn-level interrupt. The extension writes a durable cancellation request, sends Escape to the child pane, and the child drains its own active descendants before publishing its cancellation acknowledgement. Once the acknowledgement is observed, the watch arc closes the child's herdr pane, releases the registry entry, and delivers a cancelled steer to the parent conversation — the cancelled attempt never produces a result. Cancelling a subagent that is already finalizing (or already cancelled) is a no-op with an explanatory message.

A cancelled child's surface is reclaimed only after its acknowledgement so its descendant subtree can drain first. The interrupted widget label marks the row during that drain window; the row disappears once the pane is reclaimed.

Note: Only Pi-backed subagents are supported. Agent definitions with a legacy cli setting are no longer supported.


Ephemeral Child runtimes

Every subagent starts as a Fresh Ephemeral Child runtime with Pi session persistence disabled (--no-session). There is no context-inheritance mode: the child receives only its standalone task prompt and explicit role instructions; required context belongs in the task. Completion evidence and the structured result are exchanged through short-lived operation artifacts, not a Pi transcript.

Completion lifecycle

By default, a child requests Completion evidence at Pi's agent_settled boundary. For a child that owns Nested Subagents, this is only the Self-settled boundary: final evidence waits until direct descendants and pending Descendant result handoffs drain. Intermediate agent_end events do not complete the operation, provider retries are allowed, and an aborted final run publishes no automatic evidence.

Tool Access Control

The two built-in types have fixed tool access:

Type Tools Spawning
general-purpose Full toolset Allowed — may spawn further subagents
explore read, bash, web_search, web_fetch Denied — no subagent / subagent_cancel

Direct self-spawn

A Subagent cannot launch a direct Child runtime using its own type (general-purposegeneral-purpose is rejected). The rejected tool call tells it to choose the other built-in type instead; if none fits, it must complete the task directly.


Role Folders

The cwd parameter lets sub-agents start in a specific directory with its own configuration:

project/
├── agents/
│   ├── game-designer/
│   │   └── CLAUDE.md          ← "You are a game designer..."
│   ├── sre/
│   │   ├── CLAUDE.md          ← "You are an SRE specialist..."
│   │   └── .pi/skills/        ← SRE-specific skills
│   └── narrative/
│       └── CLAUDE.md          ← "You are a narrative designer..."
subagent({ name: "Game Designer", cwd: "agents/game-designer", task: "Design the combat system" });
subagent({ name: "SRE", cwd: "agents/sre", task: "Review deployment pipeline" });

Tools Widget

Every sub-agent runtime displays a compact tools widget showing available and denied tools. Toggle with Ctrl+J:

[explore] — 10 tools · 2 denied  (Ctrl+J)           ← collapsed
[explore] — 10 available  (Ctrl+J to collapse)       ← expanded
  read, bash, web_search, web_fetch, ...
  denied: subagent, subagent_cancel, ...

Requirements

  • pi — the coding agent
  • herdr — the required terminal workspace
herdr
pi

Other multiplexers and terminal backends are not supported.


Acknowledgements

The sub-agent status supervision and run cancellation features were inspired by RepoPrompt's sub-agent snapshot polling and run cancellation features.


License

MIT