@yielded/agent-testing 0.1.0-beta.173 → 0.1.0-beta.174

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.
@@ -138,38 +138,37 @@ const summaryParts = (documentId: string): ReadonlyArray<Response.StreamPartEnco
138
138
  * Attempt may resume on a fresh Layer build, so responses derive from the
139
139
  * committed history in the prompt — never from an in-Layer turn counter.
140
140
  */
141
- const makeCountingModel = (
141
+ const makeCountingModel = Effect.fnUntraced(function* (
142
142
  name: string,
143
143
  decide: (promptJson: string) => Effect.Effect<ReadonlyArray<Response.StreamPartEncoded>>,
144
- ) =>
145
- Effect.gen(function* () {
146
- const calls = yield* Ref.make(0);
147
- const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
148
-
149
- const model = Model.make(
150
- "scripted",
151
- name,
152
- Layer.effect(
153
- LanguageModel.LanguageModel,
154
- LanguageModel.make({
155
- generateText: () => Effect.succeed([]),
156
- streamText: (request) =>
157
- Stream.unwrap(
158
- Effect.gen(function* () {
159
- yield* Ref.update(calls, (value) => value + 1);
160
- const promptJson = JSON.stringify(request.prompt);
161
-
162
- yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
163
-
164
- return Stream.fromIterable(yield* decide(promptJson));
165
- }),
166
- ),
167
- }),
168
- ),
169
- );
144
+ ) {
145
+ const calls = yield* Ref.make(0);
146
+ const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
147
+
148
+ const model = Model.make(
149
+ "scripted",
150
+ name,
151
+ Layer.effect(
152
+ LanguageModel.LanguageModel,
153
+ LanguageModel.make({
154
+ generateText: () => Effect.succeed([]),
155
+ streamText: (request) =>
156
+ Stream.unwrap(
157
+ Effect.gen(function* () {
158
+ yield* Ref.update(calls, (value) => value + 1);
159
+ const promptJson = JSON.stringify(request.prompt);
160
+
161
+ yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
162
+
163
+ return Stream.fromIterable(yield* decide(promptJson));
164
+ }),
165
+ ),
166
+ }),
167
+ ),
168
+ );
170
169
 
171
- return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
172
- });
170
+ return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
171
+ });
173
172
 
174
173
  /** Optional overrides for one docs-researcher harness. */
