@effect-agent/platform-node 0.1.0-beta.44 → 0.1.0-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/host.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { ThreadId, SubmissionId } from "@effect-agent/core";
2
2
  import {
3
- compileRegistrations,
4
3
  type AgentRegistration,
5
4
  DurableAgentRuntime,
6
5
  type AbortCommand,
@@ -31,15 +30,14 @@ import {
31
30
  type RetryCommand,
32
31
  type Settlement,
33
32
  } from "@effect-agent/thread";
34
- import { NodeCrypto } from "@effect/platform-node";
35
33
  import { type Stream, Context, type Crypto, Effect, Layer, Ref, Schema } from "effect";
36
34
 
37
35
  import {
38
- NodeDurableRuntime,
39
- NodeDurableRuntimeConfig,
40
- type NodeDurableRuntimeInitializationError,
41
- type NodeDurableRuntimeOptions,
42
- type NodeDurableRuntimeServices,
36
+ NodeDurableAgentRuntime,
37
+ NodeDurableAgentRuntimeConfig,
38
+ type NodeDurableAgentRuntimeInitializationError,
39
+ type NodeDurableAgentRuntimeOptions,
40
+ type NodeDurableAgentRuntimeServices,
43
41
  } from "./layers.ts";
44
42
 
45
43
  /**
@@ -50,75 +48,74 @@ export class AdmissionClosed extends Schema.TaggedError<AdmissionClosed>()("Admi
50
48
  message: Schema.String,
51
49
  }) {}
52
50
 
53
- const makeHost = (bindings: ReadonlyArray<ResolvedBinding>) =>
54
- Effect.gen(function* () {
55
- const runtime = yield* DurableAgentRuntime;
56
- const config = yield* NodeDurableRuntimeConfig;
51
+ const makeHost = Effect.gen(function* () {
52
+ const runtime = yield* DurableAgentRuntime;
53
+ const config = yield* NodeDurableAgentRuntimeConfig;
57
54
 
58
- // Startup gate (deployment §5, plan §host): configuration decoding and storage compatibility
59
- // already gated this Layer's dependencies; the last gate before admission opens is recovering
60
- // EVERY nonterminal Submission. Work needing a live Agent Binding is reported `deferred` and
61
- // stays a visible obligation for `runWorkers`; lanes durably blocked on an Unknown Outcome are
62
- // reported `unknown` and wait for the authorized `resolveUnknown` path (DUR-017) — they consume
63
- // no worker permit while the settlement obligation stays owed.
64
- const startupRecovery = yield* runtime.runRecovery;
55
+ // Startup gate (deployment §5, plan §host): configuration decoding and storage compatibility
56
+ // already gated this Layer's dependencies; the last gate before admission opens is recovering
57
+ // EVERY nonterminal Submission. Work needing a live Agent Binding is reported `deferred` and
58
+ // stays a visible obligation for `runWorkers`; lanes durably blocked on an Unknown Outcome are
59
+ // reported `unknown` and wait for the authorized `resolveUnknown` path (DUR-017) — they consume
60
+ // no worker permit while the settlement obligation stays owed.
61
+ const startupRecovery = yield* runtime.runRecovery;
65
62
 
66
- const admission = yield* Ref.make(true);
63
+ const admission = yield* Ref.make(true);
67
64
 
68
- // Shutdown step 1 (DEPLOY-005): admission closes before the ownership drain in the runtime
69
- // Layer below releases claims and before the stores close in reverse acquisition order.
70
- yield* Effect.addFinalizer(() => Ref.set(admission, false));
65
+ // Shutdown step 1 (DEPLOY-005): admission closes before the ownership drain in the runtime
66
+ // Layer below releases claims and before the stores close in reverse acquisition order.
67
+ yield* Effect.addFinalizer(() => Ref.set(admission, false));
71
68
 
72
- const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(
73
- Effect.flatMap((open) =>
74
- open
75
- ? Effect.void
76
- : Effect.fail(
77
- AdmissionClosed.make({ message: "The host is shutting down; admission is closed." }),
78
- ),
79
- ),
80
- );
69
+ const requireAdmission: Effect.Effect<void, AdmissionClosed> = Ref.get(admission).pipe(
70
+ Effect.flatMap((open) =>
71
+ open
72
+ ? Effect.void
73
+ : Effect.fail(
74
+ AdmissionClosed.make({ message: "The host is shutting down; admission is closed." }),
75
+ ),
76
+ ),
77
+ );
81
78
 
82
- const submit = <InputSchema extends Schema.Top>(
83
- agent: DurableSubmitAgent<InputSchema>,
84
- input: InputSchema["Type"],
85
- options: DurableSubmitOptions,
86
- ): Effect.Effect<
87
- Receipt,
88
- AdmissionClosed | DurableSubmitFailure,
89
- InputSchema["EncodingServices"]
90
- > => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
79
+ const submit = <InputSchema extends Schema.Top>(
80
+ agent: DurableSubmitAgent<InputSchema>,
81
+ input: InputSchema["Type"],
82
+ options: DurableSubmitOptions,
83
+ ): Effect.Effect<
84
+ Receipt,
85
+ AdmissionClosed | DurableSubmitFailure,
86
+ InputSchema["EncodingServices"]
87
+ > => requireAdmission.pipe(Effect.andThen(runtime.submit(agent, input, options)));
91
88
 
92
- const runWorkers = <A, E, R>(worker: Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>
93
- Effect.forEach(
94
- Array.from({ length: config.workerConcurrency }, (_, index) => index),
95
- () => worker,
96
- { concurrency: "unbounded", discard: true },
97
- );
89
+ const runWorkers = <A, E, R>(worker: Effect.Effect<A, E, R>): Effect.Effect<void, E, R> =>
90
+ Effect.forEach(
91
+ Array.from({ length: config.workerConcurrency }, (_, index) => index),
92
+ () => worker,
93
+ { concurrency: "unbounded", discard: true },
94
+ );
98
95
 
99
- // S2 multi-binding pool: every claimed head resolves its exact registered Binding through
100
- // the host's exact registrations, so one bounded
101
- // pool serves parent and child lanes — the spec §12 smallest-pool suspension/wakeup proof
102
- // runs `workerConcurrency: 1` over exactly this loop.
103
- const runResolvedWorkers = runWorkers(runtime.runResolvedWorker(bindings));
96
+ // S2 multi-binding pool: every claimed head resolves its exact registered Binding through
97
+ // the host's exact registrations, so one bounded
98
+ // pool serves parent and child lanes — the spec §12 smallest-pool suspension/wakeup proof
99
+ // runs `workerConcurrency: 1` over exactly this loop.
100
+ const runResolvedWorkers = runWorkers(runtime.runResolvedWorker);
104
101
 
105
- return NodeDurableHost.of({
106
- startupRecovery,
107
- admissionOpen: Ref.get(admission),
108
- submit,
109
- awaitSettlement: runtime.awaitSettlement,
110
- observe: runtime.observe,
111
- abort: runtime.abort,
112
- explain: runtime.explain,
113
- explainThread: runtime.explainThread,
114
- verify: runtime.verify,
115
- retry: runtime.retry,
116
- wake: runtime.wake,
117
- scanObligations: runtime.scanObligations,
118
- runWorkers,
119
- runResolvedWorkers,
120
- });
102
+ return NodeDurableHost.of({
103
+ startupRecovery,
104
+ admissionOpen: Ref.get(admission),
105
+ submit,
106
+ awaitSettlement: runtime.awaitSettlement,
107
+ observe: runtime.observe,
108
+ abort: runtime.abort,
109
+ explain: runtime.explain,
110
+ explainThread: runtime.explainThread,
111
+ verify: runtime.verify,
112
+ retry: runtime.retry,
113
+ wake: runtime.wake,
114
+ scanObligations: runtime.scanObligations,
115
+ runWorkers,
116
+ runResolvedWorkers,
121
117
  });
118
+ });
122
119
 
123
120
  /**
124
121
  * Operational host lifecycle for the DN runtime (deployment §2/§5/§6).
@@ -210,57 +207,53 @@ export class NodeDurableHost extends Context.Service<
210
207
  AuthorizationRequirements = never,
211
208
  >(
212
209
  registrations: Entries,
213
- options: NodeDurableRuntimeOptions<
210
+ options: NodeDurableAgentRuntimeOptions<
214
211
  ContextError,
215
212
  ContextRequirements,
216
213
  AuthorizationError,
217
214
  AuthorizationRequirements
218
215
  >,
219
216
  ) {
220
- return Layer.unwrap(
221
- Effect.map(compileRegistrations(registrations), (bindings) =>
222
- NodeDurableHost.layerStack({ ...options, bindings }),
223
- ),
224
- ).pipe(Layer.provide(NodeCrypto.layer));
217
+ return NodeDurableHost.layer.pipe(
218
+ Layer.provideMerge(NodeDurableAgentRuntime.layerRegistered(registrations, options)),
219
+ );
225
220
  }
226
221
 
227
222
  /**
228
- * Host gates over an assembled `NodeDurableRuntime` stack. Bindings must carry the exact
229
- * digests stored by submitters. Omission registers no Agents, so resolved work fails closed.
223
+ * Host gates over an assembled `NodeDurableAgentRuntime` stack. The runtime Layer owns
224
+ * executable registrations; omission registers no Agents, so resolved work fails closed.
230
225
  */
231
- static readonly layer = (
232
- bindings: ReadonlyArray<ResolvedBinding> = [],
233
- ): Layer.Layer<
226
+ static readonly layer: Layer.Layer<
234
227
  NodeDurableHost,
235
228
  DurableWorkerFailure,
236
- DurableAgentRuntime | NodeDurableRuntimeConfig
237
- > => Layer.effect(NodeDurableHost)(makeHost(bindings));
229
+ DurableAgentRuntime | NodeDurableAgentRuntimeConfig
230
+ > = Layer.effect(NodeDurableHost)(makeHost);
238
231
 
239
- /** The complete DN host: `NodeDurableRuntime.layer(options)` plus the host lifecycle gates. */
232
+ /** The complete DN host: `NodeDurableAgentRuntime.layer(options)` plus the host lifecycle gates. */
240
233
  static layerStack<
241
234
  ContextError = never,
242
235
  ContextRequirements = never,
243
236
  AuthorizationError = never,
244
237
  AuthorizationRequirements = never,
245
238
  >(
246
- options: NodeDurableRuntimeOptions<
239
+ options: NodeDurableAgentRuntimeOptions<
247
240
  ContextError,
248
241
  ContextRequirements,
249
242
  AuthorizationError,
250
243
  AuthorizationRequirements
251
244
  > & { readonly bindings?: ReadonlyArray<ResolvedBinding> },
252
245
  ): Layer.Layer<
253
- NodeDurableHost | NodeDurableRuntimeServices,
246
+ NodeDurableHost | NodeDurableAgentRuntimeServices,
254
247
  | DurableWorkerFailure
255
- | NodeDurableRuntimeInitializationError
248
+ | NodeDurableAgentRuntimeInitializationError
256
249
  | ContextError
257
250
  | AuthorizationError,
258
251
  Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>
259
252
  > {
260
253
  const { bindings = [], ...runtimeOptions } = options;
261
254
 
262
- return NodeDurableHost.layer(bindings).pipe(
263
- Layer.provideMerge(NodeDurableRuntime.layer(runtimeOptions)),
255
+ return NodeDurableHost.layer.pipe(
256
+ Layer.provideMerge(NodeDurableAgentRuntime.layerWithBindings(bindings, runtimeOptions)),
264
257
  );
265
258
  }
266
259
  }
package/src/layers.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  type SqliteStorageInitializationError,
20
20
  } from "@effect-agent/storage-sqlite";
