@zachwill/pi-orchestrate 0.9.2 → 0.11.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.
@@ -8,7 +8,9 @@ import {
8
8
  WorkerReadyOutcome,
9
9
  WorkerResponseOutcome,
10
10
  WorkerUsage,
11
- } from "./domain.js";
11
+ type RunRecord,
12
+ type SettledWorkerRecord,
13
+ } from "./model.ts";
12
14
 
13
15
  const NonnegativeInteger = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
14
16
  const PositiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
@@ -20,8 +22,8 @@ const FailureStage = Schema.Literals([
20
22
  "cancellation",
21
23
  ]);
22
24
 
23
- /** Canonical schema for settlement details written by the current runtime. */
24
- export const WorkerSettlementDetails = Schema.Struct({
25
+ /** Canonical schema for settlements written and persisted by orchestration. */
26
+ export const WorkerSettlement = Schema.Struct({
25
27
  eventId: Schema.String,
26
28
  sequence: PositiveInteger,
27
29
  ownerSessionId: Schema.String,
@@ -77,25 +79,69 @@ export const WorkerSettlementDetails = Schema.Struct({
77
79
  }),
78
80
  );
79
81
 
80
- export interface WorkerSettlementDetails
81
- extends Schema.Schema.Type<typeof WorkerSettlementDetails> {}
82
+ export interface WorkerSettlement
83
+ extends Schema.Schema.Type<typeof WorkerSettlement> {}
82
84
 
83
85
  export type SettlementFailureStage = NonNullable<
84
- WorkerSettlementDetails["failureStage"]
85
- >;
86
-
87
- export type WorkerSettlement = Schema.Schema.Type<
88
- typeof WorkerSettlementDetails
86
+ WorkerSettlement["failureStage"]
89
87
  >;
90
88
 
91
89
  const decodeCurrentWorkerSettlement = Schema.decodeUnknownResult(
92
- WorkerSettlementDetails,
90
+ WorkerSettlement,
93
91
  );
94
92
 
95
- export function decodePersistedWorkerSettlementDetails(value: unknown) {
93
+ export function decodePersistedWorkerSettlement(value: unknown) {
96
94
  return decodeCurrentWorkerSettlement(value);
97
95
  }
98
96
 
97
+ /** Every input a settlement needs; orchestration state entries stay private. */
98
+ export interface WorkerSettlementInput {
99
+ readonly sequence: number;
100
+ readonly generation: number;
101
+ readonly run: RunRecord;
102
+ readonly worker: SettledWorkerRecord;
103
+ readonly settledAt: number;
104
+ readonly failureStage?: SettlementFailureStage;
105
+ }
106
+
107
+ /** Builds the canonical settlement published to listeners and persisted by Pi. */
108
+ export function createWorkerSettlement({
109
+ sequence,
110
+ generation,
111
+ run,
112
+ worker,
113
+ settledAt,
114
+ failureStage,
115
+ }: WorkerSettlementInput): WorkerSettlement {
116
+ return Object.freeze({
117
+ eventId: `${sequence}:${run.id}:${worker.id}:${generation}`,
118
+ sequence,
119
+ ownerSessionId: worker.ownerSessionId,
120
+ runId: run.id,
121
+ workerId: worker.id,
122
+ generation,
123
+ mode: run.mode,
124
+ worker: worker.worker,
125
+ title: worker.title,
126
+ lifecycle: worker.lifecycle,
127
+ status: worker.status,
128
+ outcome: Object.freeze({ ...worker.outcome }),
129
+ ...(failureStage ? { failureStage } : {}),
130
+ usage: Object.freeze({ ...worker.usage }),
131
+ startedAt: worker.startedAt,
132
+ settledAt,
133
+ ...(run.synthesisGroupId && run.synthesisGroupSize
134
+ ? {
135
+ synthesisGroupId: run.synthesisGroupId,
136
+ synthesisGroupSize: run.synthesisGroupSize,
137
+ }
138
+ : {}),
139
+ ...(worker.sessionFile !== undefined
140
+ ? { sessionFile: worker.sessionFile }
141
+ : {}),
142
+ });
143
+ }
144
+
99
145
  const InlineWorkerOutcome = Schema.Union([
100
146
  WorkerCompletedOutcome.pipe(
101
147
  Schema.encodeKeys({ assistantText: "assistant_text" }),
@@ -121,15 +167,15 @@ const InlineWorkerUsage = WorkerUsage.pipe(
121
167
 
122
168
  /** Tool transport projection derived from the canonical settlement field schemas. */
123
169
  export const InlineWorkerSettlementDetails = Schema.Struct({
124
- workerId: WorkerSettlementDetails.fields.workerId,
125
- worker: WorkerSettlementDetails.fields.worker,
126
- title: WorkerSettlementDetails.fields.title,
127
- status: WorkerSettlementDetails.fields.status,
170
+ workerId: WorkerSettlement.fields.workerId,
171
+ worker: WorkerSettlement.fields.worker,
172
+ title: WorkerSettlement.fields.title,
173
+ status: WorkerSettlement.fields.status,
128
174
  outcome: InlineWorkerOutcome,
129
175
  usage: InlineWorkerUsage,
130
- startedAt: WorkerSettlementDetails.fields.startedAt,
131
- settledAt: WorkerSettlementDetails.fields.settledAt,
132
- sessionFile: WorkerSettlementDetails.fields.sessionFile,
176
+ startedAt: WorkerSettlement.fields.startedAt,
177
+ settledAt: WorkerSettlement.fields.settledAt,
178
+ sessionFile: WorkerSettlement.fields.sessionFile,
133
179
  }).pipe(
134
180
  Schema.encodeKeys({
135
181
  workerId: "worker_id",
@@ -153,9 +199,9 @@ export interface InlineWorkerSettlementDetails
153
199
 
154
200
  export const InlineWorkerToolDetails = Schema.Struct({
155
201
  mode: Schema.Literal("inline"),
156
- runId: Schema.optionalKey(WorkerSettlementDetails.fields.runId),
202
+ runId: Schema.optionalKey(WorkerSettlement.fields.runId),
157
203
  ownerSessionId: Schema.optionalKey(
158
- WorkerSettlementDetails.fields.ownerSessionId,
204
+ WorkerSettlement.fields.ownerSessionId,
159
205
  ),
160
206
  result: InlineWorkerSettlementDetails,
161
207
  }).pipe(
@@ -0,0 +1,3 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ export const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
@@ -0,0 +1,147 @@
1
+ import type { WorkerCatalog } from "../catalog/definition.ts";
2
+
3
+ const CONTRACT_START = "<!-- pi-orchestrate:contract:start -->";
4
+ const CONTRACT_END = "<!-- pi-orchestrate:contract:end -->";
5
+
6
+ function sortedWorkers(catalog: WorkerCatalog) {
7
+ return [...catalog.workers].sort((left, right) => {
8
+ if (left.name < right.name) return -1;
9
+ if (left.name > right.name) return 1;
10
+ return 0;
11
+ });
12
+ }
13
+
14
+ function escapeContractMarkers(value: string): string {
15
+ return value
16
+ .replaceAll(CONTRACT_START, "&lt;!-- pi-orchestrate:contract:start --&gt;")
17
+ .replaceAll(CONTRACT_END, "&lt;!-- pi-orchestrate:contract:end --&gt;");
18
+ }
19
+
20
+ function formatCatalog(catalog: WorkerCatalog): string {
21
+ const workers = sortedWorkers(catalog);
22
+ if (workers.length === 0) return "- No trusted workers are available for this session.";
23
+
24
+ return workers
25
+ .map(
26
+ (worker) =>
27
+ `- \`${escapeContractMarkers(worker.name)}\` [${worker.source.kind}] (${worker.lifecycle}): ${escapeContractMarkers(worker.description)}`,
28
+ )
29
+ .join("\n");
30
+ }
31
+
32
+ interface ContractMarker {
33
+ readonly start: number;
34
+ readonly end: number;
35
+ readonly kind: "start" | "end";
36
+ }
37
+
38
+ function contractMarkers(prompt: string): ContractMarker[] {
39
+ const markers: ContractMarker[] = [];
40
+ for (const [value, kind] of [
41
+ [CONTRACT_START, "start"],
42
+ [CONTRACT_END, "end"],
43
+ ] as const) {
44
+ let offset = 0;
45
+ while (offset < prompt.length) {
46
+ const start = prompt.indexOf(value, offset);
47
+ if (start < 0) break;
48
+ markers.push({ start, end: start + value.length, kind });
49
+ offset = start + value.length;
50
+ }
51
+ }
52
+ return markers.sort((left, right) => left.start - right.start);
53
+ }
54
+
55
+ function removeContractMarkers(prompt: string): {
56
+ readonly prompt: string;
57
+ readonly insertionOffset?: number;
58
+ } {
59
+ const markers = contractMarkers(prompt);
60
+ if (markers.length === 0) return { prompt };
61
+
62
+ const removed: Array<{ start: number; end: number }> = [];
63
+ const stack: ContractMarker[] = [];
64
+ for (const marker of markers) {
65
+ if (marker.kind === "start") {
66
+ stack.push(marker);
67
+ continue;
68
+ }
69
+ const start = stack.pop();
70
+ if (start && stack.length === 0) removed.push({ start: start.start, end: marker.end });
71
+ }
72
+
73
+ for (const marker of markers) {
74
+ if (!removed.some((range) => marker.start >= range.start && marker.end <= range.end)) {
75
+ removed.push({ start: marker.start, end: marker.end });
76
+ }
77
+ }
78
+ removed.sort((left, right) => left.start - right.start);
79
+
80
+ const insertionPoint = markers[0]!.start;
81
+ let insertionOffset = 0;
82
+ let cursor = 0;
83
+ let cleaned = "";
84
+ for (const range of removed) {
85
+ if (range.start < cursor) continue;
86
+ const retained = prompt.slice(cursor, range.start);
87
+ cleaned += retained;
88
+ if (range.start <= insertionPoint) insertionOffset = cleaned.length;
89
+ cursor = range.end;
90
+ }
91
+ cleaned += prompt.slice(cursor);
92
+ return { prompt: cleaned, insertionOffset };
93
+ }
94
+
95
+ function buildContract(catalog: WorkerCatalog): string {
96
+ return `${CONTRACT_START}
97
+ ## Pi Orchestrate Contract
98
+
99
+ You are the parent orchestrator and own the task end to end.
100
+
101
+ ### Delegation
102
+
103
+ - Keep trivial or tightly coupled work in the parent. Delegate work that can proceed independently or benefit from independent judgment.
104
+ - Choose worker scopes and counts from the task. Treat workers or counts named by the user as a floor unless the user sets an exact cap.
105
+ - Each \`orchestrate\` call creates a fresh worker session. Multiple calls may use the same worker definition and identical instructions when independent judgments are useful. Do not vary briefs merely to make them appear different. Interactive follow-up instead continues one worker ID with its existing context.
106
+ - Give each worker a self-contained brief with its objective, context, paths and scope, forbidden actions, success criteria, and expected output. Workers do not receive the parent conversation.
107
+
108
+ ### Parallel dispatch
109
+
110
+ - Form the complete wave before emitting any tool call.
111
+ - For one worker, make one fully briefed \`orchestrate\` call.
112
+ - For N workers where N > 1, make exactly one \`multi_tool_use.parallel\` call. Its \`tool_uses\` must contain exactly N \`functions.orchestrate\` entries and no other tools.
113
+ - If \`multi_tool_use.parallel\` is not present, emit all N \`orchestrate\` calls as native siblings in one assistant response.
114
+ - Never dispatch a multi-worker wave as separate assistant responses. An admitted sole asynchronous \`orchestrate\` call ends the parent turn, so omitted workers cannot be added afterward.
115
+ - The expanded tool-call group must contain only the intended \`orchestrate\` calls. Mixing another tool into the group makes orchestration inline and blocking.
116
+
117
+ ### Completion and lifecycle
118
+
119
+ - Calls are admitted independently; a rejected call does not stop its siblings.
120
+ - After dispatching, wait for automatic result delivery instead of polling \`worker_status\`. When results expose more independent work, dispatch another complete wave.
121
+ - The parent reviews and synthesizes worker results, resolves conflicts, integrates changes, and runs the relevant verification.
122
+ - Prefer one-shot workers. Use interactive workers only when retained context is useful, and follow the ownership and status requirements in the lifecycle tool descriptions.
123
+
124
+ ### Trusted worker catalog
125
+
126
+ ${formatCatalog(catalog)}
127
+ ${CONTRACT_END}`;
128
+ }
129
+
130
+ export function applyOrchestratorContract(
131
+ systemPrompt: string,
132
+ catalog: WorkerCatalog,
133
+ ): string {
134
+ const section = buildContract(catalog);
135
+ const cleaned = removeContractMarkers(systemPrompt);
136
+ if (cleaned.insertionOffset !== undefined) {
137
+ return `${cleaned.prompt.slice(0, cleaned.insertionOffset)}${section}${cleaned.prompt.slice(cleaned.insertionOffset)}`;
138
+ }
139
+
140
+ const separator =
141
+ systemPrompt.length === 0 || systemPrompt.endsWith("\n\n")
142
+ ? ""
143
+ : systemPrompt.endsWith("\n")
144
+ ? "\n"
145
+ : "\n\n";
146
+ return `${systemPrompt}${separator}${section}`;
147
+ }
@@ -1,6 +1,6 @@
1
1
  import { Context, Effect, Layer } from "effect";
2
- import { Orchestration } from "./runtime.js";
3
- import type { WorkerSettlement } from "./worker-settlement.js";
2
+ import { Orchestration } from "../orchestration/service.ts";
3
+ import type { WorkerSettlement } from "../orchestration/settlement.ts";
4
4
 
5
5
  export const MAX_DELIVERY_MARKDOWN_BYTES = 50 * 1024;
6
6
  export const MAX_WORKER_DELIVERY_MARKDOWN_BYTES = 16 * 1024;
@@ -11,14 +11,11 @@ export const DELIVERY_PARENT_INSTRUCTIONS =
11
11
 
12
12
  export type ParentBindingGeneration = string | number | symbol;
13
13
 
14
- /** Complete, immutable worker output for presentation and history consumers. */
15
- export type WorkerDeliveryDetails = WorkerSettlement;
16
-
17
14
  export interface WorkerDeliveryMessage {
18
15
  readonly customType: "pi-orchestrate-worker-result";
19
16
  readonly content: string;
20
17
  readonly display: true;
21
- readonly details: WorkerDeliveryDetails;
18
+ readonly details: WorkerSettlement;
22
19
  }
23
20
 
24
21
  export interface WorkerDeliveryOptions {
@@ -67,8 +64,8 @@ export class DeliveryCoordinator implements DeliveryService {
67
64
  private readonly pendingSettlements: WorkerSettlement[] = [];
68
65
  private readonly flushingOwners = new Set<string>();
69
66
  private readonly synthesisGroups = new Map<string, SynthesisGroupState>();
70
- // Runtime settlement sequences are process-scoped and monotonic across owners,
71
- // so one watermark is valid.
67
+ // Orchestration settlement sequences are process-scoped and monotonic across
68
+ // owners, so one watermark is valid.
72
69
  private highestAcceptedSequence = 0;
73
70
 
74
71
  bind(binding: ParentBinding): void {
@@ -0,0 +1,49 @@
1
+ import type { SynthesisGroup } from "../orchestration/model.ts";
2
+
3
+ export interface ParentToolCall {
4
+ readonly id: string;
5
+ readonly name: string;
6
+ }
7
+
8
+ export interface DispatchDecision {
9
+ readonly mode: "async" | "inline";
10
+ readonly synthesisGroup?: SynthesisGroup;
11
+ }
12
+
13
+ export interface ClassifiedParentDispatch {
14
+ readonly toolCallId: string;
15
+ readonly decision: DispatchDecision;
16
+ }
17
+
18
+ const DISPATCH_TOOL_NAMES: ReadonlySet<string> = new Set([
19
+ "orchestrate",
20
+ "interactive_send",
21
+ ]);
22
+
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.
27
+ export function classifyParentDispatches(
28
+ toolCalls: readonly ParentToolCall[],
29
+ ): 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 }
35
+ : undefined;
36
+
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
+ });
49
+ }
@@ -3,37 +3,42 @@ import {
3
3
  Delivery,
4
4
  deliveryLayer,
5
5
  type DeliveryService,
6
- } from "./delivery.js";
6
+ } from "./delivery.ts";
7
7
  import {
8
8
  Orchestration,
9
9
  orchestrationLayer,
10
- type AbortTarget,
11
10
  type AcceptedRun,
12
11
  type CompletedRun,
13
- type OrchestrationContext,
14
12
  type OrchestrationService,
15
13
  SHUTDOWN_CLEANUP_GRACE_MS,
16
- type RuntimeSnapshot,
14
+ type OwnerSnapshot,
17
15
  type SettlementListener,
18
16
  type UnsubscribeSettlement,
19
- } from "./runtime.js";
20
- import { createChildSessionsLayer } from "./worker-session.js";
21
- import type { OrchestrateTaskInput, RunMode } from "./domain.js";
22
-
17
+ } from "../orchestration/service.ts";
18
+ import type {
19
+ AbortTarget,
20
+ OrchestrationContext,
21
+ } from "../orchestration/admission.ts";
22
+ import { createChildSessionsLayer } from "../worker/child-sessions.ts";
23
+ import type { OrchestrateTaskInput, RunMode } from "../orchestration/model.ts";
24
+
25
+ // Bump this key when ProcessHost/OrchestrationClient changes incompatibly.
26
+ // Multiple package copies can coexist in one Pi process and adopt the same host.
23
27
  const PROCESS_HOST_KEY = Symbol.for("@zachwill/pi-orchestrate/process-host/v3");
24
28
 
25
- type RunResult<M extends RunMode> = M extends "async"
29
+ type DispatchResult<M extends RunMode> = M extends "async"
26
30
  ? AcceptedRun
27
31
  : CompletedRun;
28
32
 
29
- export interface OrchestratorRuntime {
33
+ /** Promise-facing orchestration API used by Pi tools and presentation. */
34
+ export interface OrchestrationClient {
30
35
  orchestrate<M extends RunMode>(
31
36
  context: OrchestrationContext,
32
37
  task: OrchestrateTaskInput,
33
38
  mode: M,
34
39
  signal?: AbortSignal,
35
40
  onSettlement?: SettlementListener,
36
- ): Promise<RunResult<M>>;
41
+ ): Promise<DispatchResult<M>>;
37
42
  sendInteractive<M extends RunMode>(
38
43
  context: OrchestrationContext,
39
44
  workerId: string,
@@ -41,20 +46,20 @@ export interface OrchestratorRuntime {
41
46
  mode: M,
42
47
  signal?: AbortSignal,
43
48
  onSettlement?: SettlementListener,
44
- ): Promise<RunResult<M>>;
49
+ ): Promise<DispatchResult<M>>;
45
50
  abort(ownerSessionId: string, target: AbortTarget): Promise<void>;
46
51
  closeInteractive(ownerSessionId: string, workerId: string): Promise<void>;
47
- snapshot(ownerSessionId: string): Promise<RuntimeSnapshot>;
52
+ snapshot(ownerSessionId: string): Promise<OwnerSnapshot>;
48
53
  subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement;
49
54
  subscribeState(
50
55
  ownerSessionId: string,
51
- listener: (snapshot: RuntimeSnapshot) => void,
56
+ listener: (snapshot: OwnerSnapshot) => void,
52
57
  ): () => void;
53
58
  shutdown(): Promise<void>;
54
59
  }
55
60
 
56
61
  export interface ProcessHost {
57
- readonly runtime: OrchestratorRuntime;
62
+ readonly orchestration: OrchestrationClient;
58
63
  readonly delivery: DeliveryService;
59
64
  }
60
65
 
@@ -100,7 +105,7 @@ export function getProcessHost(): ProcessHost | undefined {
100
105
  }
101
106
 
102
107
  /** Pi-facing adapter. Every Promise operation executes one complete Orchestration Effect. */
103
- export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime {
108
+ export class ManagedOrchestrationClient<R = never> implements OrchestrationClient {
104
109
  constructor(
105
110
  private readonly effectRuntime: ManagedRuntime.ManagedRuntime<Orchestration | R, never>,
106
111
  private readonly orchestration: OrchestrationService,
@@ -192,7 +197,7 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
192
197
  return this.run(this.orchestration.closeInteractive(ownerSessionId, workerId));
193
198
  }
194
199
 
195
- snapshot(ownerSessionId: string): Promise<RuntimeSnapshot> {
200
+ snapshot(ownerSessionId: string): Promise<OwnerSnapshot> {
196
201
  // Snapshot is dependency-free and remains readable from a retained host reference
197
202
  // after the process root has been disposed.
198
203
  return Effect.runPromise(this.orchestration.snapshot(ownerSessionId));
@@ -204,7 +209,7 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
204
209
 
205
210
  subscribeState(
206
211
  ownerSessionId: string,
207
- listener: (snapshot: RuntimeSnapshot) => void,
212
+ listener: (snapshot: OwnerSnapshot) => void,
208
213
  ): () => void {
209
214
  return this.orchestration.subscribeState(ownerSessionId, listener);
210
215
  }
@@ -220,6 +225,8 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
220
225
  if (signal?.aborted) throw abortSignalReason(signal);
221
226
  if (!signal) return this.effectRuntime.runPromise(effect);
222
227
 
228
+ // Race with a private sentinel because Effect may transform failures; after
229
+ // interruption settles, Pi must receive the caller's exact AbortSignal.reason.
223
230
  const signalInterruption = {};
224
231
  const exit = await this.effectRuntime.runPromiseExit(
225
232
  Effect.raceFirst(effect, abortSignalEffect(signal, signalInterruption)),
@@ -232,15 +239,15 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
232
239
  }
233
240
  }
234
241
 
235
- export function createProcessHostRuntimeAdapter<R>(
242
+ export function createOrchestrationClient<R>(
236
243
  effectRuntime: ManagedRuntime.ManagedRuntime<Orchestration | R, never>,
237
- ): OrchestratorRuntime {
244
+ ): OrchestrationClient {
238
245
  // Orchestration acquisition is synchronous; subscriptions must remain reentrant.
239
246
  const orchestration = effectRuntime.runSync(Orchestration);
240
- return new ProcessHostRuntimeAdapter(effectRuntime, orchestration);
247
+ return new ManagedOrchestrationClient(effectRuntime, orchestration);
241
248
  }
242
249
 
243
- export function createProcessApplicationLayer(): Layer.Layer<Orchestration | Delivery> {
250
+ export function makeProcessHostLayer(): Layer.Layer<Orchestration | Delivery> {
244
251
  const orchestration = orchestrationLayer().pipe(
245
252
  Layer.provide(createChildSessionsLayer()),
246
253
  );
@@ -258,11 +265,11 @@ export function createProcessHost(): ProcessHost {
258
265
  }
259
266
  if (existing) return existing;
260
267
 
261
- const effectRuntime = ManagedRuntime.make(createProcessApplicationLayer());
262
- const runtime = createProcessHostRuntimeAdapter(effectRuntime);
268
+ const effectRuntime = ManagedRuntime.make(makeProcessHostLayer());
269
+ const orchestration = createOrchestrationClient(effectRuntime);
263
270
  const delivery = effectRuntime.runSync(Delivery);
264
271
  const host: OwnedProcessHost = {
265
- runtime,
272
+ orchestration,
266
273
  delivery,
267
274
  effectRuntime,
268
275
  attachments: new Set(),
@@ -315,7 +322,7 @@ export function destroyProcessHost(
315
322
  let shutdown: Promise<void>;
316
323
  try {
317
324
  // shutdown() closes Orchestration admission before returning its bounded teardown Promise.
318
- shutdown = ownedHost.runtime.shutdown();
325
+ shutdown = ownedHost.orchestration.shutdown();
319
326
  } catch (error) {
320
327
  shutdown = Promise.reject(error);
321
328
  }