@maxiaochao/pi-toolkit 0.11.6 → 0.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,363 +1,94 @@
1
- # @maxiaochao/pi-herdr-subagents
1
+ # Herdr Subagents
2
2
 
3
- A Pi extension that runs focused foreground or background work as normal interactive Pi Agents in visible Herdr panes. Full answers and sessions stay in project-local records while the parent receives only concise summaries and document paths.
3
+ Run focused Pi subagents in visible, focusable Herdr panes and collect exactly one terminal result per task.
4
4
 
5
- ![A parent Pi conversation showing the live Herdr Subagents Fleet footer](docs/screenshot.png)
5
+ The implementation is intentionally small: one task state machine, one derived run state, and one bounded worker pool. Herdr provides visible panes and navigation; the package only dispatches serial or read-only parallel work, waits for a report, and records the result. It does not add a second workflow engine around Pi.
6
6
 
7
- ## Demo
7
+ ![Herdr Subagents running as visible panes](docs/screenshot.png)
8
8
 
9
- The recording shows a real Pi conversation invoking a three-task read-only batch, the active Fleet footer in the parent, Herdr opening one interactive Agent pane per task, and the compact results returning to the parent.
9
+ ## Demo
10
10
 
11
11
  https://github.com/user-attachments/assets/8ddf3ae6-a11a-492b-9cae-d9496ba65b67
12
12
 