175
174
  export interface DocsResearcherHarnessOptions {
@@ -204,98 +203,100 @@ export interface DocsResearcherHarness {
204
203
  * at assembly, not assumed. Content-tool execution then flows through the
205
204
  * counting `DocumentLibrary` — the scripted MCP server's content store.
206
205
  */
207
- export const makeDocsResearcherHarness = (options?: DocsResearcherHarnessOptions) =>
208
- Effect.gen(function* () {
209
- const documentIds = options?.documentIds ?? researchCorpusDocumentIds;
210
-
211
- // MCP discovery gate (CAP-009): bounded, digest-verified, fail-closed.
212
- const discovery = yield* Effect.scoped(
213
- Effect.gen(function* () {
214
- const connection = yield* connectMcp(docsMcpRequest);
215
-
216
- yield* assertDiscoveryMatchesAuthoredToolkit(connection);
217
-
218
- return connection.discovery;
219
- }),
220
- ).pipe(Effect.provide(docsMcpConnectorLayer));
221
-
222
- const fetchCounts = yield* Ref.make<ReadonlyMap<string, number>>(new Map());
223
-
224
- const libraryLayer = Layer.succeed(
225
- DocumentLibrary,
226
- DocumentLibrary.of({
227
- fetch: (query) =>
228
- Ref.update(fetchCounts, (current) =>
229
- new Map(current).set(query.documentId, (current.get(query.documentId) ?? 0) + 1),
230
- ).pipe(Effect.andThen(researchDocumentLookup(query))),
231
- }),
232
- );
233
-
234
- const childToolkitLayer = docContentToolkitLayer.pipe(Layer.provideMerge(libraryLayer));
235
-
236
- const childModel = yield* makeCountingModel("doc-summarizer-p7", (promptJson) =>
237
- Effect.suspend(() => {
238
- const documentId = documentIds.find((candidate) => promptJson.includes(candidate));
239
-
240
- if (documentId === undefined) {
241
- return Effect.die(new Error("The summarizer prompt names no corpus document"));
242
- }
243
-
244
- return Effect.succeed(
245
- promptJson.includes(fetchCallId(documentId))
246
- ? summaryParts(documentId)
247
- : fetchParts(documentId),
248
- );
249
- }),
250
- );
251
-
252
- const childBinding = Agent.withModel(DocSummarizer, childModel.model);
253
-
254
- const firstCallId = summarizeCallId(documentIds[0] ?? "durability-notes");
255
-
256
- const parentModel = yield* makeCountingModel("docs-researcher-p7", (promptJson) =>
257
- Effect.succeed(
258
- promptJson.includes(firstCallId)
259
- ? digestParts(documentIds)
260
- : summaryDelegationParts(documentIds),
206
+ export const makeDocsResearcherHarness = Effect.fnUntraced(function* (
207
+ options?: DocsResearcherHarnessOptions,
208
+ ) {
209
+ const documentIds = options?.documentIds ?? researchCorpusDocumentIds;
210
+
211
+ // MCP discovery gate (CAP-009): bounded, digest-verified, fail-closed.
212
+ const discovery = yield* Effect.scoped(
213
+ Effect.gen(function* () {
214
+ const connection = yield* connectMcp(docsMcpRequest);
215
+
216
+ yield* assertDiscoveryMatchesAuthoredToolkit(connection);
217
+
218
+ return connection.discovery;
219
+ }),
220
+ ).pipe(Effect.provide(docsMcpConnectorLayer));
221
+
222
+ const fetchCounts = yield* Ref.make<ReadonlyMap<string, number>>(new Map());
223
+
224
+ const libraryLayer = Layer.succeed(
225
+ DocumentLibrary,
226
+ DocumentLibrary.of({
227
+ fetch: (query) =>
228
+ Ref.update(fetchCounts, (current) =>
229
+ new Map(current).set(query.documentId, (current.get(query.documentId) ?? 0) + 1),
230
+ ).pipe(Effect.andThen(researchDocumentLookup(query))),
231
+ }),
232
+ );
233
+
234
+ const childToolkitLayer = docContentToolkitLayer.pipe(Layer.provideMerge(libraryLayer));
235
+
236
+ const childModel = yield* makeCountingModel("doc-summarizer-p7", (promptJson) =>
237
+ Effect.suspend(() => {
238
+ const documentId = documentIds.find((candidate) => promptJson.includes(candidate));
239
+
240
+ if (documentId === undefined) {
241
+ // oxlint-disable-next-line effect/no-native-error -- Deterministic model fixture assertion remains a native defect.
242
+ return Effect.die(new Error("The summarizer prompt names no corpus document"));
243
+ }
244
+
245
+ return Effect.succeed(
246
+ promptJson.includes(fetchCallId(documentId))
247
+ ? summaryParts(documentId)
248
+ : fetchParts(documentId),
249
+ );
250
+ }),
251
+ );
252
+
253
+ const childBinding = Agent.withModel(DocSummarizer, childModel.model);
254
+
255
+ const firstCallId = summarizeCallId(documentIds[0] ?? "durability-notes");
256
+
257
+ const parentModel = yield* makeCountingModel("docs-researcher-p7", (promptJson) =>
258
+ Effect.succeed(
259
+ promptJson.includes(firstCallId)
260
+ ? digestParts(documentIds)
261
+ : summaryDelegationParts(documentIds),
262
+ ),
263
+ );
264
+
265
+ const parentBinding = Agent.withModel(DocsResearcher, parentModel.model);
266
+
267
+ const delegationLayer = docsSummaryHandlersLayer(childBinding).pipe(
268
+ Layer.provide(
269
+ Layer.mergeAll(
270
+ childToolkitLayer,
271
+ SubagentReservationsMemoryLive,
272
+ DeterministicIdGeneratorLayer,
261
273
  ),
262
- );
263
-
264
- const parentBinding = Agent.withModel(DocsResearcher, parentModel.model);
265
-
266
- const delegationLayer = docsSummaryHandlersLayer(childBinding).pipe(
267
- Layer.provide(
268
- Layer.mergeAll(
269
- childToolkitLayer,
270
- SubagentReservationsMemoryLive,
271
- DeterministicIdGeneratorLayer,
272
- ),
273
- ),
274
- );
275
-
276
- const parentResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
277
- parentBinding,
278
- docsCoordinatorDigests,
279
- ).pipe(Effect.provide(delegationLayer));
280
-
281
- const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
282
- childBinding,
283
- docsSummarizerDigests,
284
- ).pipe(Effect.provide(childToolkitLayer));
285
-
286
- const harness: DocsResearcherHarness = {
287
- bindings: [parentResolved, childResolved],
288
- discovery,
289
- parentModelCalls: parentModel.calls,
290
- parentPrompts: parentModel.prompts,
291
- childModelCalls: childModel.calls,
292
- childPrompts: childModel.prompts,
293
- fetchInvocations: (documentId) =>
294
- Ref.get(fetchCounts).pipe(Effect.map((current) => current.get(documentId) ?? 0)),
295
- };
296
-
297
- return harness;
298
- });
274
+ ),
275
+ );
276
+
277
+ const parentResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
278
+ parentBinding,
279
+ docsCoordinatorDigests,
280
+ ).pipe(Effect.provide(delegationLayer));
281
+
282
+ const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
283
+ childBinding,
284
+ docsSummarizerDigests,
285
+ ).pipe(Effect.provide(childToolkitLayer));
286
+
287
+ const harness: DocsResearcherHarness = {
288
+ bindings: [parentResolved, childResolved],
289
+ discovery,
290
+ parentModelCalls: parentModel.calls,
291
+ parentPrompts: parentModel.prompts,
292
+ childModelCalls: childModel.calls,
293
+ childPrompts: childModel.prompts,
294
+ fetchInvocations: (documentId) =>
295
+ Ref.get(fetchCounts).pipe(Effect.map((current) => current.get(documentId) ?? 0)),
296
+ };
297
+
298
+ return harness;
299
+ });
299
300
 