21
21
  import {
22
+ type AgentRegistration,
23
+ type ResolvedBinding,
22
24
  type ScheduleStore,
23
25
  type ThreadStore,
24
26
  type WakeScheduler,
@@ -62,8 +64,8 @@ export class NodePlatformConfigError extends Schema.TaggedError<NodePlatformConf
62
64
  * construction, exposed as a typed service). Every cadence is in milliseconds and every bound is
63
65
  * finite; `workerConcurrency` caps how many worker loops `NodeDurableHost.runWorkers` drives.
64
66
  */
65
- export class NodeDurableRuntimeConfigValue extends Schema.Class<NodeDurableRuntimeConfigValue>(
66
- "@effect-agent/platform-node/NodeDurableRuntimeConfigValue",
67
+ export class NodeDurableAgentRuntimeConfigValue extends Schema.Class<NodeDurableAgentRuntimeConfigValue>(
68
+ "@effect-agent/platform-node/NodeDurableAgentRuntimeConfigValue",
67
69
  )({
68
70
  /** SQLite database file backing BOTH the Thread Log and the Submission Ledger. */
69
71
  filename: Schema.NonEmptyString,
@@ -90,17 +92,17 @@ export class NodeDurableRuntimeConfigValue extends Schema.Class<NodeDurableRunti
90
92
  }) {}
91
93
 
92
94
  /** Explicit configuration authority for the assembled Node durable runtime. */
93
- export class NodeDurableRuntimeConfig extends Context.Service<
94
- NodeDurableRuntimeConfig,
95
- NodeDurableRuntimeConfigValue
96
- >()("@effect-agent/platform-node/NodeDurableRuntimeConfig") {}
95
+ export class NodeDurableAgentRuntimeConfig extends Context.Service<
96
+ NodeDurableAgentRuntimeConfig,
97
+ NodeDurableAgentRuntimeConfigValue
98
+ >()("@effect-agent/platform-node/NodeDurableAgentRuntimeConfig") {}
97
99
 
98
100
  /**
99
- * Raw (unvalidated) construction options for `NodeDurableRuntime.layer`. Optional fields default
101
+ * Raw (unvalidated) construction options for `NodeDurableAgentRuntime.layer`. Optional fields default
100
102
  * to the documented production values; everything is schema-decoded into
101
- * `NodeDurableRuntimeConfigValue` before any resource opens (deployment §5 gate 1).
103
+ * `NodeDurableAgentRuntimeConfigValue` before any resource opens (deployment §5 gate 1).
102
104
  */
103
- export interface NodeDurableRuntimeOptions<
105
+ export interface NodeDurableAgentRuntimeOptions<
104
106
  ContextError = never,
105
107
  ContextRequirements = never,
106
108
  AuthorizationError = never,
@@ -161,25 +163,25 @@ export interface NodeDurableRuntimeOptions<
161
163
  }
