@effect-agent/platform-node 0.1.0-beta.47 → 0.1.0-beta.48

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.
@@ -10,7 +10,7 @@ import { Context, Crypto, Effect, Layer, Schema, Stream } from "effect";
10
10
  import { IntegrityReport, ObligationReport, ObligationThresholds, RecoveryExplanation, RetryCommand } from "@effect-agent/thread/Admin";
11
11
  import { OperationDenied } from "@effect-agent/thread/OperationAuthorizer";
12
12
  declare namespace NodeDurableHost_d_exports {
13
- export { AdmissionClosed, NodeDurableHost };
13
+ export { AdmissionClosed, NodeDurableHost, layer, run };
14
14
  }
15
15
  declare const AdmissionClosed_base: Schema.Class<AdmissionClosed, Schema.TaggedStruct<"AdmissionClosed", {
16
16
  readonly message: Schema.String;
@@ -29,6 +29,8 @@ declare const NodeDurableHost_base: Context.ServiceClass<NodeDurableHost, "@effe
29
29
  readonly startupRecovery: ReadonlyArray<RecoveryReport>;
30
30
  /** Admission-role readiness (deployment §7): true until shutdown begins. */
31
31
  readonly admissionOpen: Effect.Effect<boolean>;
32
+ /** Observe the managed worker pool, preserving its failure. Manual hosts start their pool here. */
33
+ readonly run: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;
32
34
  /** `DurableAgentRuntime.submit` behind the host admission gate. */
33
35
  readonly submit: <InputSchema extends Schema.Top>(agent: DurableSubmitAgent<InputSchema>, input: InputSchema["Type"], options: DurableSubmitOptions) => Effect.Effect<Receipt, AdmissionClosed | DurableSubmitFailure, InputSchema["EncodingServices"]>;
34
36
  readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, DurableAwaitFailure>;
@@ -56,11 +58,13 @@ declare const NodeDurableHost_base: Context.ServiceClass<NodeDurableHost, "@effe
56
58
  * Run `workerConcurrency` copies of `DurableAgentRuntime.runResolvedWorker` over the host's
57
59
  * registered Bindings (S2): every claimed head resolves its exact stored Binding before any
58
60
  * code runs (SUB-023), so one bounded pool serves parent and attached-child lanes.
61
+ * Hosts built with the module-level `layer` instead join their existing pool.
59
62
  */
60
63
  readonly runResolvedWorkers: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;
61
64
  }>;
62
65
  /**
63
- * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
66
+ * Operational host service. Prefer the module's `layer` and `run` for managed workers.
67
+ * The static constructors on this class retain explicit, manual worker ownership.
64
68
  *
65
69
  * Startup gates run during Layer construction, so the service existing implies readiness:
66
70
  * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
@@ -115,6 +119,43 @@ declare class NodeDurableHost extends NodeDurableHost_base {
115
119
  readonly bindings?: ReadonlyArray<ResolvedBinding>;
116
120
  }): Layer.Layer<NodeDurableHost | NodeDurableAgentRuntimeServices, DurableWorkerFailure | NodeDurableAgentRuntimeInitializationError | ContextError | AuthorizationError, Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>>;
117
121
  }
122
+ /**
123
+ * Acquire a complete Node host and start one bounded, scoped worker pool after recovery.
124
+ * Provide model, tool, instruction, and schema dependencies to this Layer. Reusing the Layer
125
+ * shares the same pool. A worker failure closes admission; observe it with `run` at the process
126
+ * boundary so the application exits and releases the host instead of remaining idle.
127
+ */
128
+ declare const layer: <const Entries extends ReadonlyArray<AgentRegistration>, ContextError = never, ContextRequirements = never, AuthorizationError = never, AuthorizationRequirements = never>(registrations: Entries, options: NodeDurableAgentRuntimeOptions<ContextError, ContextRequirements, AuthorizationError, AuthorizationRequirements>) => Layer.Layer<NodeDurableHost | NodeDurableAgentRuntimeServices, AuthorizationError | ContextError | NodePlatformConfigError | DurableWorkerFailure | import("@effect-agent/storage-sqlite/SqliteThreadStore").SqliteStorageInitializationError, Exclude<AuthorizationRequirements, Crypto.Crypto> | Exclude<ContextRequirements, Crypto.Crypto> | Exclude<Exclude<Exclude<Exclude<Exclude<Exclude<[Entries[number]] extends [never] ? never : Entries[number] extends (infer T_2) ? T_2 extends Entries[number] ? T_2 extends {
129
+ readonly attemptLayer: (context: import("@effect-agent/thread/AgentRegistration").AgentAttemptContext) => Layer.Layer<infer Provides, never, infer Requires>;
130
+ } ? Requires | Exclude<T_2 extends (infer T_3) ? T_3 extends T_2 ? T_3 extends {
131
+ readonly agent: infer A extends import("@effect-agent/thread/AgentRegistration").ExecutableAgentBinding;
132
+ } ? import("@effect-agent/thread/DurableAgentRuntime").DurableWorkerRequirements<A> : T_3 extends {
133
+ readonly agent: infer D extends import("@effect-agent/core/Agent").Definition<Schema.Top, Schema.Top, unknown, import("effect/unstable/ai/Toolkit").Any, import("@effect-agent/core/Agent").RunDispositionDeclaration<never, Schema.Top> | undefined, unknown> & {
134
+ readonly instructions: import("@effect-agent/core/Agent").InstructionSource<never, unknown, unknown>;
135
+ readonly inputPrompt?: import("@effect-agent/core/Agent").InputPromptSource<never, unknown, unknown> | undefined;
136
+ };
137
+ readonly model: infer M extends import("@effect-agent/thread/AgentRegistration").ExecutableAgentBinding["model"];
138
+ } ? import("@effect-agent/thread/DurableAgentRuntime").DurableWorkerRequirements<{
139
+ readonly definition: D;
140
+ readonly model: M;
141
+ }> : never : never : never, Provides> : T_2 extends {
142
+ readonly agent: infer A extends import("@effect-agent/thread/AgentRegistration").ExecutableAgentBinding;
143
+ } ? import("@effect-agent/thread/DurableAgentRuntime").DurableWorkerRequirements<A> : T_2 extends {
144
+ readonly agent: infer D extends import("@effect-agent/core/Agent").Definition<Schema.Top, Schema.Top, unknown, import("effect/unstable/ai/Toolkit").Any, import("@effect-agent/core/Agent").RunDispositionDeclaration<never, Schema.Top> | undefined, unknown> & {
145
+ readonly instructions: import("@effect-agent/core/Agent").InstructionSource<never, unknown, unknown>;
146
+ readonly inputPrompt?: import("@effect-agent/core/Agent").InputPromptSource<never, unknown, unknown> | undefined;
147
+ };
148
+ readonly model: infer M extends import("@effect-agent/thread/AgentRegistration").ExecutableAgentBinding["model"];
149
+ } ? import("@effect-agent/thread/DurableAgentRuntime").DurableWorkerRequirements<{
150
+ readonly definition: D;
151
+ readonly model: M;
152
+ }> : never : never : never, import("effect/Scope").Scope>, import("@effect-agent/thread/DurableAgentRuntime").DurableRuntimeConfig | import("@effect-agent/thread/Schedule").ScheduleStore | import("@effect-agent/thread/SubmissionLedger").SubmissionLedger | import("@effect-agent/thread/ThreadStore").ThreadStore | import("@effect-agent/thread/WakeScheduler").WakeScheduler>, import("@effect-agent/thread/DurableFailpoint").DurableRuntimeFailpoint | NodeWakeSchedulerConfig | import("@effect-agent/thread/ToolReconciler").ToolReconciler>, Crypto.Crypto | import("effect/unstable/sql/SqlClient").SqlClient | import("@effect/sql-sqlite-node/SqliteClient").SqliteClient | import("@effect-agent/storage-sqlite/SqliteStorageConfig").SqliteStorageConfig | import("@effect-agent/storage-sqlite/SqliteStorageFailpoint").SqliteStorageFailpoint>, NodeDurableAgentRuntimeConfig>, import("@effect-agent/engine/RunOptions").RunContextPreparation | import("@effect-agent/engine/RunOptions").RunToolAuthorization>>;
153
+ /**
154
+ * Supervise the host's existing workers without starting another pool. Use with
155
+ * `Effect.provide(HostLive)` and `NodeRuntime.runMain`; race it with a server Effect when
156
+ * the same process also serves requests. Unlike `Layer.launch`, this observes worker failures.
157
+ */
158
+ declare const run: Effect.Effect<void, DurableBindingFailure | DurableWorkerFailure, NodeDurableHost>;
118
159
  //#endregion