300
301
  const encodeResearchDocument = Schema.encodeEffect(ResearchDocument);
301
302
 
@@ -403,21 +403,20 @@ export class SupplierBookingDesk extends Context.Service<
403
403
  bookings: Ref.get(state).pipe(Effect.map((current) => [...current.bookings.values()])),
404
404
  callCount: (idempotencyKey) =>
405
405
  Ref.get(state).pipe(Effect.map((current) => current.counts.get(idempotencyKey) ?? 0)),
406
- holdAfterWrite: (idempotencyKey) =>
407
- Effect.gen(function* () {
408
- const held = yield* Deferred.make<void>();
409
- const release = yield* Deferred.make<void>();
410
-
411
- yield* Ref.update(state, (current) => ({
412
- ...current,
413
- holds: new Map(current.holds).set(idempotencyKey, { held, release }),
414
- }));
415
-
416
- return {
417
- held: Deferred.await(held),
418
- release: Deferred.succeed(release, undefined).pipe(Effect.asVoid),
419
- };
420
- }),
406
+ holdAfterWrite: Effect.fnUntraced(function* (idempotencyKey) {
407
+ const held = yield* Deferred.make<void>();
408
+ const release = yield* Deferred.make<void>();
409
+
410
+ yield* Ref.update(state, (current) => ({
411
+ ...current,
412
+ holds: new Map(current.holds).set(idempotencyKey, { held, release }),
413
+ }));
414
+
415
+ return {
416
+ held: Deferred.await(held),
417
+ release: Deferred.succeed(release, undefined).pipe(Effect.asVoid),
418
+ };
419
+ }),
421
420
  });
422
421
  }),
