pi-herdr-managed-agents 0.1.0-beta.1

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.
package/AUDIT.md ADDED
@@ -0,0 +1,81 @@
1
+ # Native harness reliability audit
2
+
3
+ Compared our standalone extraction against `giuseppecrj/pi-herdr-agents` at [`8a181890479f46d5955d4f1bac3be759a2d39010`](https://github.com/giuseppecrj/pi-herdr-agents/tree/8a181890479f46d5955d4f1bac3be759a2d39010) (npm v1.6.0).
4
+
5
+ Scope: launch/readiness, process versus turn lifecycle, result delivery, supervision, and recovery. This is a targeted reliability audit, not a comprehensive security certification of either package. The comparison checkout was inspected read-only; its scripts and dependencies were not executed. Fixes below are independent implementations, not copied source.
6
+
7
+ ## Findings fixed in this extraction
8
+
9
+ ### P1 — Early waits could recommend closing a child that was still working
10
+
11
+ `subagent_wait` returns early if any requested child needs input. Its formatter nevertheless classified other `running` children as `finished`, printed their old final text, and added a terminal-cleanup instruction. Following that instruction could destroy ongoing work.
12
+
13
+ **Fix:** running, blocked, failed, and completed wait sections are distinct. A running child gets no final output or cleanup instruction. Regression coverage waits on a blocked child plus a working sibling.
14
+
15
+ Files: `extensions/subagents/index.ts`, `extensions/subagents/manager.test.ts`.
16
+
17
+ ### P1 — Missing prompt evidence caused ambiguous tasks to be replayed
18
+
19
+ The native backend resent a task when it could not correlate the submitted prompt with its transcript. Missing session hooks or delayed metadata do not prove the first submission did not execute. Side-effecting work could run twice. The same error path assumed an unseen prompt meant a stopped agent and closed its tab.
20
+
21
+ **Fix:** submit the prompt once. Claude receives a generated native `--session-id`, and the manager knows its expected transcript path before submission; integration metadata is no longer the sole source of that identity. A positively identified pending Claude paste may receive Enter, but the prompt is never pasted again automatically. On missing correlation, report the uncertainty, confirm interruption, and retain the stopped tab for diagnosis; force-close only when interruption cannot be confirmed.
22
+
23
+ Regression tests run the real native backend and manager through a fake Herdr CLI: missing session hooks can still yield a correlated final answer; absent transcripts cause one submission, an interruption, and no premature tab closure. These are offline simulations, not a claim that the user's historical live failure was reproduced and eliminated.
24
+
25
+ Files: `extensions/subagents/src/backends/herdr.ts`, `test/native-startup.test.ts`.
26
+
27
+ ### P1 — Pending follow-ups still looked completed
28
+
29
+ The manager reserved a concurrency slot through a private `restarting` flag, but left its public snapshot completed until the asynchronous `RunStarted` event arrived. A wait/check could return the old result, and pruning could treat the session as disposable. A new run also retained the previous final text.
30
+
31
+ **Fix:** reserve the new turn's visible `running` state synchronously, clear stale terminal fields, reject overlapping startup sends, and restore the old snapshot if submission fails before `RunStarted`. Regression coverage holds a follow-up at the backend boundary and checks status, waiting, duplicate rejection, failed-send rollback, and cancellation before startup acknowledgement. Cancelling a still-starting follow-up disposes its session so a delayed submission cannot start afterward.
32
+
33
+ Files: `extensions/subagents/src/manager.ts`, `extensions/subagents/manager.test.ts`.
34
+
35
+ ### P1 — Failed notification sends could lose queued results
36
+
37
+ The result queue cleared every entry before trying to deliver the batch. If one host `sendMessage` threw, that result and the rest of the batch were lost. A follow-up handler also deleted its deferred result by ID after awaiting submission, which could delete an already-completed new turn instead of the old result.
38
+
39
+ **Fix:** acknowledge each result only after the host accepts it. Preserve failed and unattempted entries for a later flush. Retract the specific previous result rather than whichever result now occupies that ID. Tests cover ordered delivery, failed sends, reentrant completion, and fast follow-up races. This does not provide transactional exactly-once delivery across process crashes or hosts that accept a message and then throw.
40
+
41
+ Files: `extensions/subagents/src/result-delivery.ts`, `extensions/subagents/index.ts`, `extensions/subagents/result-delivery.test.ts`.
42
+
43
+ ### P2 — Input-required notification was deferred and acknowledged too early
44
+
45
+ A blocked child notification used `followUp`, which waits for the parent's entire run to end, despite needing prompt attention. Its version was marked notified before the host send succeeded.
46
+
47
+ **Fix:** send input-required messages as steering messages (available after the current tool batch), and mark the version only after successful submission. Normal completion still uses deferred follow-ups so explicit waits can consume pending results without duplicate automatic delivery.
48
+
49
+ File: `extensions/subagents/index.ts`.
50
+
51
+ ### Packaging check — A supposedly clean profile still loaded personal skills
52
+
53
+ Pi discovers `~/.agents/skills` in addition to its configured agent directory. Isolating only `agentDir` allowed the maintainer's personal skills into the smoke test.
54
+
55
+ **Fix:** isolate the home environment as well and restore it afterward. The test verifies exactly the published extension and portable skill.
56
+
57
+ File: `test/package.test.ts`.
58
+
59
+ ## Ideas worth borrowing next
60
+
61
+ | Idea from the comparison package | Recommendation for our native multi-harness design |
62
+ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | Distinct process, turn, and delivery state (`lifecycle.ts`) | Adopt the invariants, not its entire state machine. The fixes above address concrete conflations. Explicit open/closed session metadata would further improve recovery and cleanup messages. |
64
+ | Explicit session identity before launch (`launch.ts`) | Applied to native Claude with its own `--session-id` option; retain each harness's actual CLI and transcript format. |
65
+ | File wake-ups with bounded reconciliation (`wake.ts`, `supervision.ts`) | Good performance follow-up. Our viewport timer alone launches two `herdr pane read` commands per second per active child. First consider on-demand viewport reads, then shared event-driven supervision with a polling fallback. |
66
+ | Retained managed worktrees (`launch.ts`) | Useful for parallel writers, but an explicit new feature: ownership, dirty-state recovery, and cleanup require their own design and tests. Do not silently create worktrees for readers. |
67
+ | Watchers survive reload/session changes (`index.ts`) | Potentially useful, but do not copy automatic delivery into a different parent conversation. Define ownership and opt-in handoff first; current teardown behavior is documented. |
68
+ | Help-request and completion sidecars (`completion.ts`) | Useful for Pi-owned children; not directly portable to native Claude/Codex. Keep native transcript correlation unless each harness exposes a supported event channel. |
69
+ | Built-in roles/model fallback | Not required to fix these bugs. Keep model-neutral self-contained tasks; never automatically replay uncertain side-effecting work on another model. |
70
+
71
+ ## Validation and remaining limits
72
+
73
+ - `npm run check`: passes against Pi 0.85.1.
74
+ - `npm test`: 48 offline tests pass, including native-backend simulations and package allowlist/clean-profile loading.
75
+ - `npm run format:check`: passes.
76
+ - Formatting and Git whitespace checks are release gates.
77
+ - No new runtime dependencies were added by these audit fixes.
78
+ - Real native smoke testing remains pending: the installed Herdr client is 0.8.2/protocol 20 while the user's running server is 0.8.0/protocol 19. The user's server was not stopped or upgraded.
79
+ - Native readiness still contains fixed grace periods. Startup-time permission dialogs, transcript-path changes, and the original screenshot-review failure need live verification before a stable release.
80
+ - These fixes are in the standalone package only; they have not been installed into the maintainer's live Pi setup.
81
+ - The packed archive also loads successfully with production-only dependencies in a fresh home/profile.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benjamin Davis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Pi Herdr Managed Agents
2
+
3
+ Run **Pi, Claude Code, and Codex subagents in separate, auditable Herdr tabs** while Pi keeps ownership of their tasks, follow-up messages, results, and cancellation.
4
+
5
+ This is an independent community extension, not an official Herdr or Pi product. It is distinct from the separately maintained `pi-herdr-agents` npm package.
6
+
7
+ **Beta:** native CLI interfaces and transcript formats change. See [limitations](#limitations-and-troubleshooting) before relying on unattended runs.
8
+
9
+ ## Install
10
+
11
+ From GitHub:
12
+
13
+ ```sh
14
+ pi install git:github.com/Crush53/pi-herdr-managed-agents@v0.1.0-beta.1
15
+ ```
16
+
17
+ From npm (beta):
18
+
19
+ ```sh
20
+ pi install npm:pi-herdr-managed-agents@0.1.0-beta.1
21
+ ```
22
+
23
+ Restart Pi or run `/reload` after installation. Disable any existing extension that registers `subagent_spawn`, the other `subagent_*` tools, `/subagents`, or `/btw` before enabling this one. In particular, don't load this alongside the original `my-pi-setup` subagents extension.
24
+
25
+ The package does **not** change your model, theme, credentials, or global instructions. Its portable orchestration skill is included; harness-specific review skills are not.
26
+
27
+ ## Requirements and Herdr setup
28
+
29
+ - Node.js 22.18+ and Pi. Development checks target Pi **0.85.1**; other releases are not yet verified.
30
+ - [Herdr](https://herdr.dev/) for visible native tabs, with a matching client/server version. Outside Herdr, headless backends are used instead.
31
+ - Install and authenticate only the harnesses you want to use: `pi`, `claude`, and/or `codex` on `PATH`.
32
+
33
+ Install Herdr using its official instructions, then install the native integrations you need:
34
+
35
+ ```sh
36
+ herdr integration install pi
37
+ herdr integration install claude
38
+ herdr integration install codex
39
+ ```
40
+
41
+ These commands update the relevant harness configuration; the package never runs them automatically. Herdr's generated integration files are not bundled, so they can match your installed Herdr version.
42
+
43
+ Launch Herdr from a trusted project, then launch Pi inside a Herdr pane:
44
+
45
+ ```sh
46
+ herdr
47
+ # In a Herdr terminal:
48
+ pi
49
+ ```
50
+
51
+ ## Use
52
+
53
+ Ask Pi naturally:
54
+
55
+ > Use a Claude subagent in plan mode to inspect this project and propose a migration plan. Don't implement it. Iterate on the plan with the same agent.
56
+
57
+ > Ask a Codex subagent to review the changed files while you run the tests. Report its findings when it finishes.
58
+
59
+ Model hints are interpreted by each harness. Pi inherits the parent model and thinking level by default; Claude and Codex use their own defaults. This package does not require any particular provider or model.
60
+
61
+ ### Tools and commands
62
+
63
+ | Tool / command | Purpose |
64
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | `subagent_spawn` | Start a self-contained task with `prompt`, `name`, `harness`, optional `working_dir`, `model`, `reasoning_effort`, and Claude-only `mode: "plan"` |
66
+ | `subagent_send` | Steer a running child or continue a settled session on the same ID |
67
+ | `subagent_check` | Nonblocking snapshot without consuming the result |
68
+ | `subagent_list` | List model-visible managed sessions |
69
+ | `subagent_wait` | Wait for requested results; return early when a child needs input |
70
+ | `subagent_cancel` | Interrupt work and retain partial transcripts |
71
+ | `/subagents` | Dashboard and managed interaction |
72
+ | `/btw <question>` | User-invoked background Pi side question |
73
+
74
+ Up to **four children** run concurrently, shared across all harnesses and `/btw`. Completion and input-required notifications return to the parent automatically. No polling loop is needed.
75
+
76
+ Native tabs retain each harness's own UI, skills, and configuration. The parent receives correlated final text, not an arbitrary terminal snapshot. Native session files stay with the harness. Outside Herdr, Pi uses an in-process SDK session, Claude uses the Agent SDK, and Codex uses its app-server protocol.
77
+
78
+ ### Planning and cleanup
79
+
80
+ Use `mode: "plan"` for planning-only Claude tasks. Iterate through `subagent_send`. At Claude's **Ready to code?** dialog, choosing Yes starts implementation: for planning-only work, choose the revision/feedback option and request the final plan without implementation.
81
+
82
+ Keep a blocked or follow-up-needed tab open. After collecting the result and finishing immediate follow-up, close only the reported child tab. The package prompts the parent to do this; it does not remove arbitrary user tabs. Shutdown, reload, and session replacement dispose managed sessions and may close their tabs.
83
+
84
+ ## Security and permissions
85
+
86
+ **These are autonomous processes with access to your machine, not sandboxes.**
87
+
88
+ - Native Claude execution uses `--dangerously-skip-permissions`; native Codex uses `--dangerously-bypass-approvals-and-sandbox`. The headless Claude and Codex execution backends also bypass native approvals.
89
+ - Claude plan mode uses the harness's native planning permissions instead of its execution bypass flag.
90
+ - Claude/Codex delegation requires a trusted working directory; an untrusted parent cannot obtain bypass access by choosing another directory. Pi child project resources remain trust-gated.
91
+ - Child tool exclusions prevent ordinary nested orchestration through this package. They are **not** a security boundary against an agent executing arbitrary shell commands.
92
+ - Children use your installed agents' authentication and incur their normal provider usage. There is no package telemetry or credential upload.
93
+ - Concurrent children share the working directory unless you explicitly supply separate, trusted directories. There is no automatic Git worktree isolation.
94
+
95
+ Review tasks and trust settings before delegating destructive or sensitive work.
96
+
97
+ ## Limitations and troubleshooting
98
+
99
+ - A **Herdr client/server protocol mismatch** prevents agent startup. Save your work and restart Herdr yourself when safe. Stopping its server terminates pane processes; this extension never does that automatically.
100
+ - **Claude prompt not recorded** has been reported on some native startups. This beta assigns Claude a native session UUID before launch and never automatically resends an uncorrelated prompt. Check integrations, the child's terminal, and session metadata before retrying: missing evidence does not prove no work happened. The historical live failure still needs verification with a matching Herdr client/server.
101
+ - A closed native session cannot be reopened with `subagent_send`. A failed run whose session is still open can be continued on the same ID after inspecting state.
102
+ - Input typed directly into an already-settled native terminal is outside Pi's manager. Use `subagent_send` for managed follow-up.
103
+ - Native approval and planning UI changes can affect readiness detection. Only harness-specific correlated final markers count as completion.
104
+ - macOS is the initial native test platform. Other platforms need validation against their Herdr and agent CLI versions.
105
+
106
+ ## Development
107
+
108
+ ```sh
109
+ npm ci
110
+ npm run check
111
+ npm run format:check
112
+ npm test
113
+ npm pack --dry-run
114
+ ```
115
+
116
+ `npm test` is offline: it tests manager concurrency, cancellation, blocked-input wakeups, transcript correlation, trust boundaries, clean-profile loading, and the publish allowlist. `npm run check` supplies TypeScript static checking; formatting is handled by Prettier rather than a separate linter.
117
+
118
+ Optional headless integration tests use real providers and may incur charges:
119
+
120
+ ```sh
121
+ npm run test:live
122
+ ```
123
+
124
+ No compilation step or install/prepare script is required. Pi loads the TypeScript extension directly. Runtime dependency versions are pinned because Effect is currently a prerelease.
125
+
126
+ See [RELEASING.md](RELEASING.md) for publishing and the native smoke-test checklist, and [AUDIT.md](AUDIT.md) for the comparative reliability audit and regression fixes.
127
+
128
+ ## Attribution
129
+
130
+ Extracted from [Crush53/my-pi-setup](https://github.com/Crush53/my-pi-setup), based on Benjamin Davis's [my-pi-setup](https://github.com/davis7dotsh/my-pi-setup). The original MIT copyright and license are preserved in [LICENSE](LICENSE). Herdr integration and standalone packaging by Crush53.
package/RELEASING.md ADDED
@@ -0,0 +1,57 @@
1
+ # Releasing
2
+
3
+ ## Validate
4
+
5
+ ```sh
6
+ npm ci
7
+ npm run check
8
+ npm run format:check
9
+ npm test
10
+ npm pack --dry-run
11
+ ```
12
+
13
+ Inspect the tarball file list for credentials, transcripts, personal settings, and missing runtime helpers. Test the packed archive with a fresh Pi profile and production dependencies; loading successfully from a developer checkout alone is not sufficient.
14
+
15
+ ## Native smoke tests
16
+
17
+ Use a disposable Herdr test session, never stop a user's active server. Ensure the client/server protocols match and integrations are current. Record exact Pi, Herdr, Claude, and Codex versions.
18
+
19
+ - Start Pi, Claude, and Codex children with a short harmless prompt; require a correlated final response.
20
+ - Send a second prompt to the same ID and verify the new result rather than stale output.
21
+ - Test a long multiline Claude prompt, plus a screenshot path.
22
+ - Start a Claude planner; confirm blocked-input notification and early wait return. Answer a routine question, request a revision, and finish without allowing implementation.
23
+ - Cancel a running child and verify the native process stops.
24
+ - Exercise untrusted-directory rejection without approving any trust prompt.
25
+ - Run outside Herdr and verify headless backends.
26
+ - Close only test-owned tabs and stop only the disposable server.
27
+
28
+ Do not call a release stable while native startup failures remain unverified or reproducible. Keep prereleases on npm's `beta` tag and mark GitHub releases as prereleases.
29
+
30
+ ## Publish
31
+
32
+ Authenticate in your own terminal; never put tokens into repository files or prompts:
33
+
34
+ ```sh
35
+ npm login
36
+ npm whoami
37
+ ```
38
+
39
+ Set the next version in `package.json` and update the lockfile and README install examples. Commit reviewed changes, then:
40
+
41
+ ```sh
42
+ npm publish --access public --tag beta
43
+ # Only after publication succeeds:
44
+ git tag v0.1.0-beta.1
45
+ git push origin main --tags
46
+ gh release create v0.1.0-beta.1 --prerelease --generate-notes
47
+ ```
48
+
49
+ `prepublishOnly` reruns static checks, formatting, and offline tests. Publication may require npm's browser-based 2FA approval. Check `npm view pi-herdr-managed-agents@0.1.0-beta.1` before retrying an ambiguous publish failure: published versions cannot be overwritten.
50
+
51
+ For a GitHub-only beta, create the tag and prerelease without npm publishing and clearly label the npm release as pending.
52
+
53
+ ## Pi gallery
54
+
55
+ The manifest includes the `pi-package` keyword and an explicit `pi` resource manifest. The [Pi package gallery](https://pi.dev/packages) discovers tagged npm packages; indexing is not necessarily immediate. Do not claim a gallery listing until it is visible.
56
+
57
+ An optional public MP4 URL in `pi.video` or image URL in `pi.image` can add a gallery preview. Use a demo captured in a clean test workspace, not personal session screenshots.
@@ -0,0 +1,104 @@
1
+ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+
3
+ export const CHILD_TOOL_CALL_TIMEOUT_MS = 3 * 60 * 1_000;
4
+
5
+ interface ToolRegistry {
6
+ getAllTools(): Array<{ name: string }>;
7
+ getToolDefinition(name: string): ToolDefinition | undefined;
8
+ }
9
+
10
+ function formatTimeout(timeoutMs: number) {
11
+ if (timeoutMs % 60_000 === 0) {
12
+ const minutes = timeoutMs / 60_000;
13
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
14
+ }
15
+ if (timeoutMs % 1_000 === 0) {
16
+ const seconds = timeoutMs / 1_000;
17
+ return `${seconds} second${seconds === 1 ? "" : "s"}`;
18
+ }
19
+ return `${timeoutMs} ms`;
20
+ }
21
+
22
+ export class ToolCallTimeoutError extends Error {
23
+ constructor(toolName: string, timeoutMs: number) {
24
+ super(
25
+ `Tool call "${toolName}" timed out after ${formatTimeout(timeoutMs)}.`,
26
+ );
27
+ this.name = "ToolCallTimeoutError";
28
+ }
29
+ }
30
+
31
+ export async function runWithToolCallTimeout<T>(
32
+ toolName: string,
33
+ timeoutMs: number,
34
+ signal: AbortSignal | undefined,
35
+ execute: (signal: AbortSignal) => Promise<T>,
36
+ ) {
37
+ const timeoutController = new AbortController();
38
+ const executionSignal = signal
39
+ ? AbortSignal.any([signal, timeoutController.signal])
40
+ : timeoutController.signal;
41
+ const timeoutError = new ToolCallTimeoutError(toolName, timeoutMs);
42
+ let timer: ReturnType<typeof setTimeout> | undefined;
43
+ const timeout = new Promise<never>((_resolve, reject) => {
44
+ timer = setTimeout(() => {
45
+ reject(timeoutError);
46
+ timeoutController.abort(timeoutError);
47
+ }, timeoutMs);
48
+ });
49
+
50
+ let removeAbortListener: (() => void) | undefined;
51
+ const aborted = new Promise<never>((_resolve, reject) => {
52
+ if (!signal) return;
53
+ const onAbort = () => {
54
+ reject(
55
+ signal.reason instanceof Error
56
+ ? signal.reason
57
+ : new Error(`Tool call "${toolName}" was aborted.`),
58
+ );
59
+ };
60
+ if (signal.aborted) {
61
+ onAbort();
62
+ return;
63
+ }
64
+ signal.addEventListener("abort", onAbort, { once: true });
65
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
66
+ });
67
+
68
+ try {
69
+ return await Promise.race([execute(executionSignal), timeout, aborted]);
70
+ } finally {
71
+ if (timer) clearTimeout(timer);
72
+ removeAbortListener?.();
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Wrap every currently registered child tool with an independent execution
78
+ * timeout. Calling apply() again is safe and picks up tools registered later.
79
+ */
80
+ export function createToolCallTimeoutGuard(
81
+ timeoutMs = CHILD_TOOL_CALL_TIMEOUT_MS,
82
+ ) {
83
+ const wrapped = new WeakSet<ToolDefinition>();
84
+
85
+ const wrap = (definition: ToolDefinition) => {
86
+ if (wrapped.has(definition)) return;
87
+ wrapped.add(definition);
88
+
89
+ const execute = definition.execute;
90
+ definition.execute = async (toolCallId, params, signal, onUpdate, ctx) =>
91
+ runWithToolCallTimeout(definition.name, timeoutMs, signal, (signal) =>
92
+ execute.call(definition, toolCallId, params, signal, onUpdate, ctx),
93
+ );
94
+ };
95
+
96
+ return {
97
+ apply(session: ToolRegistry) {
98
+ for (const { name } of session.getAllTools()) {
99
+ const definition = session.getToolDefinition(name);
100
+ if (definition) wrap(definition);
101
+ }
102
+ },
103
+ };
104
+ }