119
- export { AdmissionClosed, NodeDurableHost, NodeDurableHost_d_exports as t };
160
+ export { AdmissionClosed, NodeDurableHost, layer, run, NodeDurableHost_d_exports as t };
120
161
  //# sourceMappingURL=NodeDurableHost.d.mts.map
@@ -6,25 +6,26 @@ import { DurableAgentRuntime } from "@effect-agent/thread/DurableAgentRuntime";
6
6
  import "@effect-agent/thread/Records";
7
7
  import "@effect-agent/thread/SubmissionLedger";
8
8
  import "@effect-agent/thread/ThreadStore";
9
- import { Context, Effect, Layer, Ref, Schema } from "effect";
9
+ import { Context, Effect, Fiber, Layer, Ref, Schema } from "effect";
10
10
  import "@effect-agent/thread/Admin";
11
11
  import "@effect-agent/thread/OperationAuthorizer";
12
12
  //#region src/NodeDurableHost.ts
13
13
  var NodeDurableHost_exports = /* @__PURE__ */ __exportAll({
14
14
  AdmissionClosed: () => AdmissionClosed,
15
- NodeDurableHost: () => NodeDurableHost
15
+ NodeDurableHost: () => NodeDurableHost,
16
+ layer: () => layer,
17
+ run: () => run
16
18
  });
17
19
  /**
18
20
  * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).
19
21
  * Accepted work is unaffected — only NEW admissions are refused.
20
22
  */
21
23
  var AdmissionClosed = class extends Schema.TaggedError()("AdmissionClosed", { message: Schema.String }) {};
22
- const makeHost = Effect.gen(function* () {
24
+ const makeHost = Effect.fn("NodeDurableHost.make")(function* (startWorkers) {
23
25
  const runtime = yield* DurableAgentRuntime;
24
26
  const config = yield* NodeDurableAgentRuntimeConfig;
25
27
  const startupRecovery = yield* runtime.runRecovery;
26
28
  const admission = yield* Ref.make(true);
27
- yield* Effect.addFinalizer(() => Ref.set(admission, false));
28
29
  const requireAdmission = Ref.get(admission).pipe(Effect.flatMap((open) => open ? Effect.void : Effect.fail(AdmissionClosed.make({ message: "The host is shutting down; admission is closed." }))));
29
30
  const submit = (agent, input, options) => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
30
31
  const runWorkers = (worker) => Effect.forEach(Array.from({ length: config.workerConcurrency }, (_, index) => index), () => worker, {
@@ -32,6 +33,8 @@ const makeHost = Effect.gen(function* () {
32
33
  discard: true
33
34
  });
34
35
  const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);
36
+ const run = startWorkers ? Fiber.join(yield* runResolvedWorkers.pipe(Effect.onExit(() => Ref.set(admission, false)), Effect.forkScoped)) : runResolvedWorkers;
37
+ yield* Effect.addFinalizer(() => Ref.set(admission, false));
35
38
  return NodeDurableHost.of({
36
39
  startupRecovery,
37
40
  admissionOpen: Ref.get(admission),
@@ -46,11 +49,13 @@ const makeHost = Effect.gen(function* () {
46
49
  wake: runtime.wake,
47
50
  scanObligations: runtime.scanObligations,
48
51
  runWorkers,
49
- runResolvedWorkers
52
+ run,
53
+ runResolvedWorkers: run
50
54
  });
51
55
  });
52
56
  /**
53
- * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
57
+ * Operational host service. Prefer the module's `layer` and `run` for managed workers.
58
+ * The static constructors on this class retain explicit, manual worker ownership.
54
59
  *
55
60
  * Startup gates run during Layer construction, so the service existing implies readiness:
56
61
  * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
@@ -77,14 +82,29 @@ var NodeDurableHost = class NodeDurableHost extends Context.Service()("@effect-a
77
82
  * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns
78
83
  * executable registrations; omission registers no Agents, so resolved work fails closed.
79
84
  */
80
- static layer = Layer.effect(NodeDurableHost)(makeHost);
85
+ static layer = Layer.effect(NodeDurableHost)(makeHost(false));
81
86
  /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */
82
87
  static layerStack(options) {
83
88
  const { bindings = [], ...runtimeOptions } = options;
84
89
  return NodeDurableHost.layer.pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)));
85
90
  }
86
91
  };
92
+ /**
93
+ * Acquire a complete Node host and start one bounded, scoped worker pool after recovery.
94
+ * Provide model, tool, instruction, and schema dependencies to this Layer. Reusing the Layer
95
+ * shares the same pool. A worker failure closes admission; observe it with `run` at the process
96
+ * boundary so the application exits and releases the host instead of remaining idle.
97
+ */
98
+ const layer = (registrations, options) => Layer.effect(NodeDurableHost)(makeHost(true)).pipe(Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)));
99
+ /**
100
+ * Supervise the host's existing workers without starting another pool. Use with
101
+ * `Effect.provide(HostLive)` and `NodeRuntime.runMain`; race it with a server Effect when
102
+ * the same process also serves requests. Unlike `Layer.launch`, this observes worker failures.
103
+ */
104
+ const run = Effect.gen(function* () {
105
+ return yield* (yield* NodeDurableHost).run;
106
+ });
87
107
  //#endregion