13
- The video starts a three-agent batch, focuses each live child pane in turn, captures the parent Fleet footer with yellow running indicators and true usage, then returns to the parent summary. Completed Fleet rows use green indicators. The reproducible source recording is retained as [`docs/demo.webm`](docs/demo.webm).
14
-
15
- ## Requirements and install
16
-
17
- Run Pi inside Herdr, then install the package:
18
-
19
- ```bash
20
- pi install npm:@maxiaochao/pi-herdr-subagents
21
- ```
22
-
23
- Update an existing standalone installation with:
24
-
25
- ```bash
26
- pi update npm:@maxiaochao/pi-herdr-subagents
27
- ```
28
-
29
- The full `@maxiaochao/pi-toolkit` package also bundles this extension. Install the full toolkit or this standalone child package, not both.
30
-
31
- Inside a Herdr workspace, the package registers `herdr_subagents`, `herdr_subagents_control`, and `/herdr-subagents`. Outside Herdr (or without a workspace ID), the extension registers none of them, so these capabilities are absent from the model context. Start Pi inside Herdr or reload the extension there to enable them.
32
-
33
- The design replaces hidden print-mode subprocesses with ordinary interactive Pi Agents managed through Herdr, so delegated work stays visible, inspectable, resumable, and durably recorded.
34
-
35
- See [ARCHITECTURE.md](ARCHITECTURE.md) for the current execution architecture, the proposed decision-pause state machine, and configuration-normalization boundaries.
36
-
37
- ## Usage
38
-
39
- Discover the effective personas first:
40
-
41
- ```text
42
- herdr_subagents_control({ action: "list" })
43
- ```
44
-
45
- Then run one self-contained task:
46
-
47
- ```text
48
- herdr_subagents({
49
- label: "auth-review",
50
- tasks: [
51
- { name: "auth", agent: "worker", prompt: "Inspect the authentication flow and summarize the risks" }
52
- ]
53
- })
54
- ```
55
-
56
- Or submit an ordered foreground batch:
57
-
58
- ```text
59
- herdr_subagents({
60
- label: "security-review",
61
- concurrency: 3,
62
- tasks: [
63
- { name: "auth", agent: "reviewer", prompt: "Review authentication" },
64
- { name: "sessions", agent: "explorer", prompt: "Trace session storage" }
65
- ]
66
- })
67
- ```
68
-
69
- `tasks` is always a non-empty array: one item is a single task and multiple items form a batch. Set `background: true` for either form to return a stable run ID immediately while the queue continues. The dispatcher starts batches FIFO, with one batch holding its active slot at a time; a failed foreground handoff releases that slot as described below. A blocked run retains the slot so its queued siblings can continue after an answer. While that slot is blocked, a new foreground submission fails immediately with the blocking run ID instead of waiting indefinitely; a background submission remains queued. Cancelling a foreground tool call while it is queued removes it from the queue immediately. Each background run sends one compact parent notification only when the whole run completes, partially fails, fails, or blocks. Individual successful tasks do not wake the parent model.
70
-
71
- A foreground concurrent batch normally waits for the whole batch. If any task fails while siblings are still running, the call immediately returns the stable `runId` and that failure's details, then durably continues the same batch in the background without cancelling its siblings. The handed-off batch releases its dispatcher slot immediately, so a later submission can start while its surviving tasks are still running. The detached batch remains cancellable and is still joined during session shutdown. Its final aggregate is delivered exactly once through the same follow-up notification used by an explicit background run.
72
-
73
- Background work normally relies on that terminal notification. When the user asks for progress or the parent must confirm a review gate before continuing, query only that run's compact snapshot:
74
-
75
- ```text
76
- herdr_subagents_control({ action: "status", runId: "<run-id>" })
77
- ```
78
-
79
- The snapshot reports the aggregate run state and every task's state, including a blocked question when present. It checks durable run state before queued state, never returns unrelated runs, and does not wait or trigger a parent turn. Do not poll full `history` for progress.
80
-
81
- Run controls accept the complete run UUID, the saved run-directory name, or any unique UUID prefix. Results always return the canonical complete UUID.
82
-
83
- Every child reports exactly one of three outcomes. `completed` and `failed` are terminal; `blocked` means the child is still alive in its pane but needs the parent agent to answer or decide something before it can go on. A blocked task keeps its Herdr tab, pane, partial report, and Pi session, and returns control to the parent with an exact question. Answer through the control tool to continue the original session:
84
-
85
- ```text
86
- herdr_subagents_control({ action: "respond", runId: "<run-id>", task: 1, answer: "Use the main branch." })
87
- ```
88
-
89
- Blocked is a normal control state, not a failed tool call. Only terminal failure, cancellation, or a partial aggregate is returned as a tool error.
90
-
91
- Blocking works the same way at any concurrency: a blocked task never holds up its independent siblings, which keep running and settling normally. A blocked run does keep the dispatcher slot until you respond or cancel it, so another foreground run started meanwhile is refused with the blocking run ID and a background run stays queued. That is what keeps a second write-capable run from editing the repository behind an unanswered question.
92
-
93
- A failed task is not a dead end. Name it explicitly to continue its original Pi session with another message, even while the rest of its batch is still running:
94
-
95
- ```text
96
- herdr_subagents_control({ action: "respond", runId: "<run-id>", task: 2, answer: "Use the staging credentials and retry." })
97
- ```
98
-
99
- The task resumes in its own live pane when that pane still hosts its child, and otherwise restarts from the saved session in a new pane. `task` is always required so an answer never picks a task implicitly. A task whose child never produced a saved Pi session cannot be continued and says so; submit a new run instead. Continuing a failed task never starts its batch's queued siblings, which the running batch still owns. While a continued task is working, its run stays `running` and its Herdr tab stays open even if the batch that created it has already settled.
100
-
101
- Blocked work always belongs to a live parent session. A historical follow-up may block too, keeping its shared resume tab until you answer it. Startup reconciliation cancels blocked work whose parent session disappeared, so no blocked run is ever left without an owner.
102
-
103
- Cancel one active or queued run with `herdr_subagents_control({ action: "cancel", runId: "<run-id>" })`. Cancellation aborts its live child prompts, archives active or queued tasks as cancelled, closes its run tab, and leaves unrelated runs alone.
104
-
105
- While a batch is active, a compact Fleet monitor stays below the editor. Its divider/title row summarizes the run, and each task row shows a hollow or filled status circle, the real persona and task name, business status or current runtime telemetry, live active duration, and cumulative child Pi token usage in one compact shared metrics column after the longest row rather than at the terminal edge. Herdr `blocked` is shown neutrally as `waiting_interaction` while the task remains `running`; only a child `subagent_report` with `status=blocked` makes the business task `blocked`. Prompt-delivery timeouts appear as `delivery_unknown` while a working live child is still allowed to publish its authoritative report. Running work uses Pi's warning/yellow semantic color and completed work uses success/green. Token totals use the child session's persisted assistant, tool-result, standalone usage, compaction, and branch-summary usage; they are never estimated. Queued tasks show neither fabricated time nor tokens. The monitor follows Pi theme semantics, truncates by visible terminal columns, and refreshes session usage from both file events and the running one-second timer.
106
-
107
- With an empty editor, press `↓` to enter the list, `↑`/`↓` to choose a task, `Enter` to focus its exact pane, and `Esc` to return. `/herdr-subagents focus <task-number>` remains available as a fallback. Inside every child Agent, press `Alt+P` or run `/parent` to return to the exact parent Agent pane; the shortcut is also shown in the child footer.
108
-
109
- ## History
110
-
111
- Run `/herdr-subagents history` or `herdr_subagents_control({ action: "history" })` to list the full compact project-local run and task archive after Herdr tabs close. History does not accept a run ID and is not a status interface. Full results, described documents, artifacts, and persistent Pi sessions remain separate on disk and are not loaded into parent context.
112
-
113
- Continue a saved conversation with `herdr_subagents_control({ action: "resume", runId: "<run-id>", task: 1, prompt: "Follow up..." })`. Different settled tasks from the same run may resume concurrently in distinct panes of one shared resume tab; the same task remains single-writer and rejects a second resume. Remove only a selected archive with `herdr_subagents_control({ action: "cleanup", runId: "<run-id>" })`. History has no automatic count-based eviction. The extension adds the runtime path to local Git excludes when available (and otherwise uses `.pi/.gitignore`) without overwriting unrelated rules.
114
-
115
- Read-only tasks start FIFO up to the configured concurrency limit. A pane is created only when its task starts, belongs exclusively to that task, and is never reused. Completed panes remain inspectable while another task in the batch is unsettled. Results remain in input order, and one failed task does not cancel its siblings. Any batch containing a write-capable persona is forced to concurrency one.
13
+ The reproducible source recording is retained as [`docs/demo.webm`](docs/demo.webm).
116
14
 
117
- The widget shows one row per queued, running, blocked, failed, or completed task. Answering a blocked task or resuming saved history refreshes authoritative persona, timing, session, status, and pane projections as work advances. Active duration accumulates only while the task is running, excluding time spent queued or waiting for an answer. Completed rows remain selectable until the whole batch settles. `/herdr-subagents active` shows the same compact state. Native labels stay short: `SA · <batch>` for the tab and `<order> · <task>` for each pane. When all tasks settle, the tab closes immediately if unfocused or after the user leaves it; a deferred close failure is persisted and shown as a warning without changing the completed/failed business result. An already-missing tab is treated as successfully cleaned, so retries are idempotent.
15
+ ## Execution contract
118
16
 
