@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.
- package/dist/Certification.mjs +1 -1
- package/dist/Certification.mjs.map +1 -1
- package/dist/Chaos.mjs +1 -1
- package/dist/Chaos.mjs.map +1 -1
- package/dist/CodeExecutorConformance.mjs.map +1 -1
- package/dist/CodeExecutorSubstitute.mjs +14 -18
- package/dist/CodeExecutorSubstitute.mjs.map +1 -1
- package/dist/DocsResearcher.d.mts +1 -1
- package/dist/DocsResearcher.mjs +3 -3
- package/dist/DocsResearcher.mjs.map +1 -1
- package/dist/ScriptedModel.mjs +1 -1
- package/dist/ScriptedModel.mjs.map +1 -1
- package/dist/TravelPlanner.d.mts +2 -2
- package/dist/TravelPlanner.mjs +8 -8
- package/dist/TravelPlanner.mjs.map +1 -1
- package/dist/{deterministic-layers-BmRxLIws.mjs → deterministic-layers-D547EwHr.mjs} +2 -2
- package/dist/{deterministic-layers-BmRxLIws.mjs.map → deterministic-layers-D547EwHr.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/Certification.ts +17 -18
- package/src/Chaos.ts +18 -18
- package/src/CodeExecutorConformance.ts +1 -0
- package/src/CodeExecutorSubstitute.ts +27 -25
- package/src/ScriptedModel.ts +6 -7
- package/src/fixtures/docs-researcher/definition.ts +1 -0
- package/src/fixtures/docs-researcher/harness.ts +121 -120
- package/src/fixtures/travel-planner/deterministic-layers.ts +14 -15
- package/src/fixtures/travel-planner/phase5.ts +61 -64
- package/src/fixtures/travel-planner/subagents-durable.ts +89 -91
- package/src/fixtures/travel-planner/subagents.ts +88 -85
|
@@ -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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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 = (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
).
|
|
231
|
-
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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 = (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
317
|
-
Layer.provideMerge(guideLayer),
|
|
318
|
-
);
|
|
316
|
+
const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(guideLayer));
|
|
319
317
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
318
|
+
const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) =>
|
|
319
|
+
call === 0 ? researcherLookupParts(destination) : researcherReportParts(destination),
|
|
320
|
+
);
|
|
323
321
|
|
|
324
|
-
|
|
322
|
+
const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
|
|
325
323
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
330
|
+
const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
|
|
333
331
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
+
});
|