423
422
  );
@@ -225,74 +225,71 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
225
225
  Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(LodgingQuery.make(query))),
226
226
  search_activities: (query) =>
227
227
  Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(ActivityQuery.make(query))),
228
- book_flight: (request, context) =>
229
- Effect.gen(function* () {
230
- const desk = yield* SupplierBookingDesk;
231
- const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
232
-
233
- const record = yield* desk.book({
234
- operation: "book-flight",
235
- idempotencyKey: bookFlightIdempotencyKey(toolCallId),
236
- detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`,
237
- });
238
-
239
- return SupplierBookingConfirmation.make({
240
- bookingRef: record.bookingRef,
241
- status: "confirmed",
242
- detail: record.detail,
243
- });
244
- }),
245
- cancel_booking: (request) =>
246
- Effect.gen(function* () {
247
- const desk = yield* SupplierBookingDesk;
248
- const record = yield* desk.cancel(request.bookingRef);
249
-
250
- return CancellationConfirmation.make({
251
- bookingRef: record.bookingRef,
252
- status: "cancelled",
253
- });
254
- }),
255
- book_itinerary: (request, context) =>
256
- Effect.gen(function* () {
257
- const desk = yield* SupplierBookingDesk;
258
- const step = yield* DurableStep;
259
- const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
260
-
261
- const bookStep = (
262
- stepName: "reserve-flight" | "reserve-lodging" | "issue-confirmation",
263
- detail: string,
264
- ) =>
265
- step.do(
266
- stepName,
267
- SupplierBookingRecord,
268
- desk.book({
269
- operation: stepName,
270
- idempotencyKey: itineraryStepIdempotencyKey(toolCallId, stepName),
271
- detail,
272
- }),
273
- );
274
-
275
- const flight = yield* bookStep(
276
- "reserve-flight",
277
- `flight ${request.quoteId} for ${request.travelerRef}`,
278
- );
228
+ book_flight: Effect.fnUntraced(function* (request, context) {
229
+ const desk = yield* SupplierBookingDesk;
230
+ const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
279
231
 
280
- const lodging = yield* bookStep(
281
- "reserve-lodging",
282
- `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`,
283
- );
232
+ const record = yield* desk.book({
233
+ operation: "book-flight",
234
+ idempotencyKey: bookFlightIdempotencyKey(toolCallId),
235
+ detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`,
236
+ });
237
+
238
+ return SupplierBookingConfirmation.make({
239
+ bookingRef: record.bookingRef,
240
+ status: "confirmed",
241
+ detail: record.detail,
242
+ });
243
+ }),
244
+ cancel_booking: Effect.fnUntraced(function* (request) {
245
+ const desk = yield* SupplierBookingDesk;
246
+ const record = yield* desk.cancel(request.bookingRef);
284
247
 
285
- const confirmation = yield* bookStep(
286
- "issue-confirmation",
287
- `itinerary confirmation for ${request.travelerRef}`,
248
+ return CancellationConfirmation.make({
249
+ bookingRef: record.bookingRef,
250
+ status: "cancelled",
251
+ });
252
+ }),
253
+ book_itinerary: Effect.fnUntraced(function* (request, context) {
254
+ const desk = yield* SupplierBookingDesk;
255
+ const step = yield* DurableStep;
256
+ const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
257
+
258
+ const bookStep = (
259
+ stepName: "reserve-flight" | "reserve-lodging" | "issue-confirmation",
260
+ detail: string,
261
+ ) =>
262
+ step.do(
263
+ stepName,
264
+ SupplierBookingRecord,
265
+ desk.book({
266
+ operation: stepName,
267
+ idempotencyKey: itineraryStepIdempotencyKey(toolCallId, stepName),
268
+ detail,
269
+ }),
288
270
  );
289
271
 
290
- return ItineraryConfirmation.make({
291
- flightBookingRef: flight.bookingRef,
292
- lodgingBookingRef: lodging.bookingRef,
293
- confirmationCode: confirmation.bookingRef,
294
- });
295
- }),
272
+ const flight = yield* bookStep(
273
+ "reserve-flight",
274
+ `flight ${request.quoteId} for ${request.travelerRef}`,
275
+ );
276
+
277
+ const lodging = yield* bookStep(
278
+ "reserve-lodging",
279
+ `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`,
280
+ );
281
+
282
+ const confirmation = yield* bookStep(
283
+ "issue-confirmation",
284
+ `itinerary confirmation for ${request.travelerRef}`,
285
+ );
286
+
287
+ return ItineraryConfirmation.make({
288
+ flightBookingRef: flight.bookingRef,
289
+ lodgingBookingRef: lodging.bookingRef,
290
+ confirmationCode: confirmation.bookingRef,
291
+ });
292
+ }),
296
293
  });