162
164
 
163
165
  /** Built-in construction failures. `layer` also preserves supplied service Layers' errors. */
164
- export type NodeDurableRuntimeInitializationError =
166
+ export type NodeDurableAgentRuntimeInitializationError =
165
167
  | NodePlatformConfigError
166
168
  | SqliteStorageInitializationError;
167
169
 
168
- /** The services `NodeDurableRuntime.layer` provides. */
169
- export type NodeDurableRuntimeServices =
170
+ /** The services `NodeDurableAgentRuntime.layer` provides. */
171
+ export type NodeDurableAgentRuntimeServices =
170
172
  | DurableAgentRuntime
171
173
  | SubmissionLedger
172
174
  | ThreadStore
173
175
  | ScheduleStore
174
176
  | WakeScheduler
175
177
  | DurableRuntimeConfig
176
- | NodeDurableRuntimeConfig;
178
+ | NodeDurableAgentRuntimeConfig;
177
179
 
178
- const decodeConfigValue = Schema.decodeUnknownEffect(NodeDurableRuntimeConfigValue);
180
+ const decodeConfigValue = Schema.decodeUnknownEffect(NodeDurableAgentRuntimeConfigValue);
179
181
 
180
182
  const configFromOptions = (
181
- options: Omit<NodeDurableRuntimeOptions, "runContext" | "toolAuthorization">,
182
- ): Effect.Effect<NodeDurableRuntimeConfigValue, NodePlatformConfigError> =>
183
+ options: Omit<NodeDurableAgentRuntimeOptions, "runContext" | "toolAuthorization">,
184
+ ): Effect.Effect<NodeDurableAgentRuntimeConfigValue, NodePlatformConfigError> =>
183
185
  decodeConfigValue({
184
186
  filename: options.filename,
185
187
  deploymentId: options.deploymentId,
@@ -204,27 +206,30 @@ const configFromOptions = (
204
206
  );
205
207
 
206
208
  /** SQLite storage configuration derived from the single validated Node configuration. */
207
- const sqliteStorageConfigLayer: Layer.Layer<SqliteStorageConfig, never, NodeDurableRuntimeConfig> =
208
- Layer.effect(SqliteStorageConfig)(
209
- Effect.gen(function* () {
210
- const config = yield* NodeDurableRuntimeConfig;
211
-
212
- return SqliteStorageConfigValue.make({
213
- observationPollInterval: config.observationPollInterval,
214
- busyTimeout: config.busyTimeout,
215
- ownershipLeaseDuration: config.ownershipLeaseDuration,
216
- verifyOnOpen: config.verifyOnOpen,
217
- });
218
- }),
219
- );
209
+ const sqliteStorageConfigLayer: Layer.Layer<
210
+ SqliteStorageConfig,
211
+ never,
212
+ NodeDurableAgentRuntimeConfig
213
+ > = Layer.effect(SqliteStorageConfig)(
214
+ Effect.gen(function* () {
215
+ const config = yield* NodeDurableAgentRuntimeConfig;
216
+
217
+ return SqliteStorageConfigValue.make({
218
+ observationPollInterval: config.observationPollInterval,
219
+ busyTimeout: config.busyTimeout,
220
+ ownershipLeaseDuration: config.ownershipLeaseDuration,
221
+ verifyOnOpen: config.verifyOnOpen,
222
+ });
223
+ }),
224
+ );
220
225
 
221
226
  /** Thread coordinator configuration derived from the single validated Node configuration. */
222
227
  const durableRuntimeConfigLayer = (
223
228
  estimateCostMicrousd: RunCostEstimator | undefined,
224
- ): Layer.Layer<DurableRuntimeConfig, never, NodeDurableRuntimeConfig> =>
229
+ ): Layer.Layer<DurableRuntimeConfig, never, NodeDurableAgentRuntimeConfig> =>
225
230
  Layer.effect(DurableRuntimeConfig)(
226
231
  Effect.gen(function* () {
227
- const config = yield* NodeDurableRuntimeConfig;
232
+ const config = yield* NodeDurableAgentRuntimeConfig;
228
233
 
229
234
  return DurableRuntimeConfig.make({
230
235
  deploymentId: config.deploymentId,
@@ -241,10 +246,10 @@ const durableRuntimeConfigLayer = (
241
246
  const wakeSchedulerConfigLayer: Layer.Layer<
242
247
  NodeWakeSchedulerConfig,
243
248
  never,
244
- NodeDurableRuntimeConfig
249
+ NodeDurableAgentRuntimeConfig
245
250
  > = Layer.effect(NodeWakeSchedulerConfig)(
246
251
  Effect.gen(function* () {
247
- const config = yield* NodeDurableRuntimeConfig;
252
+ const config = yield* NodeDurableAgentRuntimeConfig;
248
253
 
249
254
  return { scanInterval: Duration.millis(config.wakeScanInterval) };
250
255
  }),
@@ -373,7 +378,7 @@ export const ownershipDrainLayer: Layer.Layer<SubmissionLedger, never, Submissio
373
378
  * verified during construction: an incompatible database file fails the Layer with
374
379
  * `SqliteStorageCompatibilityError` before anything is mutated (DEPLOY-008).
375
380
  */
376
- export class NodeDurableRuntime {
381
+ export class NodeDurableAgentRuntime {
377
382
  /** Validated configuration Layer; fails typed when the supplied options are out of bounds. */
378
383
  static configLayer<
379
384
  ContextError = never,
@@ -381,14 +386,14 @@ export class NodeDurableRuntime {
381
386
  AuthorizationError = never,
382
387
  AuthorizationRequirements = never,
383
388
  >(
384
- options: NodeDurableRuntimeOptions<
389
+ options: NodeDurableAgentRuntimeOptions<
385
390
  ContextError,
386
391
  ContextRequirements,
387
392
  AuthorizationError,
388
393
  AuthorizationRequirements
389
394
  >,
390
- ): Layer.Layer<NodeDurableRuntimeConfig, NodePlatformConfigError> {
391
- return Layer.effect(NodeDurableRuntimeConfig)(configFromOptions(options));
395
+ ): Layer.Layer<NodeDurableAgentRuntimeConfig, NodePlatformConfigError> {
396
+ return Layer.effect(NodeDurableAgentRuntimeConfig)(configFromOptions(options));
392
397
  }
393
398
 
394
399
  /** The full DN runtime stack over one SQLite file. */
@@ -398,20 +403,82 @@ export class NodeDurableRuntime {
398
403
  AuthorizationError = never,
399
404
  AuthorizationRequirements = never,
400
405
  >(
401
- options: NodeDurableRuntimeOptions<
406
+ options: NodeDurableAgentRuntimeOptions<
402
407
  ContextError,
403
408
  ContextRequirements,
404
409
  AuthorizationError,
405
410
  AuthorizationRequirements
406
411
  >,
407
412
  ): Layer.Layer<
408
- NodeDurableRuntimeServices,
409
- NodeDurableRuntimeInitializationError | ContextError | AuthorizationError,
413
+ NodeDurableAgentRuntimeServices,
414
+ NodeDurableAgentRuntimeInitializationError | ContextError | AuthorizationError,
410
415
  Exclude<ContextRequirements | AuthorizationRequirements, Crypto.Crypto>
411
416
  > {
412
- return Layer.unwrap(
417
+ return NodeDurableAgentRuntime.assemble(DurableAgentRuntime.layerWithServices, options);
418
+ }
419
+
420
+ /** Own typed executable registrations for every worker using this Node runtime. */
421
+ static layerRegistered<
422
+ const Entries extends ReadonlyArray<AgentRegistration>,
423
+ ContextError = never,
424
+ ContextRequirements = never,
425
+ AuthorizationError = never,
426
+ AuthorizationRequirements = never,
427
+ >(
428
+ registrations: Entries,
429
+ options: NodeDurableAgentRuntimeOptions<
430
+ ContextError,
431
+ ContextRequirements,
432
+ AuthorizationError,
433
+ AuthorizationRequirements
434
+ >,
435
+ ) {
436
+ return NodeDurableAgentRuntime.assemble(
437
+ DurableAgentRuntime.layerRegistered(registrations),
438
+ options,
439
+ );
440
+ }
441
+
442
+ /** Construct from registrations already resolved within the enclosing application Scope. */
443
+ static layerWithBindings<
444
+ ContextError = never,
445
+ ContextRequirements = never,
446
+ AuthorizationError = never,
447
+ AuthorizationRequirements = never,
448
+ >(
449
+ bindings: ReadonlyArray<ResolvedBinding>,
450
+ options: NodeDurableAgentRuntimeOptions<
451
+ ContextError,
452
+ ContextRequirements,
453
+ AuthorizationError,
454
+ AuthorizationRequirements
455
+ >,
456
+ ) {
457
+ return NodeDurableAgentRuntime.assemble(
458
+ DurableAgentRuntime.layerWithBindings(bindings),
459
+ options,
460
+ );
461
+ }
462
+
463
+ private static assemble<
464
+ RuntimeError,
465
+ RuntimeRequirements,
466
+ ContextError,
467
+ ContextRequirements,
468
+ AuthorizationError,
469
+ AuthorizationRequirements,
470
+ >(
471
+ runtimeLayer: Layer.Layer<DurableAgentRuntime, RuntimeError, RuntimeRequirements>,
472
+ options: NodeDurableAgentRuntimeOptions<
473
+ ContextError,
474
+ ContextRequirements,
475
+ AuthorizationError,
476
+ AuthorizationRequirements
477
+ >,
478
+ ) {
479
+ const assembled = Layer.unwrap(
413
480
  Effect.map(configFromOptions(options), (config) => {
414
- const nodeConfigLayer = Layer.succeed(NodeDurableRuntimeConfig)(config);
481
+ const nodeConfigLayer = Layer.succeed(NodeDurableAgentRuntimeConfig)(config);
415
482
 
416
483
  const infrastructure = Layer.mergeAll(
417
484
  sqliteStorageConfigLayer,
@@ -443,7 +510,7 @@ export class NodeDurableRuntime {
443
510
  ),
444
511
  );
445
512
 
446
- return DurableAgentRuntime.layerWithServices.pipe(
513
+ return runtimeLayer.pipe(
447
514
  Layer.provideMerge(
448
515
  Layer.mergeAll(ports, durableRuntimeConfigLayer(options.estimateCostMicrousd)),
449
516
  ),
@@ -466,5 +533,13 @@ export class NodeDurableRuntime {
466
533
  );
467
534
  }),
468
535
  );
536
+
537
+ const runtime: Layer.Layer<
538
+ NodeDurableAgentRuntimeServices,
539
+ Layer.Error<typeof assembled>,
540
+ Layer.Services<typeof assembled>
541
+ > = assembled;
542
+
543
+ return runtime;
469
544
  }
470
545
  }