@yolk-sdk/harness 0.1.0-canary.100

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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/dist/coordinator.d.mts +72 -0
  4. package/dist/coordinator.d.mts.map +1 -0
  5. package/dist/coordinator.mjs +154 -0
  6. package/dist/coordinator.mjs.map +1 -0
  7. package/dist/driver/durable-object.d.mts +20 -0
  8. package/dist/driver/durable-object.d.mts.map +1 -0
  9. package/dist/driver/durable-object.mjs +12 -0
  10. package/dist/driver/durable-object.mjs.map +1 -0
  11. package/dist/driver/memory.d.mts +21 -0
  12. package/dist/driver/memory.d.mts.map +1 -0
  13. package/dist/driver/memory.mjs +15 -0
  14. package/dist/driver/memory.mjs.map +1 -0
  15. package/dist/driver.d.mts +126 -0
  16. package/dist/driver.d.mts.map +1 -0
  17. package/dist/driver.mjs +96 -0
  18. package/dist/driver.mjs.map +1 -0
  19. package/dist/inbox.d.mts +368 -0
  20. package/dist/inbox.d.mts.map +1 -0
  21. package/dist/inbox.mjs +235 -0
  22. package/dist/inbox.mjs.map +1 -0
  23. package/dist/index.d.mts +1 -0
  24. package/dist/index.mjs +1 -0
  25. package/dist/outcome-constructors-internal.d.mts +403 -0
  26. package/dist/outcome-constructors-internal.d.mts.map +1 -0
  27. package/dist/outcome-constructors-internal.mjs +11 -0
  28. package/dist/outcome-constructors-internal.mjs.map +1 -0
  29. package/dist/outcome.d.mts +361 -0
  30. package/dist/outcome.d.mts.map +1 -0
  31. package/dist/outcome.mjs +136 -0
  32. package/dist/outcome.mjs.map +1 -0
  33. package/dist/store.d.mts +39 -0
  34. package/dist/store.d.mts.map +1 -0
  35. package/dist/store.mjs +98 -0
  36. package/dist/store.mjs.map +1 -0
  37. package/package.json +90 -0
  38. package/src/coordinator.ts +355 -0
  39. package/src/driver/durable-object.ts +28 -0
  40. package/src/driver/memory.ts +39 -0
  41. package/src/driver.ts +270 -0
  42. package/src/inbox.ts +528 -0
  43. package/src/index.ts +2 -0
  44. package/src/outcome-constructors-internal.ts +18 -0
  45. package/src/outcome.ts +377 -0
  46. package/src/store.ts +161 -0