297
294
 
298
295
  const decodePersistedJson = Schema.decodeUnknownEffect(PersistedJson);
@@ -150,40 +150,39 @@ export const encodedDestinationFacts = (destination: string): unknown => {
150
150
  const scriptedUsage = { inputTokens: { total: 96 }, outputTokens: { total: 64 } };
151
151
 
152
152
  /** One scripted model whose behavior is keyed by the global invocation index. */
153
- export const makeInvocationCountingModel = (
153
+ export const makeInvocationCountingModel = Effect.fnUntraced(function* (
154
154
  name: string,
155
155
  script: (call: number) => ReadonlyArray<Response.StreamPartEncoded>,
156
- ) =>
157
- Effect.gen(function* () {
158
- const calls = yield* Ref.make(0);
159
- const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
160
-
161
- const model = Model.make(
162
- "scripted",
163
- name,
164
- Layer.effect(
165
- LanguageModel.LanguageModel,
166
- LanguageModel.make({
167
- generateText: () => Effect.succeed([]),
168
- streamText: (request) =>
169
- Stream.unwrap(
170
- Effect.gen(function* () {
171
- const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
172
-
173
- yield* Ref.update(prompts, (previous) => [
174
- ...previous,
175
- JSON.stringify(request.prompt.content),
176
- ]);
177
-
178
- return Stream.fromIterable(script(call));
179
- }),
180
- ),
181
- }),
182
- ),
183
- );
156
+ ) {
157
+ const calls = yield* Ref.make(0);
158
+ const prompts = yield* Ref.make<ReadonlyArray<string>>([]);
159
+
160
+ const model = Model.make(
161
+ "scripted",
162
+ name,
163
+ Layer.effect(
164
+ LanguageModel.LanguageModel,
165
+ LanguageModel.make({
166
+ generateText: () => Effect.succeed([]),
167
+ streamText: (request) =>
168
+ Stream.unwrap(
169
+ Effect.gen(function* () {
170
+ const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
171
+
172
+ yield* Ref.update(prompts, (previous) => [
173
+ ...previous,
174
+ JSON.stringify(request.prompt.content),
175
+ ]);
176
+
177
+ return Stream.fromIterable(script(call));
178
+ }),
179
+ ),
180
+ }),
181
+ ),
182
+ );
184
183
 
185
- return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
186
- });
184
+ return { model, calls: Ref.get(calls), prompts: Ref.get(prompts) };
185
+ });
187
186
 
188
187
  const delegationTurnParts = (
189
188
  toolCallId: string,
@@ -296,70 +295,69 @@ export interface DurableResearchHarness {
296
295
  * registered with several `NodeDurableAgentRuntime` stacks over the same SQLite
297
296
  * file while the counters keep counting across all of them.
298
297
  */
299
- export const makeDurableResearchHarness = (options?: DurableResearchHarnessOptions) =>
300
- Effect.gen(function* () {
301
- const destination = options?.destination ?? "LHR";
302
- const focus = options?.focus ?? "museums";
303
-
304
- const guideInvocations = yield* Ref.make(0);
305
-
306
- const guideLayer = Layer.succeed(
307
- DestinationGuide,
308
- DestinationGuide.of({
309
- lookup: (query) =>
310
- Ref.update(guideInvocations, (count) => count + 1).pipe(
311
- Effect.andThen(destinationLookup(query)),
312
- ),
313
- }),
314
- );
298
+ export const makeDurableResearchHarness = Effect.fnUntraced(function* (
299
+ options?: DurableResearchHarnessOptions,
300
+ ) {
301
+ const destination = options?.destination ?? "LHR";
302
+ const focus = options?.focus ?? "museums";
303
+
304
+ const guideInvocations = yield* Ref.make(0);
305
+
306
+ const guideLayer = Layer.succeed(
307
+ DestinationGuide,
308
+ DestinationGuide.of({
309
+ lookup: (query) =>
310
+ Ref.update(guideInvocations, (count) => count + 1).pipe(
311
+ Effect.andThen(destinationLookup(query)),
312
+ ),
313
+ }),
314
+ );
315
315
 
316
- const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(
317
- Layer.provideMerge(guideLayer),
318
- );
316
+ const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(guideLayer));
319
317
 
320
- const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) =>
321
- call === 0 ? researcherLookupParts(destination) : researcherReportParts(destination),
322
- );
318
+ const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) =>
319
+ call === 0 ? researcherLookupParts(destination) : researcherReportParts(destination),
320
+ );
323
321
 
324
- const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
322
+ const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
325
323
 
326
- const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) =>
327
- call === 0
328
- ? delegationTurnParts(durableResearchCallId, destination, focus)
329
- : shortlistParts(durableResearchShortlist(destination)),
330
- );
324
+ const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) =>
325
+ call === 0
326
+ ? delegationTurnParts(durableResearchCallId, destination, focus)
327
+ : shortlistParts(durableResearchShortlist(destination)),
328
+ );
331
329
 