119
- Foreground calls wait until all children report completion or a request for input when no task fails. On the first task failure, they return early as described above while the surviving children continue. Every child is started with Herdr's Agent lifecycle as a normal Pi TUI and prompted with wait semantics; no print-mode command or completion marker is injected into the pane. The child extension reports Pi's authoritative `agent_start`/`agent_settled` lifecycle to Herdr, so provider retry and fallback screens cannot end a task early through screen detection alone.
17
+ `herdr_subagents` accepts an ordered, non-empty `tasks` array.
120
18
 
121
- ## Reports and records
122
-
123
- The child must finish through the package's structured `subagent_report` tool. Every prompt gets a unique attempt ID; the child commits to an attempt-specific report, and the parent accepts only the matching report before updating the canonical task result. A late report from an older retry, response, or historical follow-up cannot settle a newer attempt. The parent receives only:
124
-
125
- - `completed`, `blocked`, or `failed` status
126
- - a concise summary
127
- - paths to useful documents
128
- - the task-record location
129
-
130
- The complete result is not copied into the parent model context. Each run is retained under:
131
-
132
- ```text
133
- .pi/herdr-subagents/runs/<run>/tasks/<task>/
134
- ```
135
-
136
- The task directory contains the full result, canonical structured report, attempt-scoped reports and lifecycle markers, task metadata, system prompt, and persistent Pi session. Run-level metadata is stored in the parent run directory.
137
-
138
- ## Configuration and personas
139
-
140
- Herdr Subagents loads settings and persona definitions from separate files:
141
-
142
- | Scope | Settings | Persona definitions |
143
- | --- | --- | --- |
144
- | Project | `.pi/herdr-subagents.json` | `.pi/herdr-subagents/agents/*.md` |
145
- | Global | `~/.pi/agent/herdr-subagents.json` | `~/.pi/agent/herdr-subagents/agents/*.md` |
146
- | Package | built-in defaults | installed package `agents/*.md` |
147
-
148
- Project JSON values recursively override global JSON values. The built-in setting defaults are `defaultConcurrency: 1`, `maxConcurrency: 4`, and `stalledWarningSeconds: 300`. `defaultModel` and `defaultThinking` are unset, so the parent Pi model and its thinking level are inherited unless another setting wins.
149
-
150
- ### Settings file option reference
151
-
152
- These are all supported top-level fields in `herdr-subagents.json`:
153
-
154
- | Option | Accepted value | Default | Effect |
155
- | --- | --- | --- | --- |
156
- | `defaultConcurrency` | Positive integer | `1` | Number of read-only tasks allowed to run together when a batch call omits `concurrency`. It is capped to `maxConcurrency`. |
157
- | `maxConcurrency` | Positive integer | `4` | Hard ceiling for requested/default batch concurrency. A batch containing any write-capable persona still runs serially. |
158
- | `stalledWarningSeconds` | Positive integer | `300` | Seconds before a still-running child emits a warning. This is not a timeout and does not stop the child. |
159
- | `defaultModel` | Non-empty Pi model string, normally `provider/model` | Unset | Model used when neither JSON persona settings nor persona Markdown select one. If unset, the parent model is inherited. Use `/herdr-subagents models` to list authenticated models. |
160
- | `defaultThinking` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` | Unset | Thinking level below persona-specific settings. Provider/model support can vary. If unset, parent thinking is inherited only when the parent model is also inherited. |
161
- | `personas` | Object keyed by an existing persona name | `{}` | Per-persona JSON overrides described below. This does not create a persona; a matching Markdown definition must exist. |
162
-
163
- Known fields with an invalid JSON type, an empty required string, or a non-positive integer are ignored with a diagnostic shown by `/herdr-subagents settings`. Model IDs, thinking-level compatibility, and skill/extension source availability are ultimately handled by Pi when the child starts. When `defaultConcurrency` exceeds `maxConcurrency`, the effective default is reduced to the maximum.
164
-
165
- Each `personas.<name>` object supports exactly these overrides:
166
-
167
- | Option | Accepted value | Effect |
168
- | --- | --- | --- |
169
- | `model` | Non-empty Pi model string | Overrides the model from the effective persona Markdown and `defaultModel`. |
170
- | `thinking` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` | Overrides persona Markdown and `defaultThinking`. The value is passed to Pi's `--thinking` option. |
171
- | `skills` | Array of non-empty strings | Replaces the entire lower-precedence explicit skill list. Use `[]` to clear that list. Each item is passed to child Pi as `--skill`. Package-provided skills are separate; see below. |
172
- | `extensions` | Array of non-empty Pi source strings | Replaces the entire lower-precedence extension-source list. Use `[]` to clear it. Each item is passed as `--extension` after the mandatory child extensions. Accepts local files/directories, `npm:` and `git:` Pi packages, and `builtin:` sources. |
173
-
174
- JSON persona overrides cannot change `name`, `description`, `access`, `tools`, or the system prompt; define those in persona Markdown.
175
-
176
- Every child Pi explicitly loads and enables `codemode`. Built-in personas include it in their tool allowlists. Custom personas may omit `tools` and inherit Pi's defaults plus `codemode`, or list `codemode` explicitly alongside their allowed tools. A read-only persona remains read-only because codemode can call only the tools enabled for that child session.
177
-
178
- A global configuration can establish shared defaults:
179
-
180
- ```json
181
- {
182
- "defaultConcurrency": 2,
183
- "maxConcurrency": 4,
184
- "stalledWarningSeconds": 300,
185
- "defaultModel": "anthropic/claude-sonnet-4-5",
186
- "defaultThinking": "medium",
187
- "personas": {
188
- "explorer": {
189
- "model": "anthropic/claude-haiku-4-5",
190
- "thinking": "medium",
191
- "skills": ["code-search"]
192
- },
193
- "reviewer": {
194
- "thinking": "high",
195
- "skills": ["code-review"]
196
- }
197
- }
198
- }
199
- ```
200
-
201
- The project file can override only the fields that differ. Persona fields are merged by persona name:
19
+ - `concurrency: 1` runs tasks strictly in input order.
20
+ - `concurrency: N` runs at most N read-only tasks at once.
21
+ - A run containing a write-capable persona must use `concurrency: 1`; unsafe parallel writes are rejected.
22
+ - One task failing does not cancel its siblings.
23
+ - The tool returns only after every task is `completed`, `failed`, or `cancelled`.
24
+ - There is no background queue, blocked/respond flow, automatic resume, or early failure detach.
202
25
 
