@zachwill/pi-orchestrate 0.9.0 → 0.10.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.
@@ -1,6 +1,5 @@
1
1
  import { realpathSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
  import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
5
4
  import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
6
5
  import {
@@ -20,26 +19,22 @@ import {
20
19
  } from "@earendil-works/pi-coding-agent";
21
20
  import {
22
21
  Cause,
23
- Context,
24
- Deferred,
25
22
  Effect,
26
23
  Exit,
27
- FiberSet,
28
- Layer,
29
24
  Schema,
30
25
  Scope,
31
- SynchronizedRef,
32
26
  } from "effect";
27
+ import type { WorkerDefinition } from "../catalog/definition.js";
33
28
  import type {
34
- WorkerDefinition,
35
29
  WorkerMessageDirection,
36
30
  WorkerOutcome,
37
31
  WorkerUsage,
38
- } from "./domain.js";
32
+ } from "../orchestration/model.js";
33
+ import { PACKAGE_ROOT } from "../package-root.js";
39
34
 
40
35
  const DIRECT_CHILD_BOUNDARY =
41
36
  "You are a direct child worker session. Do not spawn, delegate to, or orchestrate descendant Pi worker sessions. Complete the assigned task yourself and return the result directly to the parent orchestrator.";
42
- const ORCHESTRATE_PACKAGE_ROOT = canonicalPath(fileURLToPath(new URL("..", import.meta.url)));
37
+ const ORCHESTRATE_PACKAGE_ROOT = canonicalPath(PACKAGE_ROOT);
43
38
 
44
39
  interface WorkerAgentSession {
45
40
  readonly sessionFile: string | undefined;
@@ -131,11 +126,6 @@ export class WorkerAgentSessionAcquisitionError extends Schema.TaggedError<Worke
131
126
  { operation: WorkerAgentSessionAcquisitionOperation, message: Schema.String, cause: Schema.Defect() },
132
127
  ) {}
133
128
 
134
- export class WorkerSessionAcquisitionClosedError extends Schema.TaggedError<WorkerSessionAcquisitionClosedError>()(
135
- "WorkerSession.AcquisitionClosedError",
136
- { message: Schema.String },
137
- ) {}
138
-
139
129
  const WorkerSessionAbortOperation = Schema.Literals([
140
130
  "abort-compaction",
141
131
  "abort-prompt",
@@ -155,29 +145,10 @@ export class WorkerSessionAbortError extends Schema.TaggedError<WorkerSessionAbo
155
145
  },
156
146
  ) {}
157
147
 
158
- export type WorkerSessionAcquisitionError =
148
+ export type WorkerSessionCreationError =
159
149
  | WorkerModelAcquisitionError
160
150
  | WorkerResourceAcquisitionError
161
- | WorkerAgentSessionAcquisitionError
162
- | WorkerSessionAcquisitionClosedError;
163
-
164
- export interface ChildSessionsService {
165
- /**
166
- * Acquires a child session through a process-owned producer. The adopter must
167
- * synchronously install ownership before returning a value. Returning undefined
168
- * rejects adoption and leaves ChildSessions responsible for disposal.
169
- */
170
- readonly acquire: <Adopted>(
171
- options: ChildSessionOptions,
172
- adopt: (session: WorkerSessionHandle) => Adopted | undefined,
173
- ) => Effect.Effect<Adopted | undefined, WorkerSessionAcquisitionError>;
174
- /** Closes every handoff without waiting for uncancellable Pi calls. */
175
- readonly shutdown: () => Effect.Effect<void>;
176
- }
177
-
178
- export class ChildSessions extends Context.Service<ChildSessions, ChildSessionsService>()(
179
- "@zachwill/pi-orchestrate/ChildSessions",
180
- ) {}
151
+ | WorkerAgentSessionAcquisitionError;
181
152
 
182
153
  const WorkerSessionCleanupOperation = Schema.Literals([
183
154
  "unsubscribe",
@@ -249,7 +220,12 @@ export interface WorkerSessionDependencies {
249
220
  onReclamationOpenObserved(): Effect.Effect<void>;
250
221
  }
251
222
 
252
- const defaultDependencies: WorkerSessionDependencies = {
223
+ type WorkerSessionAcquisitionDependencies = Omit<
224
+ WorkerSessionDependencies,
225
+ "beforeAdoptionReservation" | "onReclamationOpenObserved"
226
+ >;
227
+
228
+ const defaultWorkerSessionDependencies: WorkerSessionAcquisitionDependencies = {
253
229
  createSettingsManager: ({ cwd, agentDir, projectTrusted, compaction }) => {
254
230
  const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
255
231
  if (compaction !== undefined) settingsManager.applyOverrides({ compaction: { ...compaction } });
@@ -279,8 +255,6 @@ const defaultDependencies: WorkerSessionDependencies = {
279
255
  detail: `Operation: ${operation}`,
280
256
  });
281
257
  },
282
- beforeAdoptionReservation: () => Effect.void,
283
- onReclamationOpenObserved: () => Effect.void,
284
258
  };
285
259
 
286
260
  function canonicalPath(path: string): string {
@@ -656,7 +630,7 @@ const refreshModelRuntime = Effect.fn("WorkerSession.refreshModelRuntime")(funct
656
630
 
657
631
  const prepareChildModelRuntime = Effect.fn("WorkerSession.prepareChildModelRuntime")(function* (
658
632
  options: ChildSessionOptions,
659
- dependencies: WorkerSessionDependencies,
633
+ dependencies: WorkerSessionAcquisitionDependencies,
660
634
  ) {
661
635
  const selected = yield* Effect.try({
662
636
  try: () => selectedModelCoordinates(options.definition, options.parentModel),
@@ -749,7 +723,7 @@ const disposeWorkerSession = Effect.fn("WorkerSession.dispose")(function* (
749
723
 
750
724
  const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(function* (
751
725
  options: ChildSessionOptions,
752
- dependencies: WorkerSessionDependencies,
726
+ dependencies: WorkerSessionAcquisitionDependencies,
753
727
  modelRuntime: ModelRuntime,
754
728
  ) {
755
729
  const definition = options.definition;
@@ -825,7 +799,7 @@ function agentSessionFinalizer(
825
799
  }
826
800
 
827
801
  const acquireAgentSession = Effect.fn("WorkerSession.acquireAgentSession")(function* (
828
- dependencies: WorkerSessionDependencies,
802
+ dependencies: WorkerSessionAcquisitionDependencies,
829
803
  input: AgentSessionInput,
830
804
  ) {
831
805
  return yield* Effect.acquireRelease(
@@ -844,7 +818,7 @@ const acquireAgentSession = Effect.fn("WorkerSession.acquireAgentSession")(funct
844
818
  });
845
819
 
846
820
  const acquireAgentSessionRuntime = Effect.fn("WorkerSession.acquireAgentSessionRuntime")(function* (
847
- dependencies: WorkerSessionDependencies,
821
+ dependencies: WorkerSessionAcquisitionDependencies,
848
822
  ownership: AgentSessionOwnership,
849
823
  services: AgentSessionServices,
850
824
  ) {
@@ -857,7 +831,7 @@ const acquireAgentSessionRuntime = Effect.fn("WorkerSession.acquireAgentSessionR
857
831
  });
858
832
 
859
833
  const acquireSessionSubscription = Effect.fn("WorkerSession.acquireSubscription")(function* (
860
- dependencies: WorkerSessionDependencies,
834
+ dependencies: WorkerSessionAcquisitionDependencies,
861
835
  runtime: OwnedWorkerRuntime,
862
836
  handle: DefaultWorkerSessionHandle,
863
837
  ) {
@@ -874,10 +848,14 @@ const acquireSessionSubscription = Effect.fn("WorkerSession.acquireSubscription"
874
848
  );
875
849
  });
876
850
 
877
- const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
851
+ export const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
878
852
  options: ChildSessionOptions,
879
- dependencies: WorkerSessionDependencies,
853
+ overrides: Partial<WorkerSessionDependencies>,
880
854
  ) {
855
+ const dependencies: WorkerSessionAcquisitionDependencies = {
856
+ ...defaultWorkerSessionDependencies,
857
+ ...overrides,
858
+ };
881
859
  const scope = yield* Scope.make("sequential");
882
860
  const acquisition = Effect.gen(function* () {
883
861
  const definition = options.definition;
@@ -969,245 +947,3 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
969
947
  ),
970
948
  );
971
949
  });
972
-
973
- type AcquisitionHandoffState =
974
- | { readonly _tag: "Pending" }
975
- | { readonly _tag: "Offered"; readonly session: WorkerSessionHandle }
976
- | { readonly _tag: "Adopting"; readonly session: WorkerSessionHandle }
977
- | { readonly _tag: "Adopted" }
978
- | { readonly _tag: "Abandoned"; readonly error?: WorkerSessionAcquisitionClosedError }
979
- | { readonly _tag: "Failed" };
980
-
981
- interface AcquisitionHandoff {
982
- readonly state: SynchronizedRef.SynchronizedRef<AcquisitionHandoffState>;
983
- readonly result: Deferred.Deferred<WorkerSessionHandle, WorkerSessionAcquisitionError>;
984
- }
985
-
986
- type ChildSessionsState =
987
- | { readonly _tag: "Open"; readonly handoffs: ReadonlySet<AcquisitionHandoff> }
988
- | { readonly _tag: "Closed"; readonly error: WorkerSessionAcquisitionClosedError };
989
-
990
- type AdoptionReservation =
991
- | { readonly _tag: "Reserved"; readonly session: WorkerSessionHandle }
992
- | { readonly _tag: "Closed"; readonly error: WorkerSessionAcquisitionClosedError }
993
- | { readonly _tag: "Abandoned" };
994
-
995
- function disposeLateSession(session: WorkerSessionHandle): Effect.Effect<void> {
996
- return session.dispose().pipe(Effect.catchCause(() => Effect.void));
997
- }
998
-
999
- function admitReclamationOrJoinAfterClosure(
1000
- fibers: FiberSet.FiberSet<void, never>,
1001
- reclamation: Effect.Effect<void>,
1002
- onOpenObserved: () => Effect.Effect<void>,
1003
- ): Effect.Effect<void> {
1004
- return Effect.suspend(() => {
1005
- if (fibers.state._tag === "Closed") return reclamation;
1006
- return Effect.gen(function* () {
1007
- yield* onOpenObserved().pipe(Effect.catchCause(() => Effect.void));
1008
- yield* FiberSet.run(fibers, reclamation, { startImmediately: true });
1009
- if (fibers.state._tag === "Closed") {
1010
- // FiberSet.run returns an interrupted sentinel when closure wins admission.
1011
- // If admission won, closure interrupts the admitted fiber instead. Real
1012
- // session disposal is cached and uninterruptible, so this fallback safely
1013
- // joins that same disposal in both cases rather than starting cleanup twice.
1014
- yield* reclamation;
1015
- }
1016
- });
1017
- });
1018
- }
1019
-
1020
- export function createChildSessionsLayer(
1021
- overrides: Partial<WorkerSessionDependencies> = {},
1022
- ): Layer.Layer<ChildSessions> {
1023
- return Layer.effect(
1024
- ChildSessions,
1025
- Effect.gen(function* () {
1026
- const dependencies: WorkerSessionDependencies = { ...defaultDependencies, ...overrides };
1027
- const fibers = yield* FiberSet.make<void, never>();
1028
- const serviceState = yield* SynchronizedRef.make<ChildSessionsState>({
1029
- _tag: "Open",
1030
- handoffs: new Set(),
1031
- });
1032
-
1033
- const withoutHandoff = (
1034
- state: ChildSessionsState,
1035
- handoff: AcquisitionHandoff,
1036
- ): ChildSessionsState => {
1037
- if (state._tag === "Closed" || !state.handoffs.has(handoff)) return state;
1038
- const handoffs = new Set(state.handoffs);
1039
- handoffs.delete(handoff);
1040
- return { _tag: "Open", handoffs };
1041
- };
1042
-
1043
- const removeHandoff = (handoff: AcquisitionHandoff): Effect.Effect<void> =>
1044
- SynchronizedRef.update(serviceState, (state) => withoutHandoff(state, handoff));
1045
-
1046
- const runReclamation = (session: WorkerSessionHandle): Effect.Effect<void> =>
1047
- admitReclamationOrJoinAfterClosure(
1048
- fibers,
1049
- disposeLateSession(session).pipe(Effect.uninterruptible),
1050
- dependencies.onReclamationOpenObserved,
1051
- );
1052
-
1053
- const abandonHandoff = Effect.fn("ChildSessions.abandonHandoff")(function* (
1054
- handoff: AcquisitionHandoff,
1055
- ) {
1056
- const session = yield* SynchronizedRef.modifyEffect(serviceState, (service) => {
1057
- const nextService = withoutHandoff(service, handoff);
1058
- return SynchronizedRef.modify(handoff.state, (state): readonly [
1059
- { readonly session: WorkerSessionHandle | undefined },
1060
- AcquisitionHandoffState,
1061
- ] => {
1062
- if (state._tag === "Pending") {
1063
- return [{ session: undefined }, { _tag: "Abandoned" }];
1064
- }
1065
- if (state._tag === "Offered") {
1066
- return [{ session: state.session }, { _tag: "Abandoned" }];
1067
- }
1068
- return [{ session: undefined }, state];
1069
- }).pipe(Effect.map(({ session }) => [session, nextService] as const));
1070
- });
1071
- if (session) yield* runReclamation(session);
1072
- });
1073
-
1074
- const shutdown = Effect.fn("ChildSessions.shutdown")(() =>
1075
- Effect.gen(function* () {
1076
- const closed = yield* SynchronizedRef.modifyEffect(serviceState, (state) => {
1077
- if (state._tag === "Closed") return Effect.succeed([undefined, state] as const);
1078
- const error = new WorkerSessionAcquisitionClosedError({
1079
- message: "Child sessions are shutting down",
1080
- });
1081
- return Effect.gen(function* () {
1082
- const sessions: WorkerSessionHandle[] = [];
1083
- for (const handoff of state.handoffs) {
1084
- const session = yield* SynchronizedRef.modifyEffect(handoff.state, (handoffState) => {
1085
- if (handoffState._tag === "Pending") {
1086
- return Deferred.fail(handoff.result, error).pipe(
1087
- Effect.as([undefined, { _tag: "Abandoned", error }] as const),
1088
- );
1089
- }
1090
- if (handoffState._tag === "Offered") {
1091
- return Effect.succeed([
1092
- handoffState.session,
1093
- { _tag: "Abandoned", error },
1094
- ] as const);
1095
- }
1096
- // Failed already settled its Deferred. Adopting is an ownership
1097
- // reservation whose synchronous winner must be allowed to commit.
1098
- return Effect.succeed([undefined, handoffState] as const);
1099
- });
1100
- if (session) sessions.push(session);
1101
- }
1102
- return [{ sessions }, { _tag: "Closed", error }] as const;
1103
- });
1104
- });
1105
- if (!closed) return;
1106
- for (const session of closed.sessions) yield* runReclamation(session);
1107
- }).pipe(Effect.uninterruptible)
1108
- );
1109
-
1110
- yield* Effect.addFinalizer(() => shutdown());
1111
-
1112
- const acquire = Effect.fn("ChildSessions.acquire")(function* <Adopted>(
1113
- options: ChildSessionOptions,
1114
- adopt: (session: WorkerSessionHandle) => Adopted | undefined,
1115
- ) {
1116
- const handoff: AcquisitionHandoff = {
1117
- state: yield* SynchronizedRef.make<AcquisitionHandoffState>({ _tag: "Pending" }),
1118
- result: yield* Deferred.make<WorkerSessionHandle, WorkerSessionAcquisitionError>(),
1119
- };
1120
- return yield* Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
1121
- const admissionError = yield* SynchronizedRef.modify(serviceState, (state) => {
1122
- if (state._tag === "Closed") return [state.error, state] as const;
1123
- return [undefined, {
1124
- _tag: "Open",
1125
- handoffs: new Set([...state.handoffs, handoff]),
1126
- }] as const;
1127
- });
1128
- if (admissionError) return yield* Effect.fail(admissionError);
1129
-
1130
- const producer = createWorkerSession(options, dependencies).pipe(
1131
- Effect.matchCauseEffect({
1132
- onFailure: (cause) => Effect.gen(function* () {
1133
- const failed = yield* SynchronizedRef.modifyEffect(handoff.state, (state) =>
1134
- state._tag === "Pending"
1135
- ? Deferred.failCause(handoff.result, cause).pipe(
1136
- Effect.as([true, { _tag: "Failed" }] as const),
1137
- )
1138
- : Effect.succeed([false, state] as const));
1139
- if (failed) yield* removeHandoff(handoff);
1140
- }),
1141
- onSuccess: (session) => Effect.gen(function* () {
1142
- const offered = yield* SynchronizedRef.modifyEffect(handoff.state, (state) =>
1143
- state._tag === "Pending"
1144
- ? Deferred.succeed(handoff.result, session).pipe(
1145
- Effect.as([true, { _tag: "Offered", session }] as const),
1146
- )
1147
- : Effect.succeed([false, state] as const));
1148
- if (!offered) {
1149
- yield* removeHandoff(handoff);
1150
- yield* runReclamation(session);
1151
- }
1152
- }),
1153
- }),
1154
- Effect.uninterruptible,
1155
- );
1156
- yield* FiberSet.run(fibers, producer, { startImmediately: true });
1157
-
1158
- const session = yield* restore(
1159
- Deferred.await(handoff.result).pipe(
1160
- Effect.tap(() => dependencies.beforeAdoptionReservation()),
1161
- ),
1162
- ).pipe(Effect.onInterrupt(() => abandonHandoff(handoff)));
1163
- const reservation = yield* SynchronizedRef.modifyEffect(serviceState, (service) => {
1164
- if (service._tag === "Closed") {
1165
- return Effect.succeed([
1166
- { _tag: "Closed", error: service.error } satisfies AdoptionReservation,
1167
- service,
1168
- ] as const);
1169
- }
1170
- return SynchronizedRef.modify(
1171
- handoff.state,
1172
- (state): readonly [AdoptionReservation, AcquisitionHandoffState] => {
1173
- if (state._tag === "Abandoned") {
1174
- return state.error
1175
- ? [{ _tag: "Closed", error: state.error }, state]
1176
- : [{ _tag: "Abandoned" }, state];
1177
- }
1178
- if (state._tag !== "Offered" || state.session !== session) {
1179
- return [{ _tag: "Abandoned" }, state];
1180
- }
1181
- return [
1182
- { _tag: "Reserved", session },
1183
- { _tag: "Adopting", session },
1184
- ];
1185
- },
1186
- ).pipe(Effect.map((result) => [result, service] as const));
1187
- });
1188
- if (reservation._tag === "Closed") return yield* Effect.fail(reservation.error);
1189
- if (reservation._tag === "Abandoned") {
1190
- return yield* Effect.die(new Error("Child session acquisition handoff was abandoned"));
1191
- }
1192
-
1193
- // The adopter is arbitrary synchronous runtime code. The reservation
1194
- // protects it from shutdown, but no SynchronizedRef semaphore is held.
1195
- const adopted = yield* Effect.exit(Effect.sync(() => adopt(reservation.session)));
1196
- const transferred = Exit.isSuccess(adopted) && adopted.value !== undefined;
1197
- yield* SynchronizedRef.update(handoff.state, (state): AcquisitionHandoffState => {
1198
- if (state._tag !== "Adopting" || state.session !== reservation.session) return state;
1199
- return transferred ? { _tag: "Adopted" } : { _tag: "Abandoned" };
1200
- });
1201
- yield* removeHandoff(handoff);
1202
- if (transferred) return adopted.value;
1203
-
1204
- yield* runReclamation(reservation.session);
1205
- if (Exit.isFailure(adopted)) return yield* Effect.failCause(adopted.cause);
1206
- return undefined;
1207
- }));
1208
- });
1209
-
1210
- return ChildSessions.of({ acquire, shutdown });
1211
- }),
1212
- );
1213
- }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@zachwill/pi-orchestrate",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "description": "Concurrent worker orchestration for Pi",
6
+ "exports": {},
6
7
  "files": ["extension/", "examples/", "README.md", "LICENSE"],
7
8
  "keywords": ["pi-package", "pi", "workers", "orchestration"],
8
9
  "license": "MIT",