332
- const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
330
+ const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
333
331
 
334
- const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(
335
- Layer.provide(
336
- Layer.mergeAll(
337
- childToolkitLayer,
338
- SubagentReservationsMemoryLive,
339
- DeterministicIdGeneratorLayer,
340
- ResearchDispatchGate.layerOpen,
341
- ),
332
+ const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(
333
+ Layer.provide(
334
+ Layer.mergeAll(
335
+ childToolkitLayer,
336
+ SubagentReservationsMemoryLive,
337
+ DeterministicIdGeneratorLayer,
338
+ ResearchDispatchGate.layerOpen,
342
339
  ),
343
- );
344
-
345
- const parentResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
346
- parentBinding,
347
- s2CoordinatorDigests,
348
- ).pipe(Effect.provide(delegationLayer));
349
-
350
- const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
351
- childBinding,
352
- options?.childRegistrationDigests ?? s2ResearcherDigests,
353
- ).pipe(Effect.provide(childToolkitLayer));
354
-
355
- const harness: DurableResearchHarness = {
356
- bindings: [parentResolved, childResolved],
357
- parentModelCalls: parentModel.calls,
358
- parentPrompts: parentModel.prompts,
359
- childModelCalls: childModel.calls,
360
- childPrompts: childModel.prompts,
361
- guideInvocations: Ref.get(guideInvocations),
362
- };
363
-
364
- return harness;
365
- });
340
+ ),
341
+ );
342
+
343
+ const parentResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
344
+ parentBinding,
345
+ s2CoordinatorDigests,
346
+ ).pipe(Effect.provide(delegationLayer));
347
+
348
+ const childResolved: ResolvedBinding = yield* DurableWorkerBinding.make(
349
+ childBinding,
350
+ options?.childRegistrationDigests ?? s2ResearcherDigests,
351
+ ).pipe(Effect.provide(childToolkitLayer));
352
+
353
+ const harness: DurableResearchHarness = {
354
+ bindings: [parentResolved, childResolved],
355
+ parentModelCalls: parentModel.calls,
356
+ parentPrompts: parentModel.prompts,
357
+ childModelCalls: childModel.calls,
358
+ childPrompts: childModel.prompts,
359
+ guideInvocations: Ref.get(guideInvocations),
360
+ };
361
+
362
+ return harness;
363
+ });