@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.4.0
Published
Oct 3, 2026
Downloads
564/mo · 238/wk
Author
qcts33
License
MIT
Types
extension
Size
379 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, with optional Herdr-managed Git worktrees for isolated general-purpose attempts. 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", agent: "general-purpose", task: "Make the change", run_in_background: false });

// Isolated ticket implementation in a Herdr-managed Git worktree
subagent({ name: "Ticket #42", agent: "general-purpose", task: "Implement ticket #42", worktree: true });

Development

Run unit tests, lint, and typecheck locally:

npm test
npm run lint
npm run typecheck

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

PI_TEST_MODEL="deepseek/deepseek-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-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. The lifecycle suite resolves PI_TEST_MODEL against the sandboxed agent directory it launches Pi with before any test runs, and aborts with that resolution error when the sandbox cannot run the model - even when your own Pi session can.

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
subagent_inventory Report running sub-agents and the pi-subagent worktrees on disk, including retained and unclaimed ones (read-only)

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:

  • active — active, 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)
worktree boolean false Create a Herdr-managed Git worktree for this general-purpose attempt; requires a clean source repository

Isolated worktrees

worktree: true is opt-in and available only to the general-purpose type; it requires Herdr's worktree create and worktree remove commands. Herdr creates one linked worktree and branch from the source repository's current HEAD; the subagent starts at that checkout's root. The source repository must have no staged, unstaged, or untracked changes. If validation or Herdr creation fails, startup fails rather than using the shared checkout; the extension does not force creation or grant Herdr repository trust automatically. Ignored files are not copied, and UNC source paths are unsupported.

After the subagent and its descendants drain, the extension removes the worktree through Herdr only when the checkout is clean and its branch remains at the starting commit. A worktree with uncommitted changes or new commits is retained. The completion result reports its path, branch, workspace ID, and cleanup status. That workspace ID only identifies the workspace while the subagent runs: retaining a worktree closes its pane, which destroys the workspace, so removal reopens the checkout first and uses the workspace Herdr then issues:

herdr worktree open --cwd <source repository> --path <checkout>
herdr worktree remove --workspace <workspace ID printed by the first command>

An unexpected parent-process exit may leave a worktree behind because Fresh attempts are not recovered after restart. The extension does not commit, merge, force removal, or delete branches; subagent_inventory reports what is still on disk.

Subagent inventory

subagent_inventory answers "what do I have running, and what do my subagents leave behind" without touching the working tree. It is read-only and derived at call time from the running registry, Herdr's worktree state, and Git; nothing is recorded for attempts that already ended, so the report covers worktrees left by earlier sessions, by another pi session in the same repository, and by a parent process that died before reporting.

Each pi-subagent worktree is classified by who holds it:

  • held — a running subagent of this session owns it;
  • retained — no Herdr workspace hosts it, so its attempt is over and the checkout is still on disk;
  • unclaimed — a Herdr workspace hosts it, but no running subagent of this session does. It may belong to another pi session or to a subagent whose parent died, so the report names the pane and whether an agent runs there, and you should inspect that pane before acting.

For worktrees no running subagent holds, the tool runs two Git probes (working-tree changes, commits on top of the source branch) and prints the commands that remove the checkout. Anything it cannot probe is reported as unknown with the reason instead of being omitted. See ADR-0015.

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 safely reclaims the child's Herdr surface and worktree, releases the registry entry, and delivers a cancelled steer to the parent conversation — the cancelled attempt never produces a result. A worktree with changes is retained rather than discarded. Cancelling a subagent that is already finalizing (or already cancelled) is a no-op with an explanatory message.

Because cleanup follows the child's acknowledgement, the cancel acknowledgement names a worktree's path and branch along with the rule that decides its fate, and the terminal cancellation steer reports whether Herdr removed the checkout or the attempt kept it. A worktree behind a derived cascade cancellation or a parent shutdown is reclaimed without a report; call subagent_inventory to see what is still on disk. Raw herdr worktree list works too, but it must be given the repository (herdr worktree list --cwd <source repository>) because Herdr's default scope is its focused workspace, not your shell's directory.

A cancelled child's surface is reclaimed only after its acknowledgement so its descendant subtree can drain first; an unchanged worktree is removed through Herdr, while a changed worktree is retained. The interrupted widget label marks the row during that drain window; the row disappears once the surface 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

Each type uses Pi's normal configured tool selection, with role-specific limits:

Type Tools Spawning
general-purpose Pi's configured tools, including extensions and custom tools Allowed — may spawn further subagents
explore Pi's configured tools, except edit and write Denied — no subagent / subagent_cancel

explore uses Pi's normal tool selection with --exclude-tools edit,write instead of a fixed --tools allowlist. This preserves the user's configured extensions and custom tools while removing Pi's dedicated file-editing tools. This is not a security sandbox: bash and custom tools may still have side effects, so the role instructions prohibit using them to modify state.

Direct self-spawn

A Subagent cannot launch a direct Child runtime using its own type (general-purpose → general-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] — configured tools available  (Ctrl+J)     ← collapsed
[explore] — available and denied tools  (Ctrl+J)     ← expanded
  read, bash, web_search, my_custom_tool, ...
  denied: edit, write, 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