package/src/outcome.ts ADDED
@@ -0,0 +1,377 @@
1
+ import { Effect, Match, Predicate, Stream, type Data } from 'effect'
2
+ import {
3
+ HitlMatch as hitlMatch,
4
+ OverflowCompactionResult as overflowCompactionResult,
5
+ StepOutcome as stepOutcome
6
+ } from './outcome-constructors-internal.ts'
7
+ import {
8
+ addAgentUsage,
9
+ zeroAgentUsage,
10
+ type AgentEvent,
11
+ type AgentMessage,
12
+ type AgentUsage,
13
+ type HitlRequest,
14
+ type HitlResponse
15
+ } from '@yolk-sdk/agent/protocol'
16
+ import {
17
+ collectModelTurnAttempt,
18
+ LLMError,
19
+ runModelTurn,
20
+ runToolBatch,
21
+ type AgentLoopError,
22
+ type ContextTransformer,
23
+ type LLMProvider,
24
+ type LoopConfig,
25
+ type ModelTurnConfig,
26
+ type ModelTurnResult,
27
+ type ToolBatchConfig,
28
+ type ToolExecutor
29
+ } from '@yolk-sdk/agent/loop'
30
+ import {
31
+ applyOverflowCompaction,
32
+ isOverflowCompactionAttemptCount
33
+ } from '@yolk-sdk/agent/compaction'
34
+
35
+ export type OverflowCompactionResult =
36
+ | { readonly _tag: 'Compacted'; readonly messages: ReadonlyArray<AgentMessage> }
37
+ | { readonly _tag: 'Skipped' }
38
+
39
+ export const OverflowCompactionResult = overflowCompactionResult
40
+
41
+ export type StepOutcome = Data.TaggedEnum<{
42
+ Completed: {
43
+ readonly needsContinuation: boolean
44
+ readonly assistantMessage: ModelTurnResult['assistantMessage']
45
+ readonly toolCalls: ModelTurnResult['toolCalls']
46
+ readonly usage: ModelTurnResult['usage']
47
+ readonly stopReason: ModelTurnResult['stopReason']
48
+ }
49
+ Retry: {
50
+ readonly error: AgentLoopError
51
+ }
52
+ Continue: {
53
+ readonly error: AgentLoopError
54
+ readonly assistantMessage: ModelTurnResult['assistantMessage']
55
+ readonly toolCalls: ModelTurnResult['toolCalls']
56
+ readonly usage: ModelTurnResult['usage']
57
+ readonly stopReason: ModelTurnResult['stopReason']
58
+ }
59
+ RecoverFull: {
60
+ readonly error: AgentLoopError
61
+ }
62
+ Compacted: {
63
+ readonly messages: ReadonlyArray<AgentMessage>
64
+ readonly overflowCompactionAttempt: number
65
+ }
66
+ AwaitingInput: {
67
+ readonly requests: ReadonlyArray<HitlRequest>
68
+ readonly usage: AgentUsage
69
+ }
70
+ }>
71
+
72
+ export const StepOutcome = stepOutcome
73
+
74
+ export type CompletedTurn = Extract<StepOutcome, { readonly _tag: 'Completed' }>
75
+
76
+ export type ModelTurnOutcome = Exclude<StepOutcome, { readonly _tag: 'AwaitingInput' }>
77
+
78
+ export type ToolBatchOutcome = Extract<
79
+ StepOutcome,
80
+ { readonly _tag: 'Completed' | 'AwaitingInput' }
81
+ >
82
+
83
+ const completedOutcome = (result: ModelTurnResult): CompletedTurn =>
84
+ StepOutcome.Completed({
85
+ needsContinuation: result.stopReason === 'tool_use',
86
+ assistantMessage: result.assistantMessage,
87
+ toolCalls: result.toolCalls,
88
+ usage: result.usage,
89
+ stopReason: result.stopReason
90
+ })
91
+
92
+ const modelTurnResult = (result: ModelTurnResult): ModelTurnResult => ({
93
+ assistantMessage: result.assistantMessage,
94
+ toolCalls: result.toolCalls,
95
+ usage: result.usage,
96
+ stopReason: result.stopReason
97
+ })
98
+
99
+ const invalidOverflowCompactionAttemptError = () =>
100
+ new LLMError({
101
+ cause: 'validation_error',
102
+ message: 'overflowCompactionAttempt must be a finite integer >= 0',
103
+ retryable: false
104
+ })
105
+
106
+ const isAgentLoopError = (error: unknown): error is AgentLoopError => {
107
+ if (!Predicate.isObjectOrArray(error) || !('_tag' in error)) {
108
+ return false
109
+ }
110
+
111
+ const tag = error._tag
112
+
113
+ return (
114
+ tag === 'LLMError' ||
115
+ tag === 'FauxExhaustedError' ||
116
+ tag === 'ToolError' ||
117
+ tag === 'ContextTransformError' ||
118
+ tag === 'AbortError'
119
+ )
120
+ }
121
+
122
+ const isOverflow = (error: AgentLoopError) =>
123
+ Match.value(error).pipe(
124
+ Match.tag('LLMError', 'ContextTransformError', current => current.cause === 'context_overflow'),
125
+ Match.orElse(() => false)
126
+ )
127
+
128
+ const isMissingDone = (error: AgentLoopError) =>
129
+ Match.value(error).pipe(
130
+ Match.tag('LLMError', current => current.responseIssue === 'missing_done'),
131
+ Match.orElse(() => false)
132
+ )
133
+
134
+ const isRetryableLlm = (
135
+ error: AgentLoopError
136
+ ): error is Extract<AgentLoopError, { _tag: 'LLMError' }> =>
137
+ Predicate.isTagged(error, 'LLMError') && error.retryable
138
+
139
+ const classifyModelTurnFailure = <E2, R2>(input: {
140
+ readonly error: AgentLoopError
141
+ readonly collected: ModelTurnResult
142
+ readonly outputStarted: boolean
143
+ readonly messages: ReadonlyArray<AgentMessage>
144
+ readonly overflowCompactionAttempt: number
145
+ readonly compact?: (
146
+ messages: ReadonlyArray<AgentMessage>
147
+ ) => Effect.Effect<OverflowCompactionResult, E2, R2>
148
+ }): Effect.Effect<ModelTurnOutcome, AgentLoopError | E2, R2> => {
149
+ if (isOverflow(input.error)) {
150
+ if (input.compact === undefined) {
151
+ return Effect.fail(input.error)
152
+ }
153
+
154
+ return applyOverflowCompaction({
155
+ compact: input.compact,
156
+ messages: input.messages,
157
+ attempt: input.overflowCompactionAttempt,
158
+ outputStarted: input.outputStarted
159
+ }).pipe(
160
+ Effect.flatMap(result =>
161
+ Match.value(result).pipe(
162
+ Match.tag('Compacted', current =>
163
+ Effect.succeed(
164
+ StepOutcome.Compacted({
165
+ messages: current.messages,
166
+ overflowCompactionAttempt: input.overflowCompactionAttempt + 1
167
+ })
168
+ )
169
+ ),
170
+ Match.orElse(() => Effect.fail(input.error))
171
+ )
172
+ )
173
+ )
174
+ }
175
+
176
+ if (isMissingDone(input.error) && !input.outputStarted) {
177
+ return Effect.succeed(StepOutcome.RecoverFull({ error: input.error }))
178
+ }
179
+
180
+ if (isMissingDone(input.error) && input.outputStarted) {
181
+ return Effect.succeed(
182
+ StepOutcome.Continue({
183
+ error: input.error,
184
+ ...modelTurnResult(input.collected)
185
+ })
186
+ )
187
+ }
188
+
189
+ if (isRetryableLlm(input.error) && !input.outputStarted) {
190
+ if (input.error.cause === 'invalid_response') {
191
+ return Effect.succeed(StepOutcome.RecoverFull({ error: input.error }))
192
+ }
193
+
194
+ return Effect.succeed(StepOutcome.Retry({ error: input.error }))
195
+ }
196
+
197
+ if (isRetryableLlm(input.error) && input.outputStarted) {
198
+ return Effect.succeed(
199
+ StepOutcome.Continue({
200
+ error: input.error,
201
+ ...modelTurnResult(input.collected)
202
+ })
203
+ )
204
+ }
205
+
206
+ return Effect.fail(input.error)
207
+ }
208
+
209
+ export const attemptModelTurn = <E2 = never, R2 = never>(
210
+ config: ModelTurnConfig,
211
+ options?: {
212
+ readonly onEvent?: (event: AgentEvent) => Effect.Effect<void, E2, R2>
213
+ readonly initialUsage?: AgentUsage
214
+ readonly overflowCompactionAttempt?: number
215
+ readonly compact?: (
216
+ messages: ReadonlyArray<AgentMessage>
217
+ ) => Effect.Effect<OverflowCompactionResult, E2, R2>
218
+ }
219
+ ): Effect.Effect<
220
+ ModelTurnOutcome,
221
+ AgentLoopError | E2,
222
+ ContextTransformer | LLMProvider | LoopConfig | R2
223
+ > =>
224
+ Effect.gen(function* () {
225
+ const overflowCompactionAttempt = options?.overflowCompactionAttempt ?? 0
226
+
227
+ if (!isOverflowCompactionAttemptCount(overflowCompactionAttempt)) {
228
+ return yield* Effect.fail(invalidOverflowCompactionAttemptError())
229
+ }
230
+
231
+ const outcome = yield* collectModelTurnAttempt(runModelTurn(config), {
232
+ onEvent: options?.onEvent,
233
+ initialUsage: options?.initialUsage
234
+ })
235
+
236
+ return yield* Match.value(outcome).pipe(
237
+ Match.tag('Collected', current => Effect.succeed(completedOutcome(current.collection))),
238
+ Match.tag('SinkFailed', current => Effect.fail(current.error)),
239
+ Match.tag('StreamFailed', current => {
240
+ if (!isAgentLoopError(current.error)) {
241
+ return Effect.fail(current.error)
242
+ }
243
+
244
+ return classifyModelTurnFailure({
245
+ error: current.error,
246
+ collected: {
247
+ assistantMessage:
248
+ current.collection.assistantMessage ?? current.collection.partialAssistantMessage,
249
+ toolCalls: current.collection.toolCalls,
250
+ usage: current.collection.usage,
251
+ stopReason: current.collection.stopReason
252
+ },
253
+ outputStarted: current.collection.outputStarted,
254
+ messages: config.messages,
255
+ overflowCompactionAttempt,
256
+ compact: options?.compact
257
+ })
258
+ }),
259
+ Match.exhaustive
260
+ )
261
+ })
262
+
263
+ type AttemptToolBatchFoldState = {
264
+ requests: ReadonlyArray<HitlRequest>
265
+ usage: AgentUsage
266
+ toolCalls: ModelTurnResult['toolCalls']
267
+ }
268
+
269
+ export const attemptToolBatch = <E2 = never, R2 = never>(
270
+ config: ToolBatchConfig,
271
+ options?: {
272
+ readonly onEvent?: (event: AgentEvent) => Effect.Effect<void, E2, R2>
273
+ }
274
+ ): Effect.Effect<ToolBatchOutcome, AgentLoopError | E2, LoopConfig | ToolExecutor | R2> => {
275
+ const onEvent = options?.onEvent
276
+
277
+ return runToolBatch(config).pipe(
278
+ Stream.runFoldEffect(
279
+ (): AttemptToolBatchFoldState => ({
280
+ requests: [],
281
+ usage: config.usage ?? zeroAgentUsage,
282
+ toolCalls: []
283
+ }),
284
+ (acc, event) => {
285
+ const next = Predicate.isTagged(event, 'AgentAwaitingInput')
286
+ ? { ...acc, requests: event.requests, usage: event.usage }
287
+ : Predicate.isTagged(event, 'UsageUpdate')
288
+ ? { ...acc, usage: addAgentUsage(acc.usage, event.usage) }
289
+ : Predicate.isTagged(event, 'ToolExecutionCompleted') ||
290
+ Predicate.isTagged(event, 'ToolExecutionAccepted')
291
+ ? { ...acc, toolCalls: [...acc.toolCalls, event.call] }
292
+ : acc
293
+
294
+ return onEvent === undefined ? Effect.succeed(next) : onEvent(event).pipe(Effect.as(next))
295
+ }
296
+ ),
297
+ Effect.map((result): ToolBatchOutcome => {
298
+ if (result.requests.length === 0) {
299
+ const needsContinuation = result.toolCalls.length > 0
300
+
301
+ return StepOutcome.Completed({
302
+ needsContinuation,
303
+ assistantMessage: undefined,
304
+ toolCalls: result.toolCalls,
305
+ usage: result.usage,
306
+ stopReason: needsContinuation ? 'tool_use' : 'stop'
307
+ })
308
+ }
309
+
310
+ return StepOutcome.AwaitingInput({ requests: result.requests, usage: result.usage })
311
+ })
312
+ )
313
+ }
314
+
315
+ export type HitlMatch =
316
+ | { readonly _tag: 'Match'; readonly requestId: string }
317
+ | { readonly _tag: 'Mismatch' }
318
+
319
+ export const HitlMatch = hitlMatch
320
+
321
+ const hitlResponseMatchesRequest = (response: HitlResponse, request: HitlRequest) =>
322
+ Match.value(response).pipe(
323
+ Match.tag(
324
+ 'ToolApprovalResponse',
325
+ current =>
326
+ Predicate.isTagged(request, 'ToolApprovalRequest') &&
327
+ current.requestId === request.requestId &&
328
+ current.toolCallId === request.toolCallId
329
+ ),
330
+ Match.tag(
331
+ 'QuestionResponse',
332
+ current =>
333
+ Predicate.isTagged(request, 'QuestionRequest') &&
334
+ current.requestId === request.requestId &&
335
+ current.toolCallId === request.toolCallId
336
+ ),
337
+ Match.tag(
338
+ 'InputResponse',
339
+ current =>
340
+ Predicate.isTagged(request, 'InputRequest') &&
341
+ current.requestId === request.requestId &&
342
+ current.toolCallId === request.toolCallId
343
+ ),
344
+ Match.tag(
345
+ 'InteractionResponse',
346
+ current =>
347
+ Predicate.isTagged(request, 'InteractionRequest') &&
348
+ current.requestId === request.requestId &&
349
+ current.toolCallId === request.toolCallId
350
+ ),
351
+ Match.exhaustive
352
+ )
353
+
354
+ export const matchHitlResponse = (
355
+ pending: ReadonlyArray<HitlRequest>,
356
+ response: HitlResponse
357
+ ): HitlMatch => {
358
+ const matched = pending.find(request => hitlResponseMatchesRequest(response, request))
359
+
360
+ return matched === undefined
361
+ ? HitlMatch.Mismatch()
362
+ : HitlMatch.Match({ requestId: matched.requestId })
363
+ }
364
+
365
+ export const resumeHitlIfMatched = <A, E, R>(input: {
366
+ readonly pending: ReadonlyArray<HitlRequest>
367
+ readonly response: HitlResponse
368
+ readonly resume: (requestId: string) => Effect.Effect<A, E, R>
369
+ }): Effect.Effect<A | Extract<HitlMatch, { readonly _tag: 'Mismatch' }>, E, R> => {
370
+ const matched = matchHitlResponse(input.pending, input.response)
371
+
372
+ return Match.value(matched).pipe(
373
+ Match.tag('Mismatch', current => Effect.succeed(current)),
374
+ Match.tag('Match', current => input.resume(current.requestId)),
375
+ Match.exhaustive
376
+ )
377
+ }
package/src/store.ts ADDED
@@ -0,0 +1,161 @@
1
+ import { Context, Effect, Layer, Ref, Semaphore } from 'effect'
2
+
3
+ export type RunStoreApi = {
4
+ readonly claim: (runId: string) => Effect.Effect<void>
5
+ readonly release: (runId: string) => Effect.Effect<void>
6
+ readonly isClaimed: (runId: string) => Effect.Effect<boolean>
7
+ readonly claimed: Effect.Effect<ReadonlySet<string>>
8
+ readonly incrementResumeCount: (runId: string) => Effect.Effect<number>
9
+ readonly resumeCount: (runId: string) => Effect.Effect<number>
10
+ }
11
+
12
+ export type SnapshotRunStoreOptions = {
13
+ readonly load: Effect.Effect<DurableRunStoreSnapshot | undefined>
14
+ readonly save: (snapshot: DurableRunStoreSnapshot) => Effect.Effect<void>
15
+ }
16
+
17
+ export type DurableRunStoreSnapshot = {
18
+ readonly claimed: ReadonlyArray<string>
19
+ readonly resumes: ReadonlyArray<readonly [string, number]>
20
+ }
21
+
22
+ type MemorySnapshot = {
23
+ readonly claimed: ReadonlySet<string>
24
+ readonly resumes: ReadonlyMap<string, number>
25
+ }
26
+
27
+ const emptySnapshot: DurableRunStoreSnapshot = {
28
+ claimed: [],
29
+ resumes: []
30
+ }
31
+
32
+ const snapshotFromMemory = (memory: MemorySnapshot): DurableRunStoreSnapshot => ({
33
+ claimed: [...memory.claimed],
34
+ resumes: [...memory.resumes.entries()]
35
+ })
36
+
37
+ const memoryFromSnapshot = (snapshot: DurableRunStoreSnapshot): MemorySnapshot => ({
38
+ claimed: new Set(snapshot.claimed),
39
+ resumes: new Map(snapshot.resumes)
40
+ })
41
+
42
+ export class RunStore extends Context.Service<RunStore, RunStoreApi>()(
43
+ '@yolk-sdk/harness/RunStore'
44
+ ) {
45
+ /**
46
+ * Canonical owning layer for snapshot-backed claims. Each call builds a fresh layer so
47
+ * composed harnesses never share `Ref` state or the snapshot lock across factory calls.
48
+ */
49
+ static snapshotLayer = (options: SnapshotRunStoreOptions): Layer.Layer<RunStore> =>
50
+ Layer.effect(
51
+ this,
52
+ Effect.gen(function* () {
53
+ const loaded = yield* options.load
54
+ const memory = yield* Ref.make(memoryFromSnapshot(loaded ?? emptySnapshot))
55
+ const lock = yield* Semaphore.make(1)
56
+
57
+ const mutate = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
58
+ Effect.uninterruptibleMask(restore =>
59
+ restore(lock.take(1)).pipe(
60
+ Effect.flatMap(() => effect.pipe(Effect.ensuring(lock.release(1))))
61
+ )
62
+ )
63
+
64
+ const commit = (next: MemorySnapshot) =>
65
+ options.save(snapshotFromMemory(next)).pipe(Effect.flatMap(() => Ref.set(memory, next)))
66
+
67
+ return RunStore.of({
68
+ claim: runId =>
69
+ mutate(
70
+ Effect.gen(function* () {
71
+ const current = yield* Ref.get(memory)
72
+ yield* commit({
73
+ claimed: new Set(current.claimed).add(runId),
74
+ resumes: current.resumes
75
+ })
76
+ })
77
+ ),
78
+ release: runId =>
79
+ mutate(
80
+ Effect.gen(function* () {
81
+ const current = yield* Ref.get(memory)
82
+ const claimed = new Set(current.claimed)
83
+ claimed.delete(runId)
84
+ const resumes = new Map(current.resumes)
85
+ resumes.delete(runId)
86
+ yield* commit({ claimed, resumes })
87
+ })
88
+ ),
89
+ isClaimed: runId =>
90
+ Ref.get(memory).pipe(Effect.map(current => current.claimed.has(runId))),
91
+ claimed: Ref.get(memory).pipe(Effect.map(current => new Set(current.claimed))),
92
+ incrementResumeCount: runId =>
93
+ mutate(
94
+ Effect.gen(function* () {
95
+ const current = yield* Ref.get(memory)
96
+ const nextCount = (current.resumes.get(runId) ?? 0) + 1
97
+ const resumes = new Map(current.resumes)
98
+ resumes.set(runId, nextCount)
99
+ yield* commit({ claimed: current.claimed, resumes })
100
+
101
+ return nextCount
102
+ })
103
+ ),
104
+ resumeCount: runId =>
105
+ Ref.get(memory).pipe(Effect.map(current => current.resumes.get(runId) ?? 0))
106
+ })
107
+ })
108
+ )
109
+
110
+ /**
111
+ * Canonical owning layer for process-local claims. Each call builds a fresh layer so
112
+ * composed harnesses never share `Ref` state across factory calls.
113
+ */
114
+ static inMemoryLayer = (): Layer.Layer<RunStore> =>
115
+ Layer.effect(
116
+ this,
117
+ Effect.gen(function* () {
118
+ const claimed = yield* Ref.make(new Set<string>())
119
+ const resumes = yield* Ref.make(new Map<string, number>())
120
+
121
+ return RunStore.of({
122
+ claim: runId => Ref.update(claimed, current => new Set(current).add(runId)),
123
+ release: runId =>
124
+ Effect.zip(
125
+ Ref.update(claimed, current => {
126
+ const next = new Set(current)
127
+ next.delete(runId)
128
+
129
+ return next
130
+ }),
131
+ Ref.update(resumes, current => {
132
+ const next = new Map(current)
133
+ next.delete(runId)
134
+
135
+ return next
136
+ })
137
+ ).pipe(Effect.asVoid),
138
+ isClaimed: runId => Ref.get(claimed).pipe(Effect.map(current => current.has(runId))),
139
+ claimed: Ref.get(claimed).pipe(Effect.map(current => new Set(current))),
140
+ incrementResumeCount: runId =>
141
+ Ref.modify(resumes, current => {
142
+ const nextCount = (current.get(runId) ?? 0) + 1
143
+ const next = new Map(current)
144
+ next.set(runId, nextCount)
145
+
146
+ return [nextCount, next] as const
147
+ }),
148
+ resumeCount: runId =>
149
+ Ref.get(resumes).pipe(Effect.map(current => current.get(runId) ?? 0))
150
+ })
151
+ })
152
+ )
153
+ }
154
+
155
+ /** Backward-compatible delegation to {@link RunStore.snapshotLayer}. */
156
+ export const makeSnapshotRunStoreLayer = (
157
+ options: SnapshotRunStoreOptions
158
+ ): Layer.Layer<RunStore> => RunStore.snapshotLayer(options)
159
+
160
+ /** Backward-compatible delegation to {@link RunStore.inMemoryLayer}. */
161
+ export const makeInMemoryRunStoreLayer = (): Layer.Layer<RunStore> => RunStore.inMemoryLayer()