203
26
  ```json
204
27
  {
205
- "defaultConcurrency": 3,
206
- "stalledWarningSeconds": 600,
207
- "defaultThinking": "high",
208
- "personas": {
209
- "explorer": {
210
- "model": "openai/gpt-5.2"
211
- },
212
- "reviewer": {
213
- "skills": ["security-review", "code-review"]
214
- }
215
- }
28
+ "label": "review",
29
+ "concurrency": 2,
30
+ "tasks": [
31
+ { "name": "api", "prompt": "Review the API boundary.", "agent": "reviewer" },
32
+ { "name": "tests", "prompt": "Find missing test coverage.", "agent": "explorer" }
33
+ ]
216
34
  }
217
35
  ```
218
36
 
219
- The package includes these personas:
220
-
221
- | Persona | Access | Built-in tools | Intended use |
222
- | --- | --- | --- | --- |
223
- | `worker` | write | `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls` | Focused implementation work |
224
- | `explorer` | read | `read`, `bash`, `grep`, `find`, `ls` | Fast codebase investigation |
225
- | `reviewer` | read | `read`, `bash`, `grep`, `find`, `ls` | Correctness and maintainability review |
226
- | `analyst` | read | `read`, `bash`, `grep`, `find`, `ls` | Neutral analysis with the method and role supplied by each task |
227
-
228
- `analyst` deliberately does not bind a model or review method. Use the task-level `model` field when independent tasks need different models, and put roles such as auditor, replicator, or adversary in each task prompt.
229
-
230
- Persona definitions use Markdown frontmatter followed by the child system prompt. For example, save this as `.pi/herdr-subagents/agents/security-reviewer.md`:
231
-
232
- ```md
233
- ---
234
- name: security-reviewer
235
- description: Read-only reviewer for security risks
236
- access: read
237
- model: anthropic/claude-sonnet-4-5
238
- thinking: high
239
- tools: read, bash, grep, find, ls
240
- skills: security-review, code-review
241
- ---
242
-
243
- Review the assigned scope without modifying files. Report concrete risks and evidence.
244
- ```
245
-
246
- Persona Markdown supports these frontmatter fields:
247
-
248
- | Field | Accepted value | Required/default | Effect |
249
- | --- | --- | --- | --- |
250
- | `name` | Non-empty string | Required | Persona identifier used by `agent`. Names must be unique within one persona directory. |
251
- | `description` | Non-empty string | Required | Description shown to the parent when agents are listed. |
252
- | `access` | `read` or `write` | `write` | Scheduling declaration. Any batch containing `write` is forced to concurrency one. `read` permits parallel scheduling but does not itself remove tools. |
253
- | `model` | Non-empty Pi model string | Unset | Persona model default. |
254
- | `thinking` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max` | Unset | Persona thinking default passed to Pi's `--thinking` option. |
255
- | `tools` | Non-empty comma-separated list, optionally bracketed | Child Pi defaults | Restricts built-in tools. Supported names are `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`, and `powershell`. `subagent_report` is always added. |
256
- | `skills` | Comma-separated list, optionally bracketed | None | Skills loaded explicitly into child Pi; automatic skill discovery is disabled. |
257
-
258
- The Markdown body after the frontmatter is the child system prompt and may be empty. To make a custom persona operationally read-only, set `access: read` **and** choose an appropriate `tools` allowlist; `access` alone is not a tool sandbox. Model names and skill values are passed to Pi, so they must be available in the installed Pi environment.
259
-
260
- Persona Markdown definitions use replacement precedence, not field merging. A same-name project definition replaces the entire global or package definition:
37
+ Each task starts a single-task interactive Pi process in its own Herdr pane. Herdr first confirms the interactive Pi is ready, then the prompt is delivered exactly once. Prompt acknowledgement never decides task completion. Panes remain ordinary Herdr panes: they can be focused and inspected while work is running.
261
38
 
262
- 1. project `.pi/herdr-subagents/agents/*.md`
263
- 2. global `~/.pi/agent/herdr-subagents/agents/*.md`
264
- 3. package built-ins
39
+ ## Result protocol
265
40
 
266
- ### Model and persona-option precedence
41
+ The child must call `subagent_report` once with a terminal status:
267
42
 
268
- The child model is selected in this exact order:
43
+ - `completed`: the requested work finished.
44
+ - `failed`: it cannot be completed; include `error`.
269
45
 
270
- 1. `model` on the task or call
271
- 2. project `personas.<name>.model` JSON setting
272
- 3. global `personas.<name>.model` JSON setting
273
- 4. `model` in the effective persona Markdown frontmatter
274
- 5. effective `defaultModel` JSON setting (project overrides global)
275
- 6. parent Pi model
46
+ The complete result is stored on disk. The parent receives only the compact summary and document paths.
276
47
 
277
- The child thinking level is selected in this order:
48
+ An atomically renamed attempt report is the authoritative completion signal. Herdr states such as `idle`, `blocked`, and `done` are wake-up hints only. A prompt transport error or Pi retry cannot fail a task while its child process remains alive. If the process exits without a valid report, the task fails explicitly.
278
49
 
279
- 1. project `personas.<name>.thinking` JSON setting
280
- 2. global `personas.<name>.thinking` JSON setting
281
- 3. `thinking` in the effective persona Markdown frontmatter
282
- 4. effective `defaultThinking` JSON setting (project overrides global)
283
- 5. parent Pi thinking level when the child also inherits the parent model
50
+ Duplicate identical reports are idempotent. A conflicting report for the same task attempt is rejected. Terminal task records are immutable.
284
51
 
285
- Thinking has no call-level override. `skills` and `extensions` use the first three persona-level steps and have no global default. A project array replaces, rather than appends to, the global or effective Markdown list; `[]` explicitly clears it. There is no task-level extension override.
52
+ ## State model
286
53
 
287
- ### Explicit extension sources and Pi packages
54
+ The same small state machine is used for every task; there are no separate foreground, background, blocked, resumed, or retry lifecycles.
288
55
 
289
- For a local routing hook, add this frontmatter to a persona Markdown file (the hook must already exist):
56
+ New task records use:
290
57
 
291
- ```md
292
- ---
293
- name: routed-explorer
294
- description: Explorer with a provider routing hook
295
- access: read
296
- tools: read, bash, grep, find, ls, codemode
297
- extensions: ~/.pi/agent/extensions/gateway-routing.ts
298
- ---
299
- Inspect the requested code and report your findings.
300
58
  ```
301
-
302
- Frontmatter accepts a single source or a comma-separated inline list, optionally enclosed in brackets. Use `extensions: []` to clear the list. Like the existing persona format, it does not support multiline YAML lists.
303
-
304
- JSON accepts an array only. For example, override the built-in explorer with a local hook and an npm Pi package:
305
-
306
- ```json
307
- {
308
- "personas": {
309
- "explorer": {
310
- "extensions": [
311
- "~/.pi/agent/extensions/gateway-routing.ts",
312
- "npm:@example/pi-tools@1.0.0"
313
- ]
314
- }
315
- }
316
- }
59
+ queued -> running -> completed | failed | cancelled
317
60
  ```
318
61
 
319
- `@example/pi-tools` is illustrative; replace it with a trusted Pi package. An ordinary npm library is not automatically a Pi extension package. Pin npm versions or git refs when reproducible restarts matter. Relative local paths resolve from the child working directory (the project root), **not** from the JSON or Markdown file. `~` resolves to the user's home directory. Source strings are passed to Pi without rewriting `npm:`, `git:`, or `builtin:` identifiers.
62
+ A run is `running` while any task is queued or running. Settled aggregate states are `completed`, `failed`, `partial`, or `cancelled`.
320
63
 
321
- **This field uses Pi's native package-loading semantics. It is not an extensions-only resource filter.** A file loads that extension. A Pi package can also load its declared **skills, prompt templates, and themes**. In particular, explicitly selected package skills still load despite the child's `--no-skills`; persona `skills: []` clears only the explicit skill list, not skills carried by an extension package. To select one extension without the package's other resources, use its local extension-file path or a dedicated package that exposes only the intended resources. The string list does not support Pi's settings object-form resource filters.
64
+ Older records containing `starting` or `blocked` remain readable for history and cleanup, but new runs never write those states.
322
65
 
323
- Children still start with `--no-extensions`, then explicitly load `builtin:codemode`, the bundled `subagent-report`, and the configured sources in order. Unconfigured personas retain their existing behavior. The list does not inherit the parent's extensions, installed-package settings, or package resource filters. Pi manages source resolution, caching, dependency installation, and startup diagnostics; an npm/git source may require network access. Herdr Subagents does not implement a separate installer or guarantee that Pi's extension warnings terminate a task. Inspect the child's startup diagnostics when a required routing hook does not run.
66
+ ## Durable records
324
67
 
325
- Effective sources are saved in each task record and shown by persona listing. Historical resume, failed/blocked-task restarts, and queued-task recovery reuse the saved list instead of re-reading current persona overrides. A live child is not reloaded when configuration changes. Saving a source does not freeze its code: local files and unpinned package versions can change.
68
+ Records are stored under:
326
69
 
327
- Load only trusted extension code. `access: read` controls scheduling; it is not a sandbox for extension hooks, which can perform side effects outside model tools. `PI_HERDR_SUBAGENTS_CHILD` prevents **this package** from registering recursively, not arbitrary third-party extensions. Loading a package does not broaden an explicit `tools` allowlist. Persona `tools` still accepts only the existing built-in names; adding extension-defined tools to that schema is a separate feature.
328
-
329
- Override the model for one task:
330
-
331
- ```text
332
- herdr_subagents({
333
- tasks: [
334
- { name: "authorization", agent: "reviewer", model: "anthropic/claude-opus-4-1:high", prompt: "Review the authorization boundary" }
335
- ]
336
- })
337
70
  ```
338
-
339
- Each task in a batch can select its own model:
340
-
341
- ```text
342
- herdr_subagents({
343
- concurrency: 2,
344
- tasks: [
345
- { name: "map", agent: "explorer", model: "anthropic/claude-haiku-4-5", prompt: "Map the request flow" },
346
- { name: "review", agent: "reviewer", model: "anthropic/claude-sonnet-4-5", prompt: "Review input validation" }
347
- ]
348
- })
71
+ .pi/herdr-subagents/runs/<sortable-run-id>/
72
+ run.json
73
+ tasks/<order>-<task-id>/
74
+ task.json
75
+ session.jsonl
76
+ system-prompt.md
77
+ report.json
78
+ reports/<attempt-id>.json
79
+ results/<attempt-id>.md
349
80
  ```
350
81
 
351
- Run `/herdr-subagents agents`, `/herdr-subagents models`, or `/herdr-subagents settings` to inspect effective values and their sources.
82
+ The orchestrator is the only writer of `run.json`, `task.json`, and promoted `report.json`. The child only writes its attempt-scoped result and report.
352
83
 
353
- ## Recovery and failures
84
+ ## Control and UI
354
85
 
355
- Queued background submissions are persisted before dispatch. Closing the parent Pi session cancels queued, running, and blocked work, records `cancelled`, and closes the owned run tab. On the next Pi start, stale active records and queued submissions from a prior parent session are reconciled to a terminal state, including when Pi reused the same process, and leaked tabs are cleaned up when possible. Missing, truncated, or structurally invalid child reports become explicit failures or cancellation diagnostics; recovery errors are reported without leaving new submissions paused.
86
+ `herdr_subagents_control` supports `list`, `status`, `history`, `cancel`, and `cleanup`. Follow-up work is a new task or run; settled tasks are never revived.
356
87
 
357
- Tab, pane, and child startup commands are not automatically retried: an error may arrive after Herdr already created the resource. Recovery is best effort, not complete: Herdr 0.9.2 has no idempotency key for tab creation or pane splitting, so a `SIGKILL` after the external resource is created but before its ID is durably persisted can leave an orphan that cannot be identified for cleanup. A failure after prompt submission is preserved and never automatically rerun. Historical follow-ups use a recoverable task-scoped `.resume.lock`, so different tasks can continue concurrently without allowing two writers for one saved session. Herdr exposes no retrying state, and Pi exposes no reliable public auto-retry hook, so `retrying` is supported by the runtime schema and Fleet but is not fabricated from ambiguous lifecycle events. `stalledWarningSeconds` emits a warning without killing long-running work. Run/task/report snapshots use atomic replacement so interruption cannot expose half-written JSON.
88
+ Use `/herdr-subagents focus <task-number>` to focus a running pane. `/herdr-subagents active`, `history`, `agents`, `models`, and `settings` provide compact inspection.
358
89
 
359
- The child does not inherit parent extensions or parent conversation. Skills load from explicit persona paths and from explicitly selected Pi packages; normal project context files still apply. It uses the same Pi installation, provider configuration, model catalog, and credentials as the parent.
90
+ ## Personas and settings
360
91
 
361
- ## Development and tests
92
+ Bundled personas are `worker`, `explorer`, `analyst`, and `reviewer`. Project personas can be added under `.pi/agents/` using the same frontmatter format as the bundled files. Access is declared as `read` or `write` and drives concurrency safety.
362
93
 
363
- See [TESTING.md](TESTING.md) for deterministic tests, extension loading, packaging, and real Herdr smoke requirements.
94
+ Settings are read from the existing package configuration. `defaultConcurrency`, `maxConcurrency`, and `stalledWarningSeconds` remain supported.
@@ -1,53 +1,36 @@
1
- # Herdr Subagents test strategy
2
-
3
- ## Flow
4
-
5
- 1. `npm --prefix packages/herdr-subagents test` runs deterministic scheduler, recovery, and persona tests, then a controlled run-state-lock regression for deferred cleanup. The regression proves that the old 10 ms assertion can observe unfinished cleanup while both successful cleanup and error persistence eventually settle after lock release; it also checks that the condition waiter fails on timeout.
6
- 2. `PI_TEST_BINARY="$(command -v pi)" npm --prefix packages/herdr-subagents run load-test` verifies extension contracts and loading, then starts real Pi with generated child arguments against temporary file/package fixtures. The package fixture must expose its skill and prompt despite discovery flags, while the explicit tool allowlist remains unchanged. This check makes no provider calls or network installs. `PI_TEST_BINARY` pins the tested CLI so npm cannot substitute an older workspace-local Pi binary; use a Pi version supporting native `builtin:codemode` and package sources.
7
- 3. From a Pi session inside Herdr, run `npm --prefix packages/herdr-subagents run test:real-herdr`. This is a mandatory child-package and root-bundle release gate. It starts one unconfigured real child Pi and then two concurrent read-only child Pi Agents with an explicit local Pi package. It requires assistant events, matching attempt-scoped reports, saved extension sources, a package-extension startup marker, package skill availability despite `--no-skills`, and final tab cleanup. `HERDR_SUBAGENTS_E2E_MODEL=provider/model` optionally pins the smoke model. The check waits up to five seconds for pane removal and the durable cleanup commit; a recorded `cleanupError` fails immediately instead of being retried away. A failed assertion or runtime check preserves the temporary durable records, `trace.json`, and `failure.json` with tab/pane snapshots. Teardown errors are retained separately and fail the gate; already-missing tabs are successful cleanup.
8
-
9
- For an intentional focus regression, run `HERDR_SUBAGENTS_E2E_FOCUS_CLEANUP=1 HERDR_SUBAGENTS_E2E_MODEL=provider/model npm --prefix packages/herdr-subagents run test:real-herdr`. It briefly focuses only the concurrent test's tab at cleanup, requires `cleanupPendingTabIds`, restores the globally focused workspace and its active tab, asserts that focus is still restored after cleanup, then verifies automatic cleanup without manually closing the test tab. Run this only when brief visible focus changes are acceptable; it requires an existing focused tab to restore.
10
- 4. From a dedicated visible X11 Herdr client/window with the GStreamer X11 and VP8 plugins installed, run `HERDR_MEDIA_WINDOW_ID=<window-id> node scripts/capture-package-media.mjs`. Capture intentionally cycles through the live child panes, so avoid using that client/window for other work while recording; the script restores the previously focused pane afterward when it still exists. The Herdr capture must show a parent Pi conversation, a three-task run tab, each of the three real interactive Agent panes receiving focus in turn, the active parent Fleet footer with yellow running indicators and real metrics, and the final compact parent response. The published screenshot must be the parent Fleet view, while the full WebM preserves the parent → three focused children → Fleet → parent sequence.
11
- 5. `npm pack ./packages/herdr-subagents --dry-run` verifies package contents.
12
- 6. Verify the README renders the GitHub attachment video directly and live Herdr UI media `docs/screenshot.png` and `docs/demo.webm` are present in the tarball; `docs/demo.gif` must be absent. Inspect the WebM metadata and play it through once to confirm the parent → three focused children → Fleet → parent sequence.
13
-
14
- ## Cases
15
-
16
- - Resolve package, global, and project personas and validate frontmatter/configuration, including the neutral read-only `analyst` persona.
17
- - Verify extension-source Markdown parsing, JSON type diagnostics, precedence, trimming, replacement/empty lists, unchanged unconfigured startup, local/npm source argument preservation, persona listing, durable history, historical resume, blocked-task restart, and queued-task recovery.
18
- - Run foreground tasks and FIFO bounded-concurrency batches through an injectable Herdr boundary.
19
- - Simulate one fast failure beside a controlled slow success and verify the foreground call returns the stable run ID and failure before releasing the slow task, detaches caller cancellation without aborting that task, releases the dispatcher so the next batch starts before the slow task settles, removes durable queue state, and emits exactly one final follow-up.
20
- - Verify a detached dispatcher promise can settle normally after its successor starts without clearing or otherwise disturbing the successor's active slot, while blocked foreground runs retain their existing dispatcher ownership.
21
- - Let a detached run later block or be explicitly cancelled and verify the newer dispatcher owner remains intact and each detached run emits exactly one final notification.
22
- - Shut down the parent while a handed-off sibling is active and verify it is cancelled and joined, durable queue state is removed, and no stale follow-up is sent.
23
- - Verify all-success foreground batches still wait for every task and explicit `background: true` submissions still return immediately.
24
- - Verify a blocked dispatcher owner immediately rejects both newly submitted and already-queued foreground runs with its full run ID, keeps explicit background runs queued, and removes queued foreground runs when their caller cancels.
25
- - Verify a parallel task blocks exactly like a serial one, keeping its question, attempt-scoped report, and tab while an independent sibling completes, that answering it settles and closes the run, and that reconciliation treats a blocked report identically at any concurrency.
26
- - Verify two lanes of one parallel batch can block at once, that each question is exposed in turn as they are answered, and that the run closes only after the last answer.
27
- - Answer a blocked lane while a sibling of the same parallel batch is still running and verify the batch keeps sole ownership of its queued tasks, runs them exactly once, and then settles and closes its tab against the task records rather than its own stale blocked snapshot, carrying no lifecycle field the blocked record left behind.
28
- - Verify historical follow-ups cannot create unowned blocked work and owner-loss reconciliation cancels serial blocked work instead of reconstructing hidden dispatcher ownership.
29
- - Verify all paths use interactive Agent start/prompt without print mode, shell injection, markers, or pane reuse.
30
- - Force write-capable batches to serial execution while allowing bounded read-only parallelism, including three concurrent `analyst` tasks with independent task-level models.
31
- - Preserve compact reports, full results, Pi sessions, blocked questions, and historical follow-ups.
32
- - Recover queued/running work after interruption without retrying post-prompt side effects, including truncated reports and recovery errors.
33
- - Verify the split public contracts (`herdr_subagents` with required `tasks`, plus `herdr_subagents_control` with required `action`) and injectable extension wiring, including conditional registration only inside Herdr, Fleet rendering, editor focus ownership, terminal-input passthrough, exact-pane navigation, child `/parent` and `Alt+P` return controls, and stable display while another background run queues.
34
- - Verify Fleet divider/row formatting, warning/yellow running indicators, success/green completed indicators, persona and task projections, cumulative active duration across blocking/resume, exact Pi usage aggregation from assistant messages, tool results, standalone usage, compaction, and branch summaries, malformed/truncated JSONL tolerance, queued metric omission, and visible-column bounds at narrow widths.
35
- - Verify live token updates are file-event driven and polled on each running one-second tick, elapsed repainting exists only while work runs, and widget disposal closes watchers and timers.
36
- - Verify selected active/queued run cancellation, blocked-response live Fleet updates, authoritative stale-field replacement, parent-shutdown cancellation, idempotent missing-tab cleanup, cleanup notifications, atomic state replacement, non-retried uncertain startup operations, and stalled warnings.
37
- - Verify `status(runId)` isolates one run, projects queue-only state, prefers durable state over a stale queue record, exposes blocked questions, marks terminal states settled, rejects unknown IDs, and leaves full `history` semantics unchanged.
38
- - Verify historical follow-ups allow different tasks to share one resume tab while preserving one session writer per task, and active resume membership keeps aggregate status running before the sibling task record is persisted.
39
- - Verify Herdr `blocked` projects only `waiting_interaction`, prompt-delivery timeout projects `delivery_unknown` and accepts a late report, terminal API/process errors without reports fail, and `retrying` remains a display-only running runtime state until a reliable lifecycle source exists.
40
- - Simulate a false screen-settled return while the authoritative Pi lifecycle remains working, then write the report later and verify the task completes without a transient failed state.
41
- - Verify reports are bound to unique attempt IDs, blocked results are not tool errors, duplicate blocked responses are serialized, mixed failed/cancelled aggregates remain partial, and cleanup failures never overwrite business outcomes.
42
- - Verify settling a batch merges run state written concurrently under the run lock instead of overwriting `run.json` from a stale in-memory copy.
43
- - Wait for deferred cleanup commits/notifications rather than fixed sleeps. Hold the real run-state lock across the old assertion point to reproduce both successful-cleanup and error-persistence races, then require the expected durable state after release. Real smoke failures retain evidence before teardown.
44
- - Verify an explicitly named failed task continues from its saved Pi session: it reuses its own live pane while its batch still runs, restarts in a new pane when that pane is gone, refuses to continue without a saved session, is never selected by an unqualified response, and does not start queued siblings that the running batch still owns.
45
- - Verify a settling batch adopts the task records on disk, so a continued task keeps its run `running` and its Herdr tab open instead of being overwritten by the batch's own stale view.
46
- - Verify a continuation claims its task before touching any pane, so a sibling continuation that finishes first leaves the shared tab open, and only the last one to settle closes it.
47
- - Verify a sibling that is still queued is reported as running work rather than as an invented input request with no question.
48
- - Verify a continued task may ask for input, becomes an ordinary blocked task, and is then answerable without naming its number.
49
- - Verify `respond` refuses completed, cancelled, queued, running, and nonexistent tasks without touching Herdr or the run record.
50
-
51
- ## Latest baseline
52
-
53
- All 266 deterministic scheduler, recovery, process-boundary, schema-validation, navigation, metrics, responsive-formatting, lifecycle, attempt-isolation, and persona cases, the controlled cleanup-timing regression, and the extension-contract lifecycle simulations must pass. The startup regression test reproduces the real ordering where `before_agent_start` records the attempt as working while Herdr can still report stale idle. Every release also runs the real single-Agent and concurrent read-only Herdr smoke; a skipped smoke is not a passing release check.
1
+ # Testing Herdr Subagents
2
+
3
+ Run the core suite:
4
+
5
+ ```sh
6
+ npm test --workspace @maxiaochao/pi-herdr-subagents
7
+ ```
8
+
9
+ It verifies:
10
+
11
+ - strict serial start order at concurrency one;
12
+ - bounded read-only parallelism;
13
+ - ordered results despite concurrent completion;
14
+ - sibling continuation after failure and all-failed aggregation;
15
+ - explicit rejection of parallel write-capable tasks;
16
+ - waiting through a delayed authoritative report after a prompt transport error while the Pi process is alive;
17
+ - explicit failure when the process exits without a report;
18
+ - parent cancellation of running and queued tasks;
19
+ - immutable terminal task records;
20
+ - idempotent duplicate reports and rejection of conflicting replacements.
21
+
22
+ The previous broad lifecycle suite remains available temporarily as `npm run test:legacy --workspace @maxiaochao/pi-herdr-subagents` for migration auditing. Scenarios for background queues, blocked/respond, and historical resume intentionally no longer describe the public contract.
23
+
24
+ Run extension source loading. The direct-Pi fixture validates explicit file/package sources; Herdr's `builtin:codemode` alias is validated by the real-Herdr smoke because the alias is translated by `herdr agent start`:
25
+
26
+ ```sh
27
+ npm run load-test --workspace @maxiaochao/pi-herdr-subagents
28
+ ```
29
+
30
+ Run the real Herdr smoke test from a Herdr session:
31
+
32
+ ```sh
33
+ npm run test:real-herdr --workspace @maxiaochao/pi-herdr-subagents
34
+ ```
35
+
36
+ For real smoke testing, verify that each child appears in a visible pane, can be focused and inspected, exits after one terminal report, and that the parent returns only after all requested tasks settle. Also verify cancellation closes the run tab and does not overwrite a report already atomically published.