@zachwill/pi-orchestrate 0.16.0 → 0.17.0

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,18 +1,26 @@
1
1
  # Pi Orchestrate
2
2
 
3
- [`@zachwill/pi-orchestrate`](https://www.npmjs.com/package/@zachwill/pi-orchestrate) lets a Pi session delegate work to direct child sessions. It requires Pi 0.85.0 or newer.
3
+ [`@zachwill/pi-orchestrate`](https://www.npmjs.com/package/@zachwill/pi-orchestrate) lets a Pi session delegate work to direct child sessions.
4
4
 
5
5
  - Each worker gets a focused brief and a separate conversation.
6
6
  - Workers run independently and return their results to the parent.
7
7
  - Only the parent can delegate; workers cannot create more workers.
8
8
 
9
+ Requires Pi 0.85.0 or newer.
10
+
11
+ ```bash
12
+ pi install npm:@zachwill/pi-orchestrate
13
+ ```
14
+
9
15
  ## The model
10
16
 
11
17
  Each `orchestrate` dispatch creates a fresh worker session with its own transcript. The worker receives a complete brief from the parent but not the parent's conversation.
12
18
 
13
- A worker definition is reusable configuration: it selects the worker's prompt, tools, lifecycle, and optional model settings. It is not a running or retained session.
19
+ A worker definition is reusable configuration for the worker's prompt, tools, lifecycle, and optional model settings. The parent remains responsible for the user's complete requested outcome across its own and worker work.
14
20
 
15
- Independent workers dispatched together run concurrently. Their results return only to the parent session that started them, and the parent synthesizes the group after every worker finishes. A rejected or failed worker does not cancel its peers. Orchestration dispatched alongside unrelated tool calls runs inline instead of in the background.
21
+ Workers run in the background. `orchestrate` and `interactive_send` return acceptance so the parent can continue useful independent work, including tool calls dispatched alongside the workers. Worker dispatches in the same response form one result group; dispatches in later responses form separate groups.
22
+
23
+ Results return only to the parent session that started the workers. Results that settle while the parent is busy are queued. After the parent run ends normally, individual results can enter its context as they arrive; a dispatch group resumes the parent after every admitted member settles. The parent can do useful independent work before ending its run, or end promptly when progress needs worker evidence. A rejected, failed, or aborted worker does not cancel its peers; stopping other active workers requires an explicit `worker_abort` request.
16
24
 
17
25
  Workers have one of two lifecycles:
18
26
 
@@ -23,13 +31,7 @@ A **worker ID** identifies a worker session. A **run ID** identifies one generat
23
31
 
24
32
  Interactive workers remain available across session switches and extension reloads within the same Pi process. Closing one releases its retained session; process shutdown releases any that remain.
25
33
 
26
- ## Compaction
27
-
28
- Parent compaction does not stop workers. Results that settle during compaction remain queued for the owning session and resume automatic delivery when that session becomes idle, including after failed or cancelled compaction. Grouped work still triggers synthesis only after the group settles.
29
-
30
- Before each parent model request, the extension adds a fresh, owner-scoped snapshot of active workers and ready interactive sessions. This transient context survives compaction by being rebuilt from live state; it is not appended to the transcript. The snapshot is capped at 12 KiB and reports omitted workers or truncated assignments, with `worker_status` available for recovery rather than polling.
31
-
32
- This state belongs to the running Pi process. Compaction does not require restarting workers, and persisted transcripts do not restore running workers after a process restart.
34
+ Workers keep running when the parent conversation is compacted, and their results return automatically. Restarting Pi does not restore running workers.
33
35
 
34
36
  ## Agent interface
35
37
 
@@ -43,11 +45,11 @@ Pi Orchestrate gives the parent five model-facing tools:
43
45
  | `worker_abort` | Stop active workers owned by the parent |
44
46
  | `worker_status` | Inspect the trusted catalog and diagnose the parent's worker state |
45
47
 
46
- The extension supplies the parent with the exact dispatch and lifecycle rules for these tools. The README describes their behavior rather than duplicating those model instructions.
48
+ The extension supplies the parent with instructions for using these tools.
47
49
 
48
50
  ## Worker definitions
49
51
 
50
- The package includes four fallback definitions in [`examples/workers/`](examples/workers/): `scout` for small factual probes, `investigator` for read-only cross-file research, `worker` for bounded implementation, and `web` for public web research. The first three inherit the parent's active model. The `web` worker uses the model declared in its definition and requires an installed, authenticated Codex CLI.
52
+ The package includes four fallback definitions in [`examples/workers/`](examples/workers/): `scout` for small factual probes, `investigator` for read-only cross-file research, `worker` for bounded implementation, and `web` for public web research. The fallbacks use `openai-codex/gpt-6-sol`, except `scout`, which uses `openai-codex/gpt-6-luna`. The `web` worker also requires an installed, authenticated Codex CLI.
51
53
 
52
54
  Definitions are loaded by name in this precedence order:
53
55
 
@@ -85,8 +87,6 @@ The frontmatter is strict:
85
87
 
86
88
  The Markdown body is the worker's nonempty system prompt. `tools` and `skills` accept either YAML arrays or comma-separated strings. Supported Pi tools are `read`, `bash`, `edit`, `write`, `grep`, `find`, and `ls`. Unknown fields and malformed definitions are rejected and appear in catalog diagnostics.
87
89
 
88
- Each `orchestrate` call starts a new worker session from the selected definition. Only `interactive_send` continues an existing session.
89
-
90
90
  ## Trust boundary
91
91
 
92
92
  Workers run in the parent process and are not security sandboxes. They share its filesystem and environment permissions.
@@ -95,6 +95,4 @@ Workers can use global Pi settings, authentication, packages, extensions, skills
95
95
 
96
96
  A definition's `tools` field controls Pi's tool allowlist, not operating-system authority. A worker with `bash` can start external processes, including other agent CLIs. A read-only prompt also does not prevent writes when the worker has a write-capable tool.
97
97
 
98
- Concurrent workers share the same working tree, so overlapping write scopes can collide. The parent owns the outcome, works directly, and delegates independent parts when doing so improves speed or quality. The parent integrates worker results and checks the evidence behind consequential claims or changes, adding independent review when a specific risk warrants it.
99
-
100
- Pi Orchestrate excludes itself from child sessions and keeps workers as direct Pi children.
98
+ Concurrent workers share the same working tree, so overlapping write scopes can collide.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: investigator
3
3
  description: Investigates cross-file questions through read-only inspection and evidence-based synthesis.
4
- thinking: medium
4
+ model: openai-codex/gpt-6-sol
5
+ thinking: high
5
6
  tools: read, grep, find, ls, bash
6
7
  lifecycle: one-shot
7
8
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: scout
3
3
  description: Answers a small factual repository question through shallow, read-only inspection.
4
+ model: openai-codex/gpt-6-luna
4
5
  thinking: low
5
6
  tools: read, grep, find, ls, bash
6
7
  lifecycle: one-shot
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: web
3
3
  description: Researches the public web with one or more Codex searches and returns a source-grounded synthesis.
4
- model: openai-codex/gpt-5.6-sol
4
+ model: openai-codex/gpt-6-sol
5
5
  thinking: medium
6
6
  tools: bash
7
7
  skills: []
@@ -31,7 +31,7 @@ stderr_log="$work_dir/stderr.log"
31
31
 
32
32
  codex exec - \
33
33
  --ignore-user-config \
34
- --model gpt-5.6-sol \
34
+ --model gpt-6-sol \
35
35
  -c 'model_reasoning_effort="medium"' \
36
36
  -c 'web_search="cached"' \
37
37
  --ephemeral \
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: worker
3
3
  description: Implements bounded code changes, fixes, and refactors.
4
+ model: openai-codex/gpt-6-sol
4
5
  thinking: medium
5
6
  tools: read, bash, edit, write, grep, find, ls
6
7
  lifecycle: one-shot
@@ -84,7 +84,7 @@ export function createOrchestrationExtension(
84
84
  orchestration: host.orchestration,
85
85
  getCatalog: catalogFor,
86
86
  getDispatchDecision: (toolCallId) =>
87
- dispatchDecisions.get(toolCallId) ?? { mode: "inline" },
87
+ dispatchDecisions.get(toolCallId) ?? {},
88
88
  });
89
89
  hostAttachment ??= attachProcessHost(host);
90
90
 
@@ -376,14 +376,13 @@ class OrchestrationEngine implements OrchestrationService {
376
376
  const runId = this.idFactories.runId();
377
377
  const completion = yield* Deferred.make<CompletedRun>();
378
378
  const now = this.clock.currentTimeMillisUnsafe();
379
- const runRecord: RunRecord = {
380
- id: runId,
381
- ownerSessionId: context.ownerSessionId,
382
- workerId: validatedWorkerId,
379
+ const runRecord = makeRunRecord(
380
+ runId,
381
+ validatedWorkerId,
382
+ context,
383
383
  mode,
384
- state: "running",
385
- createdAt: now,
386
- };
384
+ now,
385
+ );
387
386
 
388
387
  const admission = this.transact((draft) => {
389
388
  const ready = readyInteractiveDecision(
@@ -96,44 +96,35 @@ function buildContract(catalog: WorkerCatalog): string {
96
96
  return `${CONTRACT_START}
97
97
  ## Pi Orchestrate Contract
98
98
 
99
- You are responsible for delivering the user’s requested outcome. Work directly and use workers where parallel ownership or specialized judgment materially helps. Own the difficult decisions, shared problems, and final answer.
99
+ You own the user’s outcome across parent and worker work. Exercise judgment: use workers when they help, continue useful work yourself, and deliver one coherent answer.
100
100
 
101
- ### Scope
101
+ ### Outcome and scope
102
102
 
103
- - Scope comes from the user’s request and applicable instructions. Preserve explicitly broad tasks, but do not broaden narrow tasks because execution reveals related work.
104
- - Understand the requested outcome and take the next concrete step. Keep planning proportional to dependencies and risk; do not require a written scope statement, roster, or approval checkpoint unless it resolves a real ambiguity.
105
- - Necessary investigation and implementation details belong to the task. Separate optional improvements from the requested work; ask before making consequential changes beyond it.
106
- - Admit newly discovered work only when the current change would otherwise be incorrect, unsafe, nonfunctional, or unverifiable. If that work exceeds the boundary, narrow, revert, or ask the user rather than silently expanding.
107
- - Do not introduce cross-feature policy, infrastructure, deployment, or compatibility work unless the request or an unavoidable requirement of the current change calls for it.
108
- - Completed worker effort does not justify retaining an overgrown change set.
103
+ - Preserve the requested scope, completeness, and form. Do not silently substitute an easier deliverable, narrow a broad request to a sample, or treat checks against a selected subset as evidence that the full requirement is satisfied.
104
+ - Do the investigation and implementation needed for the outcome, but do not invent adjacent deliverables, policies, or cleanup. Ask when a material ambiguity or consequential out-of-scope change requires the user’s authority.
105
+ - Assign write ownership so concurrent scopes are disjoint. Give one owner any shared file or integration point, and preserve changes you do not own.
109
106
 
110
107
  ### Delegation
111
108
 
112
- - Delegate substantive, independent parts of the problem when doing so improves speed or quality. Prefer end-to-end assignments: each worker investigates what its question requires, does the work, and checks its result. The parent should solve useful parts of the problem directly rather than defaulting to a coordination-only role.
113
- - Respect the user’s requested workers and counts. Otherwise choose the smallest team that usefully advances the outcome. Do not add roles merely because preparation, implementation, and review can be separated.
114
- - Each \`orchestrate\` call creates a fresh worker session. The same worker definition and identical brief may be used for independent judgments; do not vary briefs merely to make them appear different. Interactive follow-up continues one worker ID with its existing context.
115
- - Give every worker a self-contained brief with its objective, context, owned paths, forbidden changes, success criteria, expected output, and stop condition. Instruct workers to report adjacent findings without fixing them. Workers do not receive the parent conversation.
109
+ - Delegate separable work when the expected improvement in quality or latency is worth the coordination cost. Respect worker choices and counts requested by the user; otherwise choose from the work rather than applying a minimum, maximum, or mandatory role pattern.
110
+ - Give each worker enough context to own one outcome and scope boundary, including relevant constraints, owned paths, and consequential evidence checks. Workers do not receive the parent conversation. Have them report out-of-scope findings instead of fixing them.
111
+ - **Important:** When independent worker assignments are ready, dispatch them using one \`multi_tool_use.parallel\` call containing every \`functions.orchestrate\` call. Do not dispatch one worker first and batch the rest afterward.
112
+ - Each \`orchestrate\` call creates a fresh worker session. Interactive follow-up uses \`interactive_send\` with the existing worker ID and context; close a ready interactive worker with \`interactive_close\` when it is no longer needed.
116
113
 
117
- ### Parallel dispatch
114
+ ### Dispatch and dependencies
118
115
 
119
- - Form the complete wave before emitting any tool call. “Complete” means every worker admitted for the current change and turn, not every potentially useful concern.
120
- - For one worker, make one fully briefed \`orchestrate\` call. For N workers where N > 1, make exactly one \`multi_tool_use.parallel\` call containing exactly N \`functions.orchestrate\` entries and no other tools.
121
- - If \`multi_tool_use.parallel\` is unavailable, emit all N \`orchestrate\` calls as native siblings in one assistant response.
122
- - Never split a multi-worker wave across assistant responses; an admitted sole asynchronous \`orchestrate\` call ends the parent turn.
123
- - The expanded tool-call group must contain only the intended \`orchestrate\` calls. Mixing another tool into the group makes orchestration inline and blocking.
116
+ - \`orchestrate\` and \`interactive_send\` start background work and return acceptance without ending the parent run, including alongside other tools. Worker dispatches in the same response are grouped for result delivery; dispatches in later responses form separate groups.
117
+ - After dispatch, continue useful independent work. When that work is exhausted or progress needs worker evidence, end the run normally so automatic result delivery can resume you. A brief truthful pending-status response is acceptable; do not claim final completion before necessary results are considered.
118
+ - Do not poll \`worker_status\`, sleep, duplicate active assignments, or invent work while results are pending. Do not force parent work when none is useful, and do not stop before doing independent work that materially advances the outcome.
119
+ - Results that settle while the parent is busy are queued and delivered when the parent run ends. Do not redispatch queued work.
120
+ - Compaction does not stop workers. Use the fresh live-worker snapshot for active assignments and ready interactive sessions rather than stale conversation summaries. Do not redispatch work because its dispatch was compacted away. Use \`worker_status\` once only for diagnostics or recovery when the snapshot overflows or state appears inconsistent.
124
121
 
125
122
  ### Completion
126
123
 
127
- - After dispatching, wait for automatic result delivery instead of polling \`worker_status\`. Do not call \`sleep\`, poll with another tool, inspect progress indirectly, or issue no-op calls.
128
- - Compaction does not stop workers. A fresh live-worker context snapshot identifies active assignments and ready interactive sessions; use it rather than stale status in conversation summaries. Do not redispatch work because its dispatch was compacted away. If the snapshot is truncated or state appears inconsistent, use \`worker_status\` once for recovery, not polling.
129
- - While waiting, perform only already-admitted independent work from the current change; otherwise end the turn.
130
- - Classify findings before acting: fix or remove defects introduced by the current change, complete unfinished requirements inside its boundary, and record adjacent or pre-existing concerns without admitting them.
131
- - Dispatch another wave only for admitted work inside the current change. Independence, local correctness, reviewer concern, or consistency alone does not justify more work.
132
- - Inspect worker results and check the evidence behind consequential claims or changes. Add independent review when a specific risk warrants it, not as an automatic phase. Reuse credible verification already performed; investigate gaps and contradictions.
133
- - Inspect the combined result and worker evidence, resolve disagreements, and accept, reduce, or discard the change. Do not personally repeat delegated review or verification without a concrete reason.
134
- - Verification decides whether to accept the change; it is not a general source of new work. Fix failures caused by the change, but narrow, revert, report, or ask when verification demands unrelated work.
135
- - Stop when the acceptance criteria pass. Report delivered work separately from findings deliberately left outside scope.
136
- - Prefer one-shot workers. Use interactive workers only when retained context is useful and follow the lifecycle requirements in the tool descriptions.
124
+ - Treat worker reports as input, not the answer. Resolve material conflicts and assess the combined result against the original request; a worker’s local success does not redefine completion.
125
+ - Check consequential claims, changes, and failure modes with evidence suited to the task. Add review or integration tests only when a concrete risk warrants them, and do not repeat credible worker checks without a reason.
126
+ - Do not give the final answer until every worker result necessary to the outcome has been delivered and considered. Report completed work and any unresolved or out-of-scope finding directly.
127
+ - Use \`worker_abort\` only to stop active owned work. Ending the parent run, compaction, and closing a ready interactive session do not cancel other workers.
137
128
 
138
129
  ### Trusted worker catalog
139
130
 
@@ -7,7 +7,7 @@ export const MAX_WORKER_DELIVERY_MARKDOWN_BYTES = 16 * 1024;
7
7
  export const DELIVERY_TRUNCATION_MARKER =
8
8
  "\n\n[Worker result truncated for parent context. Full output remains in structured details.]";
9
9
  export const DELIVERY_PARENT_INSTRUCTIONS =
10
- "Parent: Synthesize all results, resolve conflicts, review changes and evidence, run integration checks, and continue the user's task. Do not merely forward worker reports.";
10
+ "Parent: Use these results as input to the user's task. Resolve material conflicts, check consequential evidence, and complete the requested outcome; do not merely forward worker reports or reopen work that is already satisfied.";
11
11
 
12
12
  export type ParentBindingGeneration = string | number | symbol;
13
13
  export type ScheduleIdleRecheck = (recheck: () => void) => () => void;
@@ -6,7 +6,6 @@ export interface ParentToolCall {
6
6
  }
7
7
 
8
8
  export interface DispatchDecision {
9
- readonly mode: "async" | "inline";
10
9
  readonly synthesisGroup?: SynthesisGroup;
11
10
  }
12
11
 
@@ -20,30 +19,21 @@ const DISPATCH_TOOL_NAMES: ReadonlySet<string> = new Set([
20
19
  "interactive_send",
21
20
  ]);
22
21
 
23
- // Sole dispatches and homogeneous orchestrate waves detach so the parent turn can
24
- // end while work continues. Mixed tools stay inline because their shared parent
25
- // turn still has sibling work; one wave boundary defers one synthesis turn until
26
- // every admitted member has settled.
22
+ // Every public dispatch detaches so sibling parent tools can finish independently.
23
+ // Dispatches from one assistant response share a synthesis boundary; ordinary
24
+ // sibling tools are neither group members nor part of its expected size.
27
25
  export function classifyParentDispatches(
28
26
  toolCalls: readonly ParentToolCall[],
29
27
  ): readonly ClassifiedParentDispatch[] {
30
- const isOrchestrateGroup =
31
- toolCalls.length > 1 &&
32
- toolCalls.every((toolCall) => toolCall.name === "orchestrate");
33
- const synthesisGroup = isOrchestrateGroup
34
- ? { id: `orchestrate:${toolCalls[0]?.id ?? "group"}`, size: toolCalls.length }
28
+ const dispatches = toolCalls.filter((toolCall) =>
29
+ DISPATCH_TOOL_NAMES.has(toolCall.name)
30
+ );
31
+ const synthesisGroup = dispatches.length > 1
32
+ ? { id: `dispatch:${dispatches[0]!.id}`, size: dispatches.length }
35
33
  : undefined;
36
34
 
37
- return toolCalls.flatMap((toolCall): ClassifiedParentDispatch[] => {
38
- if (!DISPATCH_TOOL_NAMES.has(toolCall.name)) return [];
39
- return [{
40
- toolCallId: toolCall.id,
41
- decision: {
42
- mode: isOrchestrateGroup || toolCalls.length === 1 ? "async" : "inline",
43
- ...(toolCall.name === "orchestrate" && synthesisGroup
44
- ? { synthesisGroup }
45
- : {}),
46
- },
47
- }];
48
- });
35
+ return dispatches.map((toolCall) => ({
36
+ toolCallId: toolCall.id,
37
+ decision: synthesisGroup ? { synthesisGroup } : {},
38
+ }));
49
39
  }
@@ -146,7 +146,7 @@ function renderContext(
146
146
  const guidance: string[] = [];
147
147
  if (activeCount > 0) {
148
148
  guidance.push(
149
- "Do not duplicate active assignments. Wait for automatic result delivery; do not poll worker_status for completion.",
149
+ "Do not duplicate active assignments. Continue useful independent work when it materially advances the outcome.",
150
150
  );
151
151
  }
152
152
  if (readyCount > 0) {
@@ -155,7 +155,7 @@ function renderContext(
155
155
  );
156
156
  }
157
157
  guidance.push(
158
- "Use worker_status only for diagnostics or recovery when this snapshot reports overflow or state appears inconsistent.",
158
+ "Settled results are delivered automatically after the parent run ends. If progress depends on pending evidence, end the run truthfully rather than polling. Use worker_status only for diagnostics or recovery when this snapshot reports overflow or state appears inconsistent.",
159
159
  );
160
160
  sections.push(`Guidance:\n- ${guidance.join("\n- ")}`);
161
161
 
@@ -1,4 +1,3 @@
1
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
1
  import type {
3
2
  ExtensionAPI,
4
3
  ExtensionContext,
@@ -28,10 +27,7 @@ import type {
28
27
  } from "../orchestration/admission.ts";
29
28
  import type {
30
29
  AcceptedRun,
31
- CompletedRun,
32
30
  OwnerSnapshot,
33
- SettlementListener,
34
- WorkerRunResult,
35
31
  } from "../orchestration/service.ts";
36
32
  import type { DispatchDecision } from "../parent/dispatch-policy.ts";
37
33
  import type { OrchestrationClient } from "../parent/process-host.ts";
@@ -42,11 +38,6 @@ import {
42
38
  workerAbortToolRenderer,
43
39
  workerStatusToolRenderer,
44
40
  } from "./tool-renderer.ts";
45
- import {
46
- encodeInlineWorkerToolDetails,
47
- type InlineWorkerSettlementDetails,
48
- type WorkerSettlement,
49
- } from "../orchestration/settlement.ts";
50
41
 
51
42
  const STRICT_OBJECT = { additionalProperties: false } as const;
52
43
  const shortTextSchema = Type.String({
@@ -114,45 +105,30 @@ export function registerOrchestrationTools(
114
105
  name: "orchestrate",
115
106
  label: "Orchestrate",
116
107
  description:
117
- "Dispatch fully briefed worker scopes. Pi executes native sibling tools concurrently; Pi Orchestrate treats a successfully admitted sole orchestrate call or pure sibling group as async. Mixing orchestrate with another tool makes it inline and blocking.",
118
- promptSnippet: "Dispatch fully briefed parallel worker scopes",
108
+ "Dispatch one fully briefed worker scope in the background. All orchestrate and interactive_send calls in the same assistant response form one result group; ordinary sibling tools execute concurrently and are not group members.",
109
+ promptSnippet: "Dispatch one fully briefed worker scope in the background",
119
110
  promptGuidelines: [
120
- "Spin up as many workers as needed to cover every useful parallel scope and distinct validation perspective. Treat user-named workers or counts as a floor unless explicitly capped, and reuse the same worker role across multiple calls when useful.",
121
- "For an intended async wave of N workers, the next assistant response must contain exactly N separate, fully briefed orchestrate calls; one call is valid only when N=1. To run it asynchronously, include no other tool calls; harmless response text does not affect runtime classification.",
122
- "When a parallel tool dispatcher is available, use it once with exactly N orchestrate entries and no other tools; for example, put N functions.orchestrate entries in multi_tool_use.parallel. Otherwise emit N native sibling orchestrate calls in one assistant response.",
123
- "Form all N calls before emitting or finalizing the response. Never emit one call and wait for its result before forming the rest of the wave: a successfully admitted sole async orchestrate call returns terminate=true and ends the turn.",
111
+ "orchestrate starts background work and returns acceptance without ending the parent run. Dispatch siblings in one assistant response share a result group; ordinary sibling tools do not join that group.",
124
112
  ],
125
113
  executionMode: "parallel",
126
114
  parameters: taskSchema,
127
115
  ...orchestrateToolRenderer,
128
- async execute(toolCallId, params, signal, onUpdate, ctx) {
116
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
129
117
  const decision = deps.getDispatchDecision(toolCallId);
130
- const mode = decision.mode;
131
- const orchestrationContext = buildOrchestrationContext(ctx, deps, decision.synthesisGroup);
132
- if (mode === "async") {
133
- const acceptedRun = await deps.orchestration.orchestrate(
134
- orchestrationContext,
135
- params,
136
- "async",
137
- signal,
138
- );
139
- const readable = acceptedRunSummary(acceptedRun);
140
- return {
141
- ...readableToolResult(`Accepted async run ${readable.run_id}.`, readable),
142
- terminate: true,
143
- };
144
- }
145
-
146
- const completedRun = await deps.orchestration.orchestrate(
118
+ const orchestrationContext = buildOrchestrationContext(
119
+ ctx,
120
+ deps,
121
+ decision.synthesisGroup,
122
+ );
123
+ const acceptedRun = await deps.orchestration.orchestrate(
147
124
  orchestrationContext,
148
125
  params,
149
- "inline",
126
+ "async",
150
127
  signal,
151
- createInlineSettlementListener(onUpdate),
152
128
  );
153
- const readable = completedRunSummary(completedRun);
129
+ const readable = acceptedRunSummary(acceptedRun);
154
130
  return readableToolResult(
155
- `Completed inline run ${readable.run_id}.`,
131
+ `Accepted async run ${readable.run_id}.`,
156
132
  readable,
157
133
  );
158
134
  },
@@ -185,43 +161,31 @@ export function registerOrchestrationTools(
185
161
  name: "interactive_send",
186
162
  label: "Interactive Send",
187
163
  description:
188
- "Send follow-up instructions only to an owned lifecycle interactive worker whose status is ready. Never use for one-shot or completed workers; one-shot sessions terminate automatically. A sole tool call runs asynchronously; sibling tool calls make it inline and blocking.",
164
+ "Send background follow-up instructions only to an owned lifecycle interactive worker whose status is ready. All interactive_send and orchestrate calls in the same assistant response form one result group; ordinary sibling tools execute concurrently and are not group members. Never use for one-shot or completed workers.",
189
165
  promptSnippet: "Use only for an owned lifecycle interactive worker with status ready; never one-shot/completed because one-shot sessions terminate automatically",
190
166
  promptGuidelines: [
191
- "Use interactive_send only for an owned lifecycle interactive worker whose status is ready; never use it for one-shot or completed workers because one-shot sessions terminate automatically.",
167
+ "interactive_send starts background follow-up work and returns acceptance without ending the parent run. Dispatch siblings in one assistant response share a result group; ordinary sibling tools do not join that group.",
192
168
  ],
193
169
  parameters: interactiveSendSchema,
194
170
  ...interactiveSendToolRenderer,
195
- async execute(toolCallId, params, signal, onUpdate, ctx) {
171
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
196
172
  const workerId = params.worker_id;
197
- const mode = deps.getDispatchDecision(toolCallId).mode;
198
- const orchestrationContext = buildOrchestrationContext(ctx, deps);
199
- if (mode === "async") {
200
- const acceptedRun = await deps.orchestration.sendInteractive(
201
- orchestrationContext,
202
- workerId,
203
- params.instructions,
204
- "async",
205
- signal,
206
- );
207
- const readable = acceptedRunSummary(acceptedRun);
208
- return {
209
- ...readableToolResult(`Accepted async run ${readable.run_id}.`, readable),
210
- terminate: true,
211
- };
212
- }
213
-
214
- const completedRun = await deps.orchestration.sendInteractive(
173
+ const decision = deps.getDispatchDecision(toolCallId);
174
+ const orchestrationContext = buildOrchestrationContext(
175
+ ctx,
176
+ deps,
177
+ decision.synthesisGroup,
178
+ );
179
+ const acceptedRun = await deps.orchestration.sendInteractive(
215
180
  orchestrationContext,
216
181
  workerId,
217
182
  params.instructions,
218
- "inline",
183
+ "async",
219
184
  signal,
220
- createInlineSettlementListener(onUpdate),
221
185
  );
222
- const readable = completedRunSummary(completedRun);
186
+ const readable = acceptedRunSummary(acceptedRun);
223
187
  return readableToolResult(
224
- `Completed inline run ${readable.run_id}.`,
188
+ `Accepted async run ${readable.run_id}.`,
225
189
  readable,
226
190
  );
227
191
  },
@@ -289,20 +253,6 @@ function buildOrchestrationContext(
289
253
  };
290
254
  }
291
255
 
292
- function createInlineSettlementListener(
293
- onUpdate: ((result: AgentToolResult<unknown>) => void) | undefined,
294
- ): SettlementListener {
295
- return (settlement) => {
296
- onUpdate?.({
297
- content: [{ type: "text", text: "Worker response received." }],
298
- details: encodeInlineWorkerToolDetails({
299
- mode: "inline",
300
- result: inlineResultValue(settlement),
301
- }),
302
- });
303
- };
304
- }
305
-
306
256
  function normalizeAbortTarget(params: {
307
257
  worker_ids?: string[];
308
258
  all?: boolean;
@@ -321,33 +271,6 @@ function acceptedRunSummary(run: AcceptedRun) {
321
271
  };
322
272
  }
323
273
 
324
- function completedRunSummary(run: CompletedRun) {
325
- return encodeInlineWorkerToolDetails({
326
- mode: "inline",
327
- runId: run.id,
328
- ownerSessionId: run.ownerSessionId,
329
- result: inlineResultValue(run.result),
330
- });
331
- }
332
-
333
- function inlineResultValue(
334
- result: WorkerRunResult | WorkerSettlement,
335
- ): InlineWorkerSettlementDetails {
336
- return {
337
- workerId: result.workerId,
338
- worker: result.worker,
339
- title: result.title,
340
- status: result.status,
341
- outcome: result.outcome,
342
- usage: result.usage,
343
- startedAt: result.startedAt,
344
- settledAt: result.settledAt,
345
- ...(result.sessionFile === undefined
346
- ? {}
347
- : { sessionFile: result.sessionFile }),
348
- };
349
- }
350
-
351
274
  function statusSummary(catalog: WorkerCatalog, snapshot: OwnerSnapshot) {
352
275
  return {
353
276
  catalog: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zachwill/pi-orchestrate",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "description": "Concurrent worker orchestration for Pi",
6
6
  "exports": {},