@zachwill/pi-orchestrate 0.7.0 → 0.7.2
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 +94 -74
- package/extension/catalog.ts +4 -2
- package/extension/delivery.ts +0 -3
- package/extension/presentation.ts +0 -1
- package/package.json +1 -1
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)
|
|
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
|
|
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
|
-
|
|
18
|
-
| --- | --- | --- |
|
|
19
|
-
| `orchestrate` | `orchestrate({ worker, title, instructions })` | Start one worker task |
|
|
20
|
-
| `worker_status` | `worker_status({})` | Inspect the aggregate worker-system diagnostics and recovery snapshot |
|
|
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
|
-
|
|
30
|
+
```text
|
|
31
|
+
orchestrate({ worker, title, instructions })
|
|
32
|
+
```
|
|
26
33
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
### `worker_abort`
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
```text
|
|
63
|
+
worker_abort({ worker_ids: ["worker-…"] })
|
|
64
|
+
worker_abort({ all: true })
|
|
65
|
+
```
|
|
49
66
|
|
|
50
|
-
|
|
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
|
-
|
|
69
|
+
Completed one-shot workers need no cleanup. Abort active interactive work before closing its retained session.
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
### `worker_status`
|
|
55
72
|
|
|
56
|
-
|
|
73
|
+
```text
|
|
74
|
+
worker_status({})
|
|
75
|
+
```
|
|
57
76
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
### `interactive_close`
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
```text
|
|
84
|
+
interactive_close({ worker_id: "worker-…" })
|
|
85
|
+
```
|
|
69
86
|
|
|
70
|
-
|
|
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
|
-
|
|
89
|
+
### `interactive_send`
|
|
78
90
|
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
112
|
+
## Configure workers
|
|
88
113
|
|
|
89
|
-
|
|
114
|
+
Worker definitions are loaded by name in this precedence order:
|
|
90
115
|
|
|
91
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
101
|
-
tools: read, grep, find, ls
|
|
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
|
|
134
|
+
Inspect only the assigned scope. Do not modify files. Return concise findings with file paths.
|
|
106
135
|
```
|
|
107
136
|
|
|
108
|
-
|
|
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`, and a nonempty `tools` list. The Markdown body is the worker's nonempty system prompt.
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
`lifecycle` is optional, accepts `one-shot` or `interactive`, and defaults to `one-shot`. Other 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
|
|
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
|
-
|
|
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
|
-
|
|
145
|
+
## Trust and isolation
|
|
126
146
|
|
|
127
|
-
Workers
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
153
|
+
Pi Orchestrate excludes itself from child sessions and instructs workers not to create descendant Pi worker sessions. Workers remain direct Pi children.
|
package/extension/catalog.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
7
7
|
import { basename, extname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { Result, Schema, SchemaGetter, type SchemaIssue } from "effect";
|
|
9
|
+
import { Effect, Result, Schema, SchemaGetter, type SchemaIssue } from "effect";
|
|
10
10
|
import type {
|
|
11
11
|
CatalogDiagnostic,
|
|
12
12
|
WorkerCatalog,
|
|
@@ -76,7 +76,9 @@ const WorkerFrontmatter = Schema.Struct({
|
|
|
76
76
|
tools: commaList(Schema.Literals(SUPPORTED_TOOL_NAMES)),
|
|
77
77
|
skills: Schema.optionalKey(commaList(Schema.NonEmptyString, true)),
|
|
78
78
|
compaction: Schema.optionalKey(Compaction),
|
|
79
|
-
lifecycle: Schema.Literals(["one-shot", "interactive"])
|
|
79
|
+
lifecycle: Schema.Literals(["one-shot", "interactive"]).pipe(
|
|
80
|
+
Schema.withDecodingDefaultKey(Effect.succeed("one-shot")),
|
|
81
|
+
),
|
|
80
82
|
});
|
|
81
83
|
const decodeWorkerFrontmatter = Schema.decodeUnknownResult(WorkerFrontmatter, {
|
|
82
84
|
errors: "all",
|
package/extension/delivery.ts
CHANGED
|
@@ -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
|
|