88
- export { AdmissionClosed, NodeDurableHost, NodeDurableHost_exports as t };
108
+ export { AdmissionClosed, NodeDurableHost, layer, run, NodeDurableHost_exports as t };
89
109
 
90
110
  //# sourceMappingURL=NodeDurableHost.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodeDurableHost.mjs","names":[],"sources":["../src/NodeDurableHost.ts"],"sourcesContent":["import { type ThreadId, type SubmissionId } from \"@effect-agent/core/Identifiers\";\nimport {\n type IntegrityReport,\n type ObligationReport,\n type ObligationThresholds,\n type RecoveryExplanation,\n type RetryCommand,\n} from \"@effect-agent/thread/Admin\";\nimport {\n type AgentRegistration,\n type ResolvedBinding,\n type DurableBindingFailure,\n} from \"@effect-agent/thread/AgentRegistration\";\nimport {\n DurableAgentRuntime,\n type DurableAbortFailure,\n type DurableAwaitFailure,\n type DurableExplainFailure,\n type DurableObserveOptions,\n type DurableObligationFailure,\n type DurableRetryFailure,\n type DurableSubmitAgent,\n type DurableSubmitFailure,\n type DurableSubmitOptions,\n type DurableVerifyFailure,\n type DurableWorkerFailure,\n type Receipt,\n type RecoveryReport,\n} from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { type OperationDenied } from \"@effect-agent/thread/OperationAuthorizer\";\nimport { type CanonicalRecordEnvelope } from \"@effect-agent/thread/Records\";\nimport {\n type AbortCommand,\n type AbortIntent,\n type Settlement,\n} from \"@effect-agent/thread/SubmissionLedger\";\nimport {\n type ThreadNotMaterialized,\n type ThreadStoreError,\n} from \"@effect-agent/thread/ThreadStore\";\nimport { type Stream, Context, type Crypto, Effect, Layer, Ref, Schema } from \"effect\";\n\nimport {\n NodeDurableAgentRuntime,\n NodeDurableAgentRuntimeConfig,\n type NodeDurableAgentRuntimeInitializationError,\n type NodeDurableAgentRuntimeOptions,\n type NodeDurableAgentRuntimeServices,\n} from \"./NodeDurableAgentRuntime.ts\";\n\n/**\n * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).\n * Accepted work is unaffected — only NEW admissions are refused.\n */\nexport class AdmissionClosed extends Schema.TaggedError<AdmissionClosed>()(\"AdmissionClosed\", {\n message: Schema.String,\n}) {}\n\nconst makeHost = Effect.gen(function* () {\n const runtime = yield* DurableAgentRuntime;\n const config = yield* NodeDurableAgentRuntimeConfig;\n\n // Startup gate (deployment §5, plan §host): configuration decoding and storage compatibility\n // already gated this Layer's dependencies; the last gate before admission opens is recovering\n // EVERY nonterminal Submission. Work needing a live Agent Binding is reported `deferred` and\n // stays a visible obligation for `runWorkers`; lanes durably blocked on an Unknown Outcome are\n // reported `unknown` and wait for the authorized `resolveUnknown` path (DUR-017) — they consume\n // no worker permit while the settlement obligation stays owed.\n const startupRecovery = yield* runtime.runRecovery;\n\n const admission = yield* Ref.make(true);\n\n // Shutdown step 1 (DEPLOY-005): admission closes before the ownership drain in the runtime\n // Layer below releases claims and before the stores close in reverse acquisition order.\n yield* Effect.addFinalizer(() => Ref.set(admission, false));\n\n const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(\n Effect.flatMap((open) =>\n open\n ? Effect.void\n : Effect.fail(\n AdmissionClosed.make({ message: \"The host is shutting down; admission is closed.\" }),\n ),\n ),\n );\n\n const submit = <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ): Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n > => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));\n\n const runWorkers = <A, E, R>(worker: Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>\n Effect.forEach(\n Array.from({ length: config.workerConcurrency }, (_, index) => index),\n () => worker,\n { concurrency: \"unbounded\", discard: true },\n );\n\n // S2 multi-binding pool: every claimed head resolves its exact registered Binding through\n // the host's exact registrations, so one bounded\n // pool serves parent and child lanes — the spec §12 smallest-pool suspension/wakeup proof\n // runs `workerConcurrency: 1` over exactly this loop.\n const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);\n\n return NodeDurableHost.of({\n startupRecovery,\n admissionOpen: Ref.get(admission),\n submit,\n awaitSettlement: runtime.awaitSettlement,\n observe: runtime.observe,\n abort: runtime.abort,\n explain: runtime.explain,\n explainThread: runtime.explainThread,\n verify: runtime.verify,\n retry: runtime.retry,\n wake: runtime.wake,\n scanObligations: runtime.scanObligations,\n runWorkers,\n runResolvedWorkers,\n });\n});\n\n/**\n * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).\n *\n * Startup gates run during Layer construction, so the service existing implies readiness:\n * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,\n * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.\n * `startupRecovery` is the auditable evidence of that reconciliation pass.\n *\n * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing\n * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim\n * still held so another host can take over the lanes immediately, then the SQLite resources\n * close. Forced termination at any point stays safe — the durability protocol, not graceful\n * shutdown, provides correctness (DEPLOY-006).\n */\nexport class NodeDurableHost extends Context.Service<\n NodeDurableHost,\n {\n /**\n * The recovery decisions executed (or deferred) by this host's startup reconciliation.\n * Reports with the `unknown` disposition identify lanes blocked on Unknown Outcomes that\n * only the authorized DUR-017 resolution path can release.\n */\n readonly startupRecovery: ReadonlyArray<RecoveryReport>;\n /** Admission-role readiness (deployment §7): true until shutdown begins. */\n readonly admissionOpen: Effect.Effect<boolean>;\n /** `DurableAgentRuntime.submit` behind the host admission gate. */\n readonly submit: <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ) => Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n >;\n readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, DurableAwaitFailure>;\n readonly observe: (\n receipt: Receipt,\n options?: DurableObserveOptions,\n ) => Stream.Stream<\n CanonicalRecordEnvelope,\n ThreadStoreError | ThreadNotMaterialized | OperationDenied\n >;\n readonly abort: (command: AbortCommand) => Effect.Effect<AbortIntent, DurableAbortFailure>;\n /** `DurableAgentRuntime.explain` — read-only recovery explanation of one Submission (P7). */\n readonly explain: (\n submissionId: SubmissionId,\n ) => Effect.Effect<RecoveryExplanation, DurableExplainFailure>;\n /** `DurableAgentRuntime.explainThread` — explain every nonterminal lane member. */\n readonly explainThread: (\n threadId: ThreadId,\n ) => Effect.Effect<ReadonlyArray<RecoveryExplanation>, DurableExplainFailure>;\n /** `DurableAgentRuntime.verify` — read-only integrity checks, never a repair (P7). */\n readonly verify: (threadId: ThreadId) => Effect.Effect<IntegrityReport, DurableVerifyFailure>;\n /** `DurableAgentRuntime.retry` — audited single-Submission re-drive with typed refusals. */\n readonly retry: (command: RetryCommand) => Effect.Effect<RecoveryReport, DurableRetryFailure>;\n /** `DurableAgentRuntime.wake` — the documented operator liveness nudge for one lane. */\n readonly wake: (threadId: ThreadId) => Effect.Effect<void, OperationDenied>;\n /** `DurableAgentRuntime.scanObligations` — the scan-based DUR-017/OPS-001 report. */\n readonly scanObligations: (\n thresholds: ObligationThresholds,\n ) => Effect.Effect<ObligationReport, DurableObligationFailure>;\n /**\n * Run `workerConcurrency` copies of the given worker effect (typically\n * `DurableAgentRuntime.runWorker(agent)`) until the caller's Scope interrupts them. The\n * bound is the validated finite configuration value; the host never forks daemon fibers.\n */\n readonly runWorkers: <A, E, R>(worker: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>;\n /**\n * Run `workerConcurrency` copies of `DurableAgentRuntime.runResolvedWorker` over the host's\n * registered Bindings (S2): every claimed head resolves its exact stored Binding before any\n * code runs (SUB-023), so one bounded pool serves parent and attached-child lanes.\n */\n readonly runResolvedWorkers: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;\n }\n>()(\"@effect-agent/platform-node/NodeDurableHost\") {\n /**\n * Compile typed registrations and acquire the complete host in one Layer Scope.\n * Node supplies Crypto; model, tool, instruction, and schema services remain required.\n * Startup recovery and shutdown gates are unchanged. Workers start only when the caller\n * runs runResolvedWorkers; this constructor never starts a background worker.\n */\n static layerRegistered<\n const Entries extends ReadonlyArray<AgentRegistration>,\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n registrations: Entries,\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n >,\n ) {\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),\n );\n }\n\n /**\n * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns\n * executable registrations; omission registers no Agents, so resolved work fails closed.\n */\n static readonly layer: Layer.Layer<\n NodeDurableHost,\n DurableWorkerFailure,\n DurableAgentRuntime | NodeDurableAgentRuntimeConfig\n > = Layer.effect(NodeDurableHost)(makeHost);\n\n /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */\n static layerStack<\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n > & { readonly bindings?: ReadonlyArray<ResolvedBinding> },\n ): Layer.Layer<\n NodeDurableHost | NodeDurableAgentRuntimeServices,\n | DurableWorkerFailure\n | NodeDurableAgentRuntimeInitializationError\n | ContextError\n | AuthorizationError,\n Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>\n > {\n const { bindings = [], ...runtimeOptions } = options;\n\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,kBAAb,cAAqC,OAAO,YAA6B,CAAC,CAAC,mBAAmB,EAC5F,SAAS,OAAO,OAClB,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,WAAW,OAAO,IAAI,aAAa;CACvC,MAAM,UAAU,OAAO;CACvB,MAAM,SAAS,OAAO;CAQtB,MAAM,kBAAkB,OAAO,QAAQ;CAEvC,MAAM,YAAY,OAAO,IAAI,KAAK,IAAI;CAItC,OAAO,OAAO,mBAAmB,IAAI,IAAI,WAAW,KAAK,CAAC;CAE1D,MAAM,mBAAyD,IAAI,IAAI,SAAS,CAAC,CAAC,KAChF,OAAO,SAAS,SACd,OACI,OAAO,OACP,OAAO,KACL,gBAAgB,KAAK,EAAE,SAAS,kDAAkD,CAAC,CACrF,CACN,CACF;CAEA,MAAM,UACJ,OACA,OACA,YAKG,iBAAiB,KAAK,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO,OAAO,CAAC,CAAC;CAEhF,MAAM,cAAuB,WAC3B,OAAO,QACL,MAAM,KAAK,EAAE,QAAQ,OAAO,kBAAkB,IAAI,GAAG,UAAU,KAAK,SAC9D,QACN;EAAE,aAAa;EAAa,SAAS;CAAK,CAC5C;CAMF,MAAM,qBAAqB,WAAW,QAAQ,iBAAiB;CAE/D,OAAO,gBAAgB,GAAG;EACxB;EACA,eAAe,IAAI,IAAI,SAAS;EAChC;EACA,iBAAiB,QAAQ;EACzB,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,eAAe,QAAQ;EACvB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,MAAM,QAAQ;EACd,iBAAiB,QAAQ;EACzB;EACA;CACF,CAAC;AACH,CAAC;;;;;;;;;;;;;;;AAgBD,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QA6D3C,CAAC,CAAC,6CAA6C,CAAC,CAAC;;;;;;;CAOjD,OAAO,gBAOL,eACA,SAMA;EACA,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,gBAAgB,eAAe,OAAO,CAAC,CACpF;CACF;;;;;CAMA,OAAgB,QAIZ,MAAM,OAAO,eAAe,CAAC,CAAC,QAAQ;;CAG1C,OAAO,WAML,SAaA;EACA,MAAM,EAAE,WAAW,CAAC,GAAG,GAAG,mBAAmB;EAE7C,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,kBAAkB,UAAU,cAAc,CAAC,CACxF;CACF;AACF"}
