@itc-steve/pi-herdr
Pi extension for Herdr-visible subagent herds - default-local workers, optional ranked-frontier think, markdown handoff.
Package details
Install @itc-steve/pi-herdr from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@itc-steve/pi-herdr- Package
@itc-steve/pi-herdr- Version
1.5.1- Published
- Sep 18, 2026
- Downloads
- 938/mo · 191/wk
- Author
- itc-steve
- License
- MIT
- Types
- extension, skill
- Size
- 254.9 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-herdr
Pi extension for Herdr-visible subagent herds — default-local workers, optional ranked-frontier think, markdown handoff.
Design intent
| Role | Who |
|---|---|
| Orchestration | Frontier model (parent session) |
| Implement / slice | Local seat — private, free, clean per-job context, cap = maxModelConcurrent |
| Isolated review / plan / VERIFY | role=think — one job per catalog model; two tasks → one each; next think rotates to the other |
| User view / permissions | Herdr panes (watch, focus, accept) |
Requires running inside Herdr (HERDR_ENV=1). Outside Herdr, herd still loads for config/status, but spawn/boot and the herdr tool are inactive.
Install
pi install npm:@itc-steve/pi-herdr
From a local checkout:
pi install /path/to/pi-herdr
Then /reload. Use /herd help for slash usage.
Do not install other herdr packages at the same time (e.g. @ogulcancelik/pi-herdr, pi-custom-herdr). This package vendors the herdr tool and refuses to load if a known competitor appears in Pi settings.
Requirements
- Node.js ≥ 22.19
- Pi coding agent
- Herdr (terminal workspace / agent multiplexer)
- Models listed in config must already be available to Pi (local vLLM/SGLang, Grok CLI, etc.)
Config
Copy herd.json.example to ~/.pi/agent/herd.json (created automatically on first models, run, or journal if missing):
{
"sessionDir": "~/.pi/agent/herd",
"maxModelConcurrent": 2,
"local": {
"enabled": true,
"model": "vllm/Qwen/Qwen3.6-27B-FP8",
"thinking": "medium",
"preflight": true
},
"do": [
{ "model": "grok-cli/grok-build", "thinking": "medium" }
],
"think": [
{ "model": "grok-cli/grok-4.6", "thinking": "high" },
{ "model": "openai-codex/gpt-5.6-sol", "thinking": "high" }
],
"private": { "enabled": false },
"defaults": {
"isolation": "none",
"timeoutMs": 600000,
"waitForReply": false,
"requireOutput": true,
"resultDelivery": "pointer",
"triggerTurnOnResult": true
}
}
think[] order is the rank. Put the best frontier first.
do[] is optional: extra non-local models a bare role=do may rotate onto when local seats are full. The local model stays the do head; each do[] model caps at one in-flight job (same as think — it shares the per-model cap, so a think job and a do job on the same model never overlap).
Old easy / medium / hard catalogs still parse: remotes fold into think (hard, then medium, then easy). local.maxStreams, preferOn, and whenFull are ignored. Cap is always maxModelConcurrent.
Field reference
| Field | Meaning |
|---|---|
sessionDir |
Run root (runs/, journals, session JSONL) |
maxModelConcurrent |
Cap on local seats and on in-flight jobs per exact provider/model |
local |
Default implementer: model id, thinking, optional preflight |
do |
Optional extra non-local do models. Bare role=do always takes a local seat while one is free; only when local seats are full does it pick a free do[] model (no think-style round-robin past a free local seat) |
think |
Ordered frontier catalog for role=think |
private.enabled |
Opt-in cloud redaction + private=true spawn (default false) |
defaults.isolation |
ignored; writers share the tree (owns= is the isolation) |
defaults.requireOutput |
Async spawn must declare output= |
defaults.resultDelivery |
pointer (default: path only) or full (paste reply) |
defaults.triggerTurnOnResult |
One parent turn when the last in-flight job finishes (default true) |
Jobs resolved to the local model are tagged local: true so parent, kick text, and herd models all see the seat.
How it works
Default-local + optional think
| Spawn | Route |
|---|---|
no role, or role=do |
Local. With do[]: local seats full → rotate onto a free do[] model (1 at a time). All full → queue a local seat. Never overflow onto think. |
role=think (aliases: review, plan, architect, verify) |
One in-flight per think model. First → think[0]. Second task or second opinion → the other. Both busy → queue think[0] |
model= |
That model (local id still takes a local seat) |
Never dump a whole project on one spawn. Decompose. Parent stays on a frontier model for orchestration; workers get a fresh session and a narrow kick.
One-release shim: difficulty=easy\|medium → do, difficulty=hard → think.
Local seat
- Default spawn claims a local seat (cap =
maxModelConcurrent). - With
do[]: extra do jobs rotate onto a freedo[]model once local seats are full. Withoutdo[]: extra do jobs wait for a free seat. Never overflow onto think. - Each job gets a fresh
sessions/<job>.jsonl. - Kick banner: You are a LOCAL worker. One slice…
Private mode (opt-in)
Default off. Set "private": { "enabled": true } in herd.json.
When on, a cloud parent (active model ≠ local.model) sees [PRIVATE:<category>] instead of detected secrets in tool output, shell output, provider requests (including compaction), and herd-result text. The configured local model is trusted and is not redacted. Detection is regex/field-name based (vendored from @spences10/pi-redact, MIT) — not a guarantee; novel formats, assembled values, and secrets inside images can miss.
No auto-spawn. If the current task needs a withheld value, the cloud agent calls:
herd spawn private=true task="…one secret-dependent step…" output=private-check.md
That spawn requires private.enabled and local.enabled=true, claims a normal local seat (FIFO queue if local seats are busy — never overflow to cloud), and rejects role=think / non-local model=. Private workers cannot spawn nested herd jobs. A [PRIVATE:] marker in a non-private spawn is rejected while private mode is enabled — retry with private=true.
The private worker reruns the operation locally. It must not plan the project, inspect unrelated files, spawn agents, or return secret values. Replies/errors back to a cloud parent are redacted again before delivery (and before the 4000-char paste cap). Journal never stores the reply.
Do not use alternate retrieval tools to recover a marker. Do not ask the worker to print the value.
local.model is a trust assertion: if that id is actually a remote endpoint, private mode will treat it as local and skip redaction.
Handoff runs
~/.pi/agent/herd/runs/<date>_<slug>/
instruction.md context.md plan.md progress.md
meta.json journal.jsonl
sessions/<job>.jsonl
jobs/…
<your output=.md files>
Shared context is markdown only — panes do not chat to each other. Each spawn opens a background tab in the parent's current workspace. Job tabs stay open after success so you can watch or intervene in Herdr.
Write lanes
Multi-writer fan-out requires disjoint owns= (and optional forbid=). Put Parallel lanes in plan.md first; the tool rejects overlapping owners.
Completion (pointer batching)
Async jobs are monitored in the background.
- Mid-wave: footer only (
herd: N mon +local M) — no parent turn. - When the last in-flight job finishes: one batched
herd-resultwith short pointers (output=path), not full reply pastes. - Parent should read the artifact if it needs content — do not reassess the whole task from a pointer.
- Use
herd wait/herd collectfor a sync barrier. SetresultDelivery=fullonly if you need reply bodies in-session.
Tools
| Tool | Role |
|---|---|
herd |
Assign / abort / steer / status local-first subagents |
herdr |
View and control Herdr terminals (workspaces, tabs, panes, worktrees) |
Rule: assign work with herd. Use herdr to view/focus/read — never herdr run into a herd job pane to assign work.
herd actions
| Action | Purpose |
|---|---|
models |
Show local seats + ranked think catalog |
status |
Active monitors / local seats |
run |
create / list / use / show handoff folders |
spawn |
Boot a pane, submit task (async needs output=; role=think optional; private=true for secret-dependent local work) |
steer / abort |
Nudge or stop a job |
wait / collect |
Block until idle / harvest reply |
close / reset |
Close panes / clear monitors |
journal |
Soft resume log for the active run |
Quick flow
herd run create name=demo goal="Summarize this repo"
herd spawn task="Fill context.md from the repo" output=context.md
herd spawn task="Implement src/client.ts" output=progress-core.md owns=src/client.ts
herd spawn role=think task="Review progress-*.md; note gaps" output=progress-review.md
herd spawn private=true task="One secret-dependent check" output=private-check.md
Exact model override:
herd spawn model=grok-cli/grok-4.6 task="…" output=notes.md
Slash
/herd help
/herd models
/herd status
/herd run create name=<slug> goal="…"
/herd spawn task="…" output=file.md
/herd spawn role=think task="…" output=review.md
/herd spawn private=true task="…" output=private-check.md
herdr (view / control)
Registered only when HERDR_ENV and HERDR_PANE_ID are set (Herdr-managed pane). Actions include workspace/tab/pane lifecycle, read / watch / wait_agent, run / send / stop, worktrees, and notifications. Prefer friendly aliases or ids from herdr list — never invent pane ids.
Skills
Package skills (herd, herdr) teach the launcher default-local vs think and the herd vs herdr split. They install with the package via the Pi skills manifest.
Footer status
While monitors or local streams are active: herd: N mon +local M. Hidden when idle.
Development
npm test
Tests use Node’s built-in runner with --experimental-strip-types (Node ≥ 22.19). Run npm run typecheck for strict TypeScript checks.
Changelog
See CHANGELOG.md.
License
MIT. See LICENSE.