@zachwill/pi-orchestrate 0.6.0 → 0.7.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Pi Orchestrate
2
2
 
3
- [`@zachwill/pi-orchestrate`](https://www.npmjs.com/package/@zachwill/pi-orchestrate) adds concurrent worker orchestration to [Pi](https://pi.dev). A parent agent can delegate bounded work to isolated child sessions, run independent tasks concurrently, and synthesize the results.
3
+ [`@zachwill/pi-orchestrate`](https://www.npmjs.com/package/@zachwill/pi-orchestrate) lets a Pi agent delegate work to separate child sessions, run independent tasks concurrently, and synthesize their results.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,126 +8,146 @@
8
8
  pi install npm:@zachwill/pi-orchestrate
9
9
  ```
10
10
 
11
- Pi packages run with your system permissions. Review this package and every worker definition you trust.
11
+ The package includes `scout`, `investigator`, `web`, and `worker` definitions. Pi packages and workers run with your system permissions, so review every package and worker definition you trust.
12
+
13
+ ## How it works
14
+
15
+ The current Pi session remains the parent and owns the task. Each `orchestrate` call starts a fresh worker session with its own transcript and a complete brief from the parent.
16
+
17
+ Workers have one of two lifecycles:
18
+
19
+ - `one-shot` workers return one result and stop automatically.
20
+ - `interactive` workers return a result and remain `ready` for follow-up work.
21
+
22
+ A **worker ID** identifies a worker session. A **run ID** identifies one generation within that session. Each interactive follow-up creates a new run while keeping the same worker ID.
12
23
 
13
24
  ## Tools
14
25
 
15
- Pi Orchestrate adds exactly five tools:
26
+ Pi Orchestrate adds exactly five tools.
16
27
 
17
- | Tool | Call | Purpose |
18
- | --- | --- | --- |
19
- | `orchestrate` | `orchestrate({ worker, title, instructions })` | Start one worker task |
20
- | `orchestration_status` | `orchestration_status({})` | Inspect the trusted catalog, diagnostics, runs, and worker states |
21
- | `interactive_send` | `interactive_send({ worker_id, instructions })` | Send a follow-up to a ready interactive worker |
22
- | `worker_abort` | `worker_abort({ worker_ids })` or `worker_abort({ all: true })` | Stop active owned work |
23
- | `interactive_close` | `interactive_close({ worker_id })` | Close a ready interactive worker |
28
+ ### `orchestrate`
24
29
 
25
- `title` is a label. `instructions` is the complete worker brief. Collapsed tool calls preview those instructions; expanded calls show them in full.
30
+ ```text
31
+ orchestrate({ worker, title, instructions })
32
+ ```
26
33
 
27
- ## Dispatch
34
+ Starts one worker. `title` is a short label; `instructions` is the complete, self-contained brief, including scope, constraints, success criteria, and expected output.
28
35
 
29
- Each `orchestrate` call validates its input, worker definition, and model before allocating IDs or starting a session. Calls are admitted independently: a rejected sibling does not block valid siblings. After admission, a startup or prompt failure settles only that worker as `failed`.
36
+ A sole call runs asynchronously. To run independent work concurrently, send the complete wave as sibling calls in one assistant response:
30
37
 
31
- Pi executes sibling tool calls concurrently. There is no extension-level sibling-group cap or hidden throttle. Before dispatching, enumerate the full wave. If an intended asynchronous wave has N workers, the next assistant response must contain exactly N separate, fully briefed `orchestrate` calls; one call is valid only when N=1. Form all N calls before emitting or finalizing the response because a successfully admitted sole async call returns `terminate: true` and ends the parent turn, so omitted siblings cannot be added afterward. Never emit one call and wait for its result before forming the rest of the wave.
38
+ ```text
39
+ orchestrate({
40
+ worker: "investigator",
41
+ title: "Trace configuration loading",
42
+ instructions: "Trace configuration loading from entry point to runtime. Read only. Return the relevant symbols, file paths, and a concise data-flow summary."
43
+ })
32
44
 
33
- When the host provides a parallel tool dispatcher, use it to submit the complete wave as one tool-call group. For example, with `multi_tool_use.parallel`, make one dispatcher call whose `tool_uses` contains exactly N `functions.orchestrate` entries and no other tools. Without a dispatcher, emit N native sibling `orchestrate` calls in one assistant response. In either form, the resulting expanded tool-call group must contain exactly those N orchestration calls and no other tool calls. Harmless response text does not affect runtime classification. A three-worker wave is one assistant response that submits all three `orchestrate({ worker, title, instructions })` calls together.
45
+ orchestrate({
46
+ worker: "investigator",
47
+ title: "Trace shutdown cleanup",
48
+ instructions: "Trace shutdown and cleanup behavior. Read only. Return the relevant symbols, lifecycle invariants, and uncovered edge cases."
49
+ })
50
+ ```
34
51
 
35
- Execution mode depends on the complete tool-call group:
52
+ A wave must contain all N intended `orchestrate` calls and no other tool calls. Form the whole wave before sending it; a successfully admitted asynchronous call ends the parent turn. When Pi provides a parallel tool dispatcher such as `multi_tool_use.parallel`, put all N `functions.orchestrate` entries in one dispatcher call. Otherwise, emit N native sibling calls.
36
53
 
37
- - Pi Orchestrate treats a successfully admitted sole `orchestrate` call as async.
38
- - Pi Orchestrate treats a successfully admitted pure group of sibling `orchestrate` calls as async; Pi executes the siblings concurrently.
39
- - Mixing `orchestrate` with any other tool makes the orchestration calls inline and blocking.
40
- - `interactive_send` is asynchronous only when it is the sole tool call in the message.
54
+ Pure orchestration groups run asynchronously, and their siblings execute concurrently. Mixing another tool into the group makes orchestration inline and blocking. Inline work follows cancellation of the parent turn; accepted asynchronous work continues independently.
41
55
 
42
- Inline work follows the parent turn's cancellation signal. Accepted asynchronous work detaches from that signal and continues independently.
56
+ Each call validates its input, worker definition, and model before starting. Sibling calls are admitted independently, so one rejection or worker failure does not roll back its peers. There is no extension-level sibling-group limit or hidden throttle.
43
57
 
44
- ## Results and ownership
58
+ Asynchronous results return to the exact parent session automatically. A sibling wave produces one final parent synthesis turn after every admitted worker settles. If that parent is busy or inactive, results wait for it; they are never delivered to another session.
45
59
 
46
- Asynchronous worker results enter the transcript individually. An ungrouped result starts a parent synthesis turn. Results from a sibling orchestration group share one final synthesis turn after every admitted member settles.
60
+ ### `worker_abort`
47
61
 
48
- All state and delivery are owner-scoped. If an owning session is busy or inactive, completed results queue until that exact session is active and idle again. They are never delivered to another session.
62
+ ```text
63
+ worker_abort({ worker_ids: ["worker-…"] })
64
+ worker_abort({ all: true })
65
+ ```
49
66
 
50
- `orchestration_status` is for diagnostics and recovery, not completion polling. It exposes bounded owner-scoped state without full task instructions or worker prompts.
67
+ Stops active workers owned by the current parent session. Explicit IDs are validated together. `{ all: true }` is a no-op when no owned workers are active and does not close interactive workers that are already `ready`.
51
68
 
52
- The bottom widget shows active work only. Completed, failed, aborted, and interactive ready workers disappear immediately. Inline work shows its current response in the live tool output while it blocks.
69
+ Completed one-shot workers need no cleanup. Abort active interactive work before closing its retained session.
53
70
 
54
- ## Lifecycle
71
+ ### `worker_status`
55
72
 
56
- A run represents one worker generation. A worker ID identifies its worker session. Completed one-shot IDs may remain in bounded diagnostics history, but their sessions have already terminated.
73
+ ```text
74
+ worker_status({})
75
+ ```
57
76
 
58
- - A **one-shot** worker is the default. It automatically terminates after settling and requires no cleanup.
59
- - An **interactive** worker is explicitly retained after a successful response as `ready`, keeping the same worker ID for follow-up work.
60
- - `interactive_send` starts a new run on that ready interactive worker.
61
- - `interactive_close` closes a ready interactive worker.
62
- - `worker_abort` stops active work only; `{ all: true }` does not close ready interactive workers.
77
+ Returns the trusted worker catalog, catalog diagnostics, and the current parent session's worker and run state. It omits worker prompts and full task instructions.
63
78
 
64
- Workers, runs, and queued delivery survive extension reloads and session switches within the same Pi process. Runtime shutdown releases retained interactive workers automatically; use `interactive_close` earlier only when their continuity is no longer needed.
79
+ Use it for diagnostics and recovery, not completion polling. Normal asynchronous results arrive automatically. The TUI widget separately shows active work, and the footer reports interactive workers that are ready.
65
80
 
66
- ## Parent contract
81
+ ### `interactive_close`
67
82
 
68
- Pi Orchestrate injects the authoritative orchestration contract and trusted catalog into the parent system prompt. The parent remains responsible for the task end to end:
83
+ ```text
84
+ interactive_close({ worker_id: "worker-…" })
85
+ ```
69
86
 
70
- 1. Keep trivial or tightly coupled work in the parent. For broad work, spin up as many workers as needed to cover every useful bounded independent scope and distinct validation perspective; never use a small default or the number of roles the user names. Named workers and counts are a floor unless the user explicitly states an exact cap; the same role can be instantiated for multiple scopes.
71
- 2. Give every worker a thorough, self-contained brief with the objective, paths and scope, context, success criteria, and expected output. Distinct validation perspectives can intentionally overlap, but avoid accidental duplicate work. State forbidden actions explicitly.
72
- 3. For an intended asynchronous wave of N workers, submit exactly N fully briefed `orchestrate` calls together and no other tool calls. Prefer one parallel dispatcher call containing N orchestration entries when a dispatcher such as `multi_tool_use.parallel` is available; otherwise emit N native siblings in one assistant response. Harmless response text does not affect runtime classification. A single call is valid only for N=1. Form the complete group before emitting it because a successfully admitted sole async call returns `terminate: true` and ends the turn; never emit one call and wait for its result before forming the rest.
73
- 4. As results expose new independent work, dispatch each full adaptive wave in parallel and continue until the whole task is complete.
74
- 5. Review evidence and changes, resolve conflicts, integrate deliberately, and verify the result.
75
- 6. Produce the final answer from the parent session.
87
+ Closes an owned interactive worker whose status is `ready`. It releases the retained session; it cannot close active work or a one-shot worker.
76
88
 
77
- Workers provide bounded evidence or changes. They do not replace parent judgment.
89
+ ### `interactive_send`
78
90
 
79
- ## Worker catalog
91
+ ```text
92
+ interactive_send({
93
+ worker_id: "worker-…",
94
+ instructions: "Verify the first risk against the tests and cite the relevant cases."
95
+ })
96
+ ```
80
97
 
81
- Definitions are loaded by name in this precedence order, from lowest to highest:
98
+ Starts a follow-up run on an owned interactive worker whose status is `ready`. The worker keeps its ID and prior session context. Send `interactive_send` as the only tool call in the assistant message to run it asynchronously; sibling tool calls make it inline and blocking.
82
99
 
83
- 1. Package fallbacks in [`examples/workers/`](examples/workers/)
84
- 2. User definitions in `~/.pi/agent/pi-orchestrate/workers/*.md`
85
- 3. Project definitions in `<project>/.pi/pi-orchestrate/workers/*.md`, only after Pi trusts the project
100
+ Interactive workers remain available across extension reloads and session switches within the same Pi process. Close them when their continuity is no longer useful. Runtime shutdown releases retained sessions automatically.
101
+
102
+ ## Parent responsibilities
103
+
104
+ The parent agent still owns the result:
105
+
106
+ - Delegate bounded, independent scopes and materially distinct validation perspectives. Keep trivial or tightly coupled work in the parent.
107
+ - Use as many workers as the task supports instead of a small fixed count. User-named roles and counts are a floor unless the user sets an exact cap.
108
+ - Give each worker a complete brief and non-overlapping write scope. Intentional overlap should serve a distinct review perspective.
109
+ - Dispatch each full wave together. As findings expose new independent work, dispatch another full wave.
110
+ - Review the evidence and changes, resolve conflicts, verify the integrated result, and answer from the parent session.
86
111
 
87
- A higher-precedence definition replaces a lower one with the same `name`. Pi performs no project-worker discovery for an untrusted project.
112
+ ## Configure workers
88
113
 
89
- The package includes `scout`, `investigator`, `web`, and `worker` fallbacks. `scout`, `investigator`, and `worker` omit `model`, so they inherit the parent's active model at dispatch. `web` uses an installed, authenticated Codex CLI for public-web research and pins its Pi session and searches to `gpt-5.6-sol`. To customize one, copy its definition to the user or project directory and keep the same filename and `name`. Add an explicit model only when that worker needs one.
114
+ Worker definitions are loaded by name in this precedence order:
90
115
 
91
- A catalog definition is dispatch configuration, not a retained session. The same definition can be dispatched repeatedly; each one-shot dispatch creates a fresh session that terminates automatically without cleanup.
116
+ 1. Package fallbacks in [`examples/workers/`](examples/workers/)
117
+ 2. User definitions in `~/.pi/agent/pi-orchestrate/workers/*.md`
118
+ 3. Project definitions in `<project>/.pi/pi-orchestrate/workers/*.md`, when Pi trusts the project
119
+
120
+ A later definition replaces an earlier definition with the same name. Untrusted projects contribute no project definitions.
92
121
 
93
- ## Worker definitions
122
+ The bundled `scout`, `investigator`, and `worker` inherit the parent's active model. The bundled `web` worker requires an installed, authenticated Codex CLI and uses `openai-codex/gpt-5.6-sol`. Copy a fallback into a user or project directory to customize it.
94
123
 
95
- A worker is a regular Markdown file whose basename matches its `name`:
124
+ A definition is a Markdown file whose basename matches its `name`:
96
125
 
97
126
  ```md
98
127
  ---
99
128
  name: reviewer
100
- description: Reviews a bounded change and returns evidence.
101
- tools: read, grep, find, ls, bash
129
+ description: Reviews a bounded area and answers follow-up questions.
130
+ tools: read, grep, find, ls
102
131
  lifecycle: interactive
103
132
  ---
104
133
 
105
- Inspect the assigned scope and return concise findings with file paths.
134
+ Inspect only the assigned scope. Do not modify files. Return concise findings with file paths.
106
135
  ```
107
136
 
108
- | Field | Rule |
109
- | --- | --- |
110
- | `name` | Required; must match the filename |
111
- | `description` | Required; used by the parent to choose a worker |
112
- | `tools` | Required, nonempty list using `read`, `bash`, `edit`, `write`, `grep`, `find`, or `ls` |
113
- | `lifecycle` | Required; exactly `one-shot` or `interactive` |
114
- | `model` | Optional `provider/model`; omitted inherits the parent model |
115
- | `thinking` | Optional Pi thinking level |
116
- | `skills` | Optional; omitted uses normal discovery, a list is an exact allowlist, and `[]` disables skills |
117
- | `compaction` | Optional worker compaction settings |
137
+ Required fields are `name`, `description`, a nonempty `tools` list, and `lifecycle` (`one-shot` or `interactive`). The Markdown body is the worker's nonempty system prompt.
118
138
 
119
- The Markdown body is the worker system prompt and must be nonempty. Unknown fields, invalid values, symlinks, and filename/name mismatches invalidate a definition.
139
+ Optional fields are `model`, `thinking`, `skills`, and `compaction`. An omitted `model` inherits the parent's model. For `skills`, omission uses normal discovery, a list is an exact allowlist, and `[]` disables skills.
120
140
 
121
- Grant the smallest useful tool set. A read-only prompt is not enforcement when the worker has tools that can write.
141
+ Definitions are strict, regular non-symlink `.md` files up to 64 KiB. Supported Pi tools are `read`, `bash`, `edit`, `write`, `grep`, `find`, and `ls`. Grant the smallest useful set: a read-only prompt does not prevent writes when the worker has tools with write authority.
122
142
 
123
- ## Trust and isolation
143
+ A definition is reusable dispatch configuration, not a retained session. Each `orchestrate` call starts a new worker session; only `interactive_send` continues an existing one.
124
144
 
125
- Workers receive fresh durable Pi session lineage without the parent's conversation. They still run in the parent process and are not security sandboxes: they share your filesystem and environment permissions.
145
+ ## Trust and isolation
126
146
 
127
- Workers use normal global Pi settings, authentication, packages, extensions, skills, and context. A trusted project may also contribute project-scoped definitions, settings, extensions, skills, and context. An untrusted project contributes none of those project-scoped resources; global resources remain available.
147
+ Workers receive fresh durable Pi child-session lineage without the parent's conversation. They run in the parent process and are not security sandboxes: they share its filesystem and environment permissions.
128
148
 
129
- Other configured extensions, including provider integrations such as `@benvargas/pi-claude-code-use`, load normally in worker sessions. Pi Orchestrate excludes itself, so workers remain direct Pi children. The injected boundary forbids recursive Pi Orchestrate delegation and descendant Pi worker sessions.
149
+ Workers use global Pi settings, authentication, packages, extensions, skills, and context. Trusted projects may add project-scoped resources. Untrusted projects do not contribute project workers, settings, extensions, skills, or context; global resources remain available.
130
150
 
131
- The worker definition controls the Pi tool allowlist, not operating-system authority. A trusted worker with `bash` can launch explicitly required external processes, including agent CLIs.
151
+ A definition's `tools` field controls Pi's tool allowlist, not operating-system authority. A worker with `bash` can start external processes, including agent CLIs. Give concurrent workers separate write scopes and inspect their changes in the parent.
132
152
 
133
- Pi Orchestrate performs no automatic filesystem writes. Workers write only through their granted tools and instructions. Give concurrent workers non-overlapping write scopes, then inspect and verify their changes in the parent.
153
+ Pi Orchestrate excludes itself from child sessions and instructs workers not to create descendant Pi worker sessions. Workers remain direct Pi children.
@@ -41,11 +41,11 @@ You are the parent orchestrator and own the task end to end.
41
41
  - Input, catalog, and model preflight is atomic per call before that worker starts. Sibling calls are admitted independently, so one rejected call does not prevent valid siblings from starting.
42
42
  - Pi Orchestrate treats a successfully admitted sole \`orchestrate\` call or pure sibling group as async. Pi executes native sibling tools concurrently. A pure group yields the parent turn, delivers each result as it settles, and starts synthesis only after the whole group settles. Mixing \`orchestrate\` with another tool makes it inline and blocking. \`interactive_send\` is asynchronous only as the sole tool call in its assistant message.
43
43
  - Exact worker instructions remain visible in the tool call and can be expanded; titles are labels, not substitutes for complete messages.
44
- - After the full current wave has been dispatched, yield the parent turn once its admissions have resolved; a rejected sibling does not block yielding. Worker responses arrive individually as each worker settles, and the final response starts parent synthesis. Do not poll \`orchestration_status\` or use it as a normal completion mechanism.
44
+ - After the full current wave has been dispatched, yield the parent turn once its admissions have resolved; a rejected sibling does not block yielding. Worker responses arrive individually as each worker settles, and the final response starts parent synthesis. Do not poll \`worker_status\` or use it as a normal completion mechanism.
45
45
  - As results expose more useful independent scopes or materially distinct perspectives, enumerate and dispatch another full parallel wave before yielding. Continue adaptive full waves until the whole task is complete.
46
46
  - The parent synthesizes worker results, reviews their evidence and changes, resolves conflicts, integrates the final result, and runs the relevant verification before declaring completion.
47
47
  - Prefer one-shot workers. Use \`interactive_send\` only for follow-up work on an owned lifecycle interactive worker whose status is ready, and \`interactive_close\` only when that ready interactive worker is finished. Never use either tool for one-shot or completed workers because one-shot sessions terminate automatically. Use \`worker_abort\` only when active work must stop.
48
- - The public tools are \`orchestrate\`, \`orchestration_status\`, \`interactive_send\`, \`worker_abort\`, and \`interactive_close\`.
48
+ - The public tools are \`orchestrate\`, \`worker_status\`, \`interactive_send\`, \`worker_abort\`, and \`interactive_close\`.
49
49
 
50
50
  ### Trusted worker catalog
51
51
 
@@ -286,9 +286,6 @@ function truncateUtf8(content: string, byteLimit: number): string {
286
286
  }
287
287
 
288
288
  function renderDisposition(settlement: WorkerSettlement): string | undefined {
289
- if (settlement.status === "completed" && settlement.lifecycle === "one-shot") {
290
- return "one-shot session ended automatically; no close needed";
291
- }
292
289
  if (settlement.status === "ready" && settlement.lifecycle === "interactive") {
293
290
  return "interactive session retained; use `interactive_send` or `interactive_close`";
294
291
  }
@@ -332,7 +332,6 @@ function resultQualifier(result: SafeSettlement): string | undefined {
332
332
  }
333
333
  if (result.status === "failed") return "failed";
334
334
  if (result.status === "ready") return "interactive ready";
335
- if (result.status === "completed") return "one-shot ended";
336
335
  return undefined;
337
336
  }
338
337
 
@@ -175,17 +175,17 @@ export function registerOrchestrationTools(
175
175
  });
176
176
 
177
177
  pi.registerTool({
178
- name: "orchestration_status",
179
- label: "Orchestration Status",
178
+ name: "worker_status",
179
+ label: "Worker Status",
180
180
  description:
181
181
  "Diagnostics and recovery only: inspect trusted catalog entries, catalog diagnostics, and this session's runtime state. Never poll for completion.",
182
- promptSnippet: "Inspect owned orchestration state for diagnostics or recovery",
182
+ promptSnippet: "Inspect owned worker state for diagnostics or recovery",
183
183
  promptGuidelines: [
184
- "Use orchestration_status only for diagnostics or recovery; never poll it for completion.",
184
+ "Use worker_status only for diagnostics or recovery; never poll it for completion.",
185
185
  ],
186
186
  parameters: statusSchema,
187
187
  renderCall(_args, theme) {
188
- return new Text(theme.fg("toolTitle", theme.bold("orchestration_status")), 0, 0);
188
+ return new Text(theme.fg("toolTitle", theme.bold("worker_status")), 0, 0);
189
189
  },
190
190
  renderResult(result, { isPartial }, theme) {
191
191
  return renderDiagnosticsResult(result, isPartial, theme);
@@ -204,7 +204,7 @@ export function registerOrchestrationTools(
204
204
  content: [
205
205
  {
206
206
  type: "text",
207
- text: readableDetails("Orchestration diagnostics and recovery snapshot.", readable),
207
+ text: readableDetails("Worker diagnostics and recovery snapshot.", readable),
208
208
  },
209
209
  ],
210
210
  details: readable,
@@ -837,7 +837,7 @@ function formatElapsed(milliseconds: number): string {
837
837
  }
838
838
 
839
839
  function renderDiagnosticsResult(result: AgentToolResult<unknown>, isPartial: boolean, theme: Theme): Text {
840
- if (isPartial) return new Text(theme.fg("muted", "Reading orchestration diagnostics…"), 0, 0);
840
+ if (isPartial) return new Text(theme.fg("muted", "Reading worker diagnostics…"), 0, 0);
841
841
  const details = result.details;
842
842
  if (isRecord(details) && isRecord(details.state) && Array.isArray(details.state.workers)) {
843
843
  const workers = details.state.workers.filter(isRecord);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zachwill/pi-orchestrate",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Concurrent worker orchestration for Pi",
6
6
  "files": ["extension/", "examples/", "README.md", "LICENSE"],