1
+ {"version":3,"file":"NodeDurableHost.mjs","names":[],"sources":["../src/NodeDurableHost.ts"],"sourcesContent":["import { type ThreadId, type SubmissionId } from \"@effect-agent/core/Identifiers\";\nimport {\n type IntegrityReport,\n type ObligationReport,\n type ObligationThresholds,\n type RecoveryExplanation,\n type RetryCommand,\n} from \"@effect-agent/thread/Admin\";\nimport {\n type AgentRegistration,\n type ResolvedBinding,\n type DurableBindingFailure,\n} from \"@effect-agent/thread/AgentRegistration\";\nimport {\n DurableAgentRuntime,\n type DurableAbortFailure,\n type DurableAwaitFailure,\n type DurableExplainFailure,\n type DurableObserveOptions,\n type DurableObligationFailure,\n type DurableRetryFailure,\n type DurableSubmitAgent,\n type DurableSubmitFailure,\n type DurableSubmitOptions,\n type DurableVerifyFailure,\n type DurableWorkerFailure,\n type Receipt,\n type RecoveryReport,\n} from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { type OperationDenied } from \"@effect-agent/thread/OperationAuthorizer\";\nimport { type CanonicalRecordEnvelope } from \"@effect-agent/thread/Records\";\nimport {\n type AbortCommand,\n type AbortIntent,\n type Settlement,\n} from \"@effect-agent/thread/SubmissionLedger\";\nimport {\n type ThreadNotMaterialized,\n type ThreadStoreError,\n} from \"@effect-agent/thread/ThreadStore\";\nimport { type Stream, Context, type Crypto, Effect, Fiber, Layer, Ref, Schema } from \"effect\";\n\nimport {\n NodeDurableAgentRuntime,\n NodeDurableAgentRuntimeConfig,\n type NodeDurableAgentRuntimeInitializationError,\n type NodeDurableAgentRuntimeOptions,\n type NodeDurableAgentRuntimeServices,\n} from \"./NodeDurableAgentRuntime.ts\";\n\n/**\n * Admission is not open on this host: it is shutting down (deployment §6 step 1, DEPLOY-005).\n * Accepted work is unaffected — only NEW admissions are refused.\n */\nexport class AdmissionClosed extends Schema.TaggedError<AdmissionClosed>()(\"AdmissionClosed\", {\n message: Schema.String,\n}) {}\n\nconst makeHost = Effect.fn(\"NodeDurableHost.make\")(function* (startWorkers: boolean) {\n const runtime = yield* DurableAgentRuntime;\n const config = yield* NodeDurableAgentRuntimeConfig;\n\n // Startup gate (deployment §5, plan §host): configuration decoding and storage compatibility\n // already gated this Layer's dependencies; the last gate before admission opens is recovering\n // EVERY nonterminal Submission. Work needing a live Agent Binding is reported `deferred` and\n // stays a visible obligation for `runWorkers`; lanes durably blocked on an Unknown Outcome are\n // reported `unknown` and wait for the authorized `resolveUnknown` path (DUR-017) — they consume\n // no worker permit while the settlement obligation stays owed.\n const startupRecovery = yield* runtime.runRecovery;\n\n const admission = yield* Ref.make(true);\n\n const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(\n Effect.flatMap((open) =>\n open\n ? Effect.void\n : Effect.fail(\n AdmissionClosed.make({ message: \"The host is shutting down; admission is closed.\" }),\n ),\n ),\n );\n\n const submit = <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ): Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n > => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));\n\n const runWorkers = <A, E, R>(worker: Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>\n Effect.forEach(\n Array.from({ length: config.workerConcurrency }, (_, index) => index),\n () => worker,\n { concurrency: \"unbounded\", discard: true },\n );\n\n // S2 multi-binding pool: every claimed head resolves its exact registered Binding through\n // the host's exact registrations, so one bounded\n // pool serves parent and child lanes — the spec §12 smallest-pool suspension/wakeup proof\n // runs `workerConcurrency: 1` over exactly this loop.\n const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);\n\n const run = startWorkers\n ? Fiber.join(\n yield* runResolvedWorkers.pipe(\n Effect.onExit(() => Ref.set(admission, false)),\n Effect.forkScoped,\n ),\n )\n : runResolvedWorkers;\n\n // Register after the worker fiber: close admission, interrupt/join workers, drain\n // runtime ownership, then close storage and captured application services.\n yield* Effect.addFinalizer(() => Ref.set(admission, false));\n\n return NodeDurableHost.of({\n startupRecovery,\n admissionOpen: Ref.get(admission),\n submit,\n awaitSettlement: runtime.awaitSettlement,\n observe: runtime.observe,\n abort: runtime.abort,\n explain: runtime.explain,\n explainThread: runtime.explainThread,\n verify: runtime.verify,\n retry: runtime.retry,\n wake: runtime.wake,\n scanObligations: runtime.scanObligations,\n runWorkers,\n run,\n runResolvedWorkers: run,\n });\n});\n\n/**\n * Operational host service. Prefer the module's `layer` and `run` for managed workers.\n * The static constructors on this class retain explicit, manual worker ownership.\n *\n * Startup gates run during Layer construction, so the service existing implies readiness:\n * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,\n * and every nonterminal Submission went through one full recovery pass BEFORE admission opened.\n * `startupRecovery` is the auditable evidence of that reconciliation pass.\n *\n * Shutdown runs in reverse Layer order when the owning Scope closes: `submit` starts refusing\n * with `AdmissionClosed` first, then the runtime Layer's ownership drain releases every claim\n * still held so another host can take over the lanes immediately, then the SQLite resources\n * close. Forced termination at any point stays safe — the durability protocol, not graceful\n * shutdown, provides correctness (DEPLOY-006).\n */\nexport class NodeDurableHost extends Context.Service<\n NodeDurableHost,\n {\n /**\n * The recovery decisions executed (or deferred) by this host's startup reconciliation.\n * Reports with the `unknown` disposition identify lanes blocked on Unknown Outcomes that\n * only the authorized DUR-017 resolution path can release.\n */\n readonly startupRecovery: ReadonlyArray<RecoveryReport>;\n /** Admission-role readiness (deployment §7): true until shutdown begins. */\n readonly admissionOpen: Effect.Effect<boolean>;\n /** Observe the managed worker pool, preserving its failure. Manual hosts start their pool here. */\n readonly run: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;\n /** `DurableAgentRuntime.submit` behind the host admission gate. */\n readonly submit: <InputSchema extends Schema.Top>(\n agent: DurableSubmitAgent<InputSchema>,\n input: InputSchema[\"Type\"],\n options: DurableSubmitOptions,\n ) => Effect.Effect<\n Receipt,\n AdmissionClosed | DurableSubmitFailure,\n InputSchema[\"EncodingServices\"]\n >;\n readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, DurableAwaitFailure>;\n readonly observe: (\n receipt: Receipt,\n options?: DurableObserveOptions,\n ) => Stream.Stream<\n CanonicalRecordEnvelope,\n ThreadStoreError | ThreadNotMaterialized | OperationDenied\n >;\n readonly abort: (command: AbortCommand) => Effect.Effect<AbortIntent, DurableAbortFailure>;\n /** `DurableAgentRuntime.explain` — read-only recovery explanation of one Submission (P7). */\n readonly explain: (\n submissionId: SubmissionId,\n ) => Effect.Effect<RecoveryExplanation, DurableExplainFailure>;\n /** `DurableAgentRuntime.explainThread` — explain every nonterminal lane member. */\n readonly explainThread: (\n threadId: ThreadId,\n ) => Effect.Effect<ReadonlyArray<RecoveryExplanation>, DurableExplainFailure>;\n /** `DurableAgentRuntime.verify` — read-only integrity checks, never a repair (P7). */\n readonly verify: (threadId: ThreadId) => Effect.Effect<IntegrityReport, DurableVerifyFailure>;\n /** `DurableAgentRuntime.retry` — audited single-Submission re-drive with typed refusals. */\n readonly retry: (command: RetryCommand) => Effect.Effect<RecoveryReport, DurableRetryFailure>;\n /** `DurableAgentRuntime.wake` — the documented operator liveness nudge for one lane. */\n readonly wake: (threadId: ThreadId) => Effect.Effect<void, OperationDenied>;\n /** `DurableAgentRuntime.scanObligations` — the scan-based DUR-017/OPS-001 report. */\n readonly scanObligations: (\n thresholds: ObligationThresholds,\n ) => Effect.Effect<ObligationReport, DurableObligationFailure>;\n /**\n * Run `workerConcurrency` copies of the given worker effect (typically\n * `DurableAgentRuntime.runWorker(agent)`) until the caller's Scope interrupts them. The\n * bound is the validated finite configuration value; the host never forks daemon fibers.\n */\n readonly runWorkers: <A, E, R>(worker: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>;\n /**\n * Run `workerConcurrency` copies of `DurableAgentRuntime.runResolvedWorker` over the host's\n * registered Bindings (S2): every claimed head resolves its exact stored Binding before any\n * code runs (SUB-023), so one bounded pool serves parent and attached-child lanes.\n * Hosts built with the module-level `layer` instead join their existing pool.\n */\n readonly runResolvedWorkers: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;\n }\n>()(\"@effect-agent/platform-node/NodeDurableHost\") {\n /**\n * Compile typed registrations and acquire the complete host in one Layer Scope.\n * Node supplies Crypto; model, tool, instruction, and schema services remain required.\n * Startup recovery and shutdown gates are unchanged. Workers start only when the caller\n * runs runResolvedWorkers; this constructor never starts a background worker.\n */\n static layerRegistered<\n const Entries extends ReadonlyArray<AgentRegistration>,\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n registrations: Entries,\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n >,\n ) {\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),\n );\n }\n\n /**\n * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns\n * executable registrations; omission registers no Agents, so resolved work fails closed.\n */\n static readonly layer: Layer.Layer<\n NodeDurableHost,\n DurableWorkerFailure,\n DurableAgentRuntime | NodeDurableAgentRuntimeConfig\n > = Layer.effect(NodeDurableHost)(makeHost(false));\n\n /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */\n static layerStack<\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n >(\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n > & { readonly bindings?: ReadonlyArray<ResolvedBinding> },\n ): Layer.Layer<\n NodeDurableHost | NodeDurableAgentRuntimeServices,\n | DurableWorkerFailure\n | NodeDurableAgentRuntimeInitializationError\n | ContextError\n | AuthorizationError,\n Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>\n > {\n const { bindings = [], ...runtimeOptions } = options;\n\n return NodeDurableHost.layer.pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)),\n );\n }\n}\n\n/**\n * Acquire a complete Node host and start one bounded, scoped worker pool after recovery.\n * Provide model, tool, instruction, and schema dependencies to this Layer. Reusing the Layer\n * shares the same pool. A worker failure closes admission; observe it with `run` at the process\n * boundary so the application exits and releases the host instead of remaining idle.\n */\nexport const layer = <\n const Entries extends ReadonlyArray<AgentRegistration>,\n ContextError = never,\n ContextRequirements = never,\n AuthorizationError = never,\n AuthorizationRequirements = never,\n>(\n registrations: Entries,\n options: NodeDurableAgentRuntimeOptions<\n ContextError,\n ContextRequirements,\n AuthorizationError,\n AuthorizationRequirements\n >,\n) =>\n Layer.effect(NodeDurableHost)(makeHost(true)).pipe(\n Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),\n );\n\n/**\n * Supervise the host's existing workers without starting another pool. Use with\n * `Effect.provide(HostLive)` and `NodeRuntime.runMain`; race it with a server Effect when\n * the same process also serves requests. Unlike `Layer.launch`, this observes worker failures.\n */\nexport const run = Effect.gen(function* () {\n const host = yield* NodeDurableHost;\n\n return yield* host.run;\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,kBAAb,cAAqC,OAAO,YAA6B,CAAC,CAAC,mBAAmB,EAC5F,SAAS,OAAO,OAClB,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,WAAW,OAAO,GAAG,sBAAsB,CAAC,CAAC,WAAW,cAAuB;CACnF,MAAM,UAAU,OAAO;CACvB,MAAM,SAAS,OAAO;CAQtB,MAAM,kBAAkB,OAAO,QAAQ;CAEvC,MAAM,YAAY,OAAO,IAAI,KAAK,IAAI;CAEtC,MAAM,mBAAyD,IAAI,IAAI,SAAS,CAAC,CAAC,KAChF,OAAO,SAAS,SACd,OACI,OAAO,OACP,OAAO,KACL,gBAAgB,KAAK,EAAE,SAAS,kDAAkD,CAAC,CACrF,CACN,CACF;CAEA,MAAM,UACJ,OACA,OACA,YAKG,iBAAiB,KAAK,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO,OAAO,CAAC,CAAC;CAEhF,MAAM,cAAuB,WAC3B,OAAO,QACL,MAAM,KAAK,EAAE,QAAQ,OAAO,kBAAkB,IAAI,GAAG,UAAU,KAAK,SAC9D,QACN;EAAE,aAAa;EAAa,SAAS;CAAK,CAC5C;CAMF,MAAM,qBAAqB,WAAW,QAAQ,iBAAiB;CAE/D,MAAM,MAAM,eACR,MAAM,KACJ,OAAO,mBAAmB,KACxB,OAAO,aAAa,IAAI,IAAI,WAAW,KAAK,CAAC,GAC7C,OAAO,UACT,CACF,IACA;CAIJ,OAAO,OAAO,mBAAmB,IAAI,IAAI,WAAW,KAAK,CAAC;CAE1D,OAAO,gBAAgB,GAAG;EACxB;EACA,eAAe,IAAI,IAAI,SAAS;EAChC;EACA,iBAAiB,QAAQ;EACzB,SAAS,QAAQ;EACjB,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,eAAe,QAAQ;EACvB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;EACf,MAAM,QAAQ;EACd,iBAAiB,QAAQ;EACzB;EACA;EACA,oBAAoB;CACtB,CAAC;AACH,CAAC;;;;;;;;;;;;;;;;AAiBD,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QAgE3C,CAAC,CAAC,6CAA6C,CAAC,CAAC;;;;;;;CAOjD,OAAO,gBAOL,eACA,SAMA;EACA,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,gBAAgB,eAAe,OAAO,CAAC,CACpF;CACF;;;;;CAMA,OAAgB,QAIZ,MAAM,OAAO,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC;;CAGjD,OAAO,WAML,SAaA;EACA,MAAM,EAAE,WAAW,CAAC,GAAG,GAAG,mBAAmB;EAE7C,OAAO,gBAAgB,MAAM,KAC3B,MAAM,aAAa,wBAAwB,kBAAkB,UAAU,cAAc,CAAC,CACxF;CACF;AACF;;;;;;;AAQA,MAAa,SAOX,eACA,YAOA,MAAM,OAAO,eAAe,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,KAC5C,MAAM,aAAa,wBAAwB,gBAAgB,eAAe,OAAO,CAAC,CACpF;;;;;;AAOF,MAAa,MAAM,OAAO,IAAI,aAAa;CAGzC,OAAO,QAAO,OAFM,gBAAA,CAED;AACrB,CAAC"}
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/platform-node","version":"0.1.0-beta.47","dependencies":{"@effect-agent/core":"0.1.0-beta.47","@effect-agent/engine":"0.1.0-beta.47","@effect-agent/storage-sqlite":"0.1.0-beta.47","@effect-agent/thread":"0.1.0-beta.47","@effect-agent/workflow":"0.1.0-beta.47","@effect/platform-node":"4.0.0-rc.112","@effect/sql-sqlite-node":"4.0.0-rc.112"},"devDependencies":{"@effect-agent/capabilities":"0.1.0-beta.47","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./NodeDurableAgentRuntime":{"types":"./dist/NodeDurableAgentRuntime.d.mts","default":"./dist/NodeDurableAgentRuntime.mjs"},"./NodeDurableHost":{"types":"./dist/NodeDurableHost.d.mts","default":"./dist/NodeDurableHost.mjs"},"./NodeScheduling":{"types":"./dist/NodeScheduling.d.mts","default":"./dist/NodeScheduling.mjs"},"./NodeSubscriptions":{"types":"./dist/NodeSubscriptions.d.mts","default":"./dist/NodeSubscriptions.mjs"},"./NodeWakeScheduler":{"types":"./dist/NodeWakeScheduler.d.mts","default":"./dist/NodeWakeScheduler.mjs"},"./NodeWorkflow":{"types":"./dist/NodeWorkflow.d.mts","default":"./dist/NodeWorkflow.mjs"}},"description":"Class DN layer assembly for Effect Agent: the durable Node/SQLite runtime host, wake scheduler, and ownership drain.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-node"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
1
+ {"name":"@effect-agent/platform-node","version":"0.1.0-beta.48","dependencies":{"@effect-agent/core":"0.1.0-beta.48","@effect-agent/engine":"0.1.0-beta.48","@effect-agent/storage-sqlite":"0.1.0-beta.48","@effect-agent/thread":"0.1.0-beta.48","@effect-agent/workflow":"0.1.0-beta.48","@effect/platform-node":"4.0.0-rc.112","@effect/sql-sqlite-node":"4.0.0-rc.112"},"devDependencies":{"@effect-agent/capabilities":"0.1.0-beta.48","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./NodeDurableAgentRuntime":{"types":"./dist/NodeDurableAgentRuntime.d.mts","default":"./dist/NodeDurableAgentRuntime.mjs"},"./NodeDurableHost":{"types":"./dist/NodeDurableHost.d.mts","default":"./dist/NodeDurableHost.mjs"},"./NodeScheduling":{"types":"./dist/NodeScheduling.d.mts","default":"./dist/NodeScheduling.mjs"},"./NodeSubscriptions":{"types":"./dist/NodeSubscriptions.d.mts","default":"./dist/NodeSubscriptions.mjs"},"./NodeWakeScheduler":{"types":"./dist/NodeWakeScheduler.d.mts","default":"./dist/NodeWakeScheduler.mjs"},"./NodeWorkflow":{"types":"./dist/NodeWorkflow.d.mts","default":"./dist/NodeWorkflow.mjs"}},"description":"Class DN layer assembly for Effect Agent: the durable Node/SQLite runtime host, wake scheduler, and ownership drain.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-node"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
@@ -38,7 +38,7 @@ import {
38
38
  type ThreadNotMaterialized,
39
39
  type ThreadStoreError,
40
40
  } from "@effect-agent/thread/ThreadStore";
41
- import { type Stream, Context, type Crypto, Effect, Layer, Ref, Schema } from "effect";
41
+ import { type Stream, Context, type Crypto, Effect, Fiber, Layer, Ref, Schema } from "effect";
42
42
 
43
43
  import {
44
44
  NodeDurableAgentRuntime,
@@ -56,7 +56,7 @@ export class AdmissionClosed extends Schema.TaggedError<AdmissionClosed>()("Admi
56
56
  message: Schema.String,
57
57
  }) {}
58
58
 
59
- const makeHost = Effect.gen(function* () {
59
+ const makeHost = Effect.fn("NodeDurableHost.make")(function* (startWorkers: boolean) {
60
60
  const runtime = yield* DurableAgentRuntime;
61
61
  const config = yield* NodeDurableAgentRuntimeConfig;
62
62
 
@@ -70,10 +70,6 @@ const makeHost = Effect.gen(function* () {
70
70
 
71
71
  const admission = yield* Ref.make(true);
72
72
 
73
- // Shutdown step 1 (DEPLOY-005): admission closes before the ownership drain in the runtime
74
- // Layer below releases claims and before the stores close in reverse acquisition order.
75
- yield* Effect.addFinalizer(() => Ref.set(admission, false));
76
-
77
73
  const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(
78
74
  Effect.flatMap((open) =>
79
75
  open
@@ -107,6 +103,19 @@ const makeHost = Effect.gen(function* () {
107
103
  // runs `workerConcurrency: 1` over exactly this loop.
108
104
  const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);
109
105
 
106
+ const run = startWorkers
107
+ ? Fiber.join(
108
+ yield* runResolvedWorkers.pipe(
109
+ Effect.onExit(() => Ref.set(admission, false)),
110
+ Effect.forkScoped,
111
+ ),
112
+ )
113
+ : runResolvedWorkers;
114
+
115
+ // Register after the worker fiber: close admission, interrupt/join workers, drain
116
+ // runtime ownership, then close storage and captured application services.
117
+ yield* Effect.addFinalizer(() => Ref.set(admission, false));
118
+
110
119
  return NodeDurableHost.of({
111
120
  startupRecovery,
112
121
  admissionOpen: Ref.get(admission),
@@ -121,12 +130,14 @@ const makeHost = Effect.gen(function* () {
121
130
  wake: runtime.wake,
122
131
  scanObligations: runtime.scanObligations,
123
132
  runWorkers,
124
- runResolvedWorkers,
133
+ run,
134
+ runResolvedWorkers: run,
125
135
  });
126
136
  });
127
137
 
128
138
  /**
129
- * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
139
+ * Operational host service. Prefer the module's `layer` and `run` for managed workers.
140
+ * The static constructors on this class retain explicit, manual worker ownership.
130
141
  *
131
142
  * Startup gates run during Layer construction, so the service existing implies readiness:
132
143
  * configuration was schema-decoded, the SQLite file passed the exact-version compatibility check,
@@ -150,6 +161,8 @@ export class NodeDurableHost extends Context.Service<
150
161
  readonly startupRecovery: ReadonlyArray<RecoveryReport>;
151
162
  /** Admission-role readiness (deployment §7): true until shutdown begins. */
152
163
  readonly admissionOpen: Effect.Effect<boolean>;
164
+ /** Observe the managed worker pool, preserving its failure. Manual hosts start their pool here. */
165
+ readonly run: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;
153
166
  /** `DurableAgentRuntime.submit` behind the host admission gate. */
154
167
  readonly submit: <InputSchema extends Schema.Top>(
155
168
  agent: DurableSubmitAgent<InputSchema>,
@@ -197,6 +210,7 @@ export class NodeDurableHost extends Context.Service<
197
210
  * Run `workerConcurrency` copies of `DurableAgentRuntime.runResolvedWorker` over the host's
198
211
  * registered Bindings (S2): every claimed head resolves its exact stored Binding before any
199
212
  * code runs (SUB-023), so one bounded pool serves parent and attached-child lanes.
213
+ * Hosts built with the module-level `layer` instead join their existing pool.
200
214
  */
201
215
  readonly runResolvedWorkers: Effect.Effect<void, DurableWorkerFailure | DurableBindingFailure>;
202
216
  }
@@ -235,7 +249,7 @@ export class NodeDurableHost extends Context.Service<
235
249
  NodeDurableHost,
236
250
  DurableWorkerFailure,
237
251
  DurableAgentRuntime | NodeDurableAgentRuntimeConfig
238
- > = Layer.effect(NodeDurableHost)(makeHost);
252
+ > = Layer.effect(NodeDurableHost)(makeHost(false));
239
253
 
240
254
  /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */
241
255
  static layerStack<
@@ -265,3 +279,39 @@ export class NodeDurableHost extends Context.Service<
265
279
  );
266
280
  }
267
281
  }
282
+
283
+ /**
284
+ * Acquire a complete Node host and start one bounded, scoped worker pool after recovery.
285
+ * Provide model, tool, instruction, and schema dependencies to this Layer. Reusing the Layer
286
+ * shares the same pool. A worker failure closes admission; observe it with `run` at the process
287
+ * boundary so the application exits and releases the host instead of remaining idle.
288
+ */
289
+ export const layer = <
290
+ const Entries extends ReadonlyArray<AgentRegistration>,
291
+ ContextError = never,
292
+ ContextRequirements = never,
293
+ AuthorizationError = never,
294
+ AuthorizationRequirements = never,
295
+ >(
296
+ registrations: Entries,
297
+ options: NodeDurableAgentRuntimeOptions<
298
+ ContextError,
299
+ ContextRequirements,
300
+ AuthorizationError,
301
+ AuthorizationRequirements
302
+ >,
303
+ ) =>
304
+ Layer.effect(NodeDurableHost)(makeHost(true)).pipe(
305
+ Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),
306
+ );
307
+
308
+ /**
309
+ * Supervise the host's existing workers without starting another pool. Use with
310
+ * `Effect.provide(HostLive)` and `NodeRuntime.runMain`; race it with a server Effect when
311
+ * the same process also serves requests. Unlike `Layer.launch`, this observes worker failures.
312
+ */
313
+ export const run = Effect.gen(function* () {
314
+ const host = yield* NodeDurableHost;
315
+
316
+ return yield* host.run;
317
+ });