@effect-agent/testing 0.1.0-beta.9 → 0.1.0-beta.90

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 (55) hide show
  1. package/dist/Certification.d.mts +106 -0
  2. package/dist/Certification.mjs +633 -0
  3. package/dist/Certification.mjs.map +1 -0
  4. package/dist/Chaos.d.mts +126 -0
  5. package/dist/Chaos.mjs +755 -0
  6. package/dist/Chaos.mjs.map +1 -0
  7. package/dist/CodeExecutorConformance.d.mts +33 -0
  8. package/dist/CodeExecutorConformance.mjs +231 -0
  9. package/dist/CodeExecutorConformance.mjs.map +1 -0
  10. package/dist/CodeExecutorSubstitute.d.mts +21 -0
  11. package/dist/CodeExecutorSubstitute.mjs +368 -0
  12. package/dist/CodeExecutorSubstitute.mjs.map +1 -0
  13. package/dist/DocsResearcher.d.mts +276 -0
  14. package/dist/DocsResearcher.mjs +490 -0
  15. package/dist/DocsResearcher.mjs.map +1 -0
  16. package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
  17. package/dist/ScriptedModel.d.mts +2 -0
  18. package/dist/ScriptedModel.mjs +155 -0
  19. package/dist/ScriptedModel.mjs.map +1 -0
  20. package/dist/TravelPlanner.d.mts +1680 -0
  21. package/dist/TravelPlanner.mjs +1963 -0
  22. package/dist/TravelPlanner.mjs.map +1 -0
  23. package/dist/deterministic-layers-D5owIoke.mjs +358 -0
  24. package/dist/deterministic-layers-D5owIoke.mjs.map +1 -0
  25. package/dist/index.d.mts +2 -3408
  26. package/dist/index.mjs +2 -4771
  27. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  28. package/package.json +1 -48
  29. package/src/{certification.ts → Certification.ts} +366 -171
  30. package/src/{chaos.ts → Chaos.ts} +241 -127
  31. package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +30 -7
  32. package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +113 -46
  33. package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
  34. package/src/{scripted-model.ts → ScriptedModel.ts} +25 -29
  35. package/src/TravelPlanner.ts +232 -0
  36. package/src/fixtures/docs-researcher/definition.ts +20 -11
  37. package/src/fixtures/docs-researcher/harness.ts +41 -34
  38. package/src/fixtures/docs-researcher/mcp.ts +49 -3
  39. package/src/fixtures/travel-planner/definition.ts +15 -2
  40. package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
  41. package/src/fixtures/travel-planner/phase2.ts +4 -3
  42. package/src/fixtures/travel-planner/phase3.ts +19 -42
  43. package/src/fixtures/travel-planner/phase4.ts +24 -37
  44. package/src/fixtures/travel-planner/phase5.ts +35 -42
  45. package/src/fixtures/travel-planner/phase6.ts +191 -88
  46. package/src/fixtures/travel-planner/phase7.ts +4 -102
  47. package/src/fixtures/travel-planner/scenarios.ts +3 -4
  48. package/src/fixtures/travel-planner/subagents-durable.ts +35 -61
  49. package/src/fixtures/travel-planner/subagents.ts +36 -14
  50. package/src/index.ts +1 -11
  51. package/src/internal/certification-report.ts +25 -0
  52. package/dist/index.mjs.map +0 -1
  53. package/src/code-executor-conformance.d.ts +0 -30
  54. package/src/fixtures/travel-planner/index.ts +0 -11
  55. package/src/fixtures/warehouse/index.ts +0 -412
@@ -0,0 +1,1963 @@
1
+ import { ScriptedModel } from "./ScriptedModel.mjs";
2
+ import { A as LodgingQuery, B as TravelPlannerToolkitLayer, C as FlightOption, D as Itinerary, E as GuidanceFailure, F as SearchLodging, I as TravelGuidance, L as TravelPlan, M as QuoteId, N as SearchActivities, O as LodgingCatalog, P as SearchFlights, R as TravelPlanner, S as FlightCatalog, T as FlightUnavailable, V as TripRequest, _ as ActivityCatalog, a as DeterministicIdGeneratorLayer, b as ActivityUnavailable, c as ReverseCompletionToolkitLayer, d as SupplierOperation, f as SupplierUnavailable, g as supplierBookingRefFor, h as cancelBookingIdempotencyKey, i as CatalogLifecycleCounts, j as LodgingUnavailable, k as LodgingOption, l as SupplierBookingDesk, m as TravelPlannerRuntimeLayer, n as BookingRef, o as FlightCatalogLayer, p as TravelGuidanceLayer, r as CatalogLifecycle, s as LodgingCatalogLayer, t as ActivityCatalogLayer, u as SupplierBookingRecord, v as ActivityQuery, w as FlightQuery, x as AirportCode, y as ActivitySearchResult, z as TravelPlannerToolkit } from "./deterministic-layers-D5owIoke.mjs";
3
+ import { Context, Deferred, Duration, Effect, Layer, Option, Ref, Schema, Stream } from "effect";
4
+ import { LanguageModel, Model, Tool, Toolkit } from "effect/unstable/ai";
5
+ import * as Agent from "effect-agent/agent";
6
+ import { AgentPolicy } from "effect-agent/agent-policy";
7
+ import { DurableWorkerBinding } from "effect-agent/agent-registration";
8
+ import "effect-agent/durable-agent-runtime";
9
+ import { DurableStep, DurableStepError, ToolExecutionClass } from "effect-agent/durable-step";
10
+ import { AgentId, RunId, SubmissionId, ThreadId } from "effect-agent/identifiers";
11
+ import { BatchId, CanonicalBatch, CanonicalRecordEnvelope, DefinitionDigests, DeploymentId, Digest, PersistedJson, ProducerId, RecordEnvelope, RecordId } from "effect-agent/records";
12
+ import * as Subagent from "effect-agent/subagent";
13
+ import { SubagentPolicy, delegationAllocationFromPolicy } from "effect-agent/subagent";
14
+ import { SubagentReservationsMemoryLive } from "effect-agent/subagent-reservations";
15
+ import { Principal } from "effect-agent/submission-ledger";
16
+ import { ThreadCheckpoint } from "effect-agent/thread-store";
17
+ import { ReconciliationCompleted, ReconciliationSafeToRetry, ReconciliationUncertain, ToolReconciler, ToolReconcilerError } from "effect-agent/tool-reconciler";
18
+ import "effect-agent/agent-runtime";
19
+ import { ThreadProjection } from "effect-agent/thread-projection";
20
+ //#region src/fixtures/travel-planner/phase2.ts
21
+ var ItineraryHoldRequest = class extends Schema.Class("@effect-agent/testing/travel-planner/ItineraryHoldRequest")({
22
+ quoteId: QuoteId,
23
+ expiresInMinutes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(60))
24
+ }) {};
25
+ var ItineraryHold = class extends Schema.Class("@effect-agent/testing/travel-planner/ItineraryHold")({
26
+ holdId: Schema.NonEmptyString,
27
+ quoteId: QuoteId,
28
+ status: Schema.Literal("held")
29
+ }) {};
30
+ var ItineraryHoldUnavailable = class extends Schema.TaggedError()("ItineraryHoldUnavailable", {
31
+ quoteId: QuoteId,
32
+ message: Schema.String
33
+ }) {};
34
+ var ItineraryHoldGateway = class extends Context.Service()("@effect-agent/testing/travel-planner/ItineraryHoldGateway") {};
35
+ /**
36
+ * The first mutating Travel Planner Tool. Effect AI marks it as approval-gated
37
+ * so the engine must settle approval before acquiring a handler permit.
38
+ */
39
+ const HoldItinerary = Tool.make("hold_itinerary", {
40
+ parameters: ItineraryHoldRequest,
41
+ success: ItineraryHold,
42
+ failure: ItineraryHoldUnavailable,
43
+ failureMode: "error",
44
+ dependencies: [ItineraryHoldGateway],
45
+ needsApproval: true
46
+ });
47
+ const TravelPlannerPhase2Toolkit = Toolkit.make(SearchFlights, SearchLodging, SearchActivities, HoldItinerary);
48
+ const TravelPlannerPhase2ToolkitLayer = TravelPlannerPhase2Toolkit.toLayer({
49
+ search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(query)),
50
+ search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(query)),
51
+ search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(query)),
52
+ hold_itinerary: (request) => Effect.flatMap(ItineraryHoldGateway, (gateway) => gateway.hold(request))
53
+ });
54
+ const TravelPlannerPhase2 = Agent.make("travel-planner-phase-2", {
55
+ input: TripRequest,
56
+ output: TravelPlan,
57
+ instructions: (input) => Effect.flatMap(TravelGuidance, (guidance) => guidance.instructions(input)),
58
+ toolkit: TravelPlannerPhase2Toolkit,
59
+ policy: AgentPolicy.make({
60
+ maxTurns: 3,
61
+ maxToolCalls: 4,
62
+ maxDuration: "30 seconds",
63
+ toolConcurrency: 3,
64
+ tokenBudget: 4096
65
+ }),
66
+ description: "Build a review-only itinerary and require approval before creating a temporary hold.",
67
+ metadata: {
68
+ deploymentClass: "E",
69
+ phase: "P2"
70
+ }
71
+ });
72
+ //#endregion
73
+ //#region src/fixtures/travel-planner/scenarios.ts
74
+ const usage = {
75
+ inputTokens: { total: 128 },
76
+ outputTokens: { total: 96 }
77
+ };
78
+ const phase1Trip = Schema.decodeSync(TripRequest)({
79
+ request: "Plan a review-only London trip using the deterministic flight, lodging, and activity searches.",
80
+ origin: "SFO",
81
+ destination: "LHR",
82
+ departOn: "2026-09-14",
83
+ nights: 4,
84
+ travelers: 2,
85
+ budgetCents: 35e4,
86
+ currency: "USD"
87
+ });
88
+ const expectedTravelPlan = Schema.decodeSync(TravelPlan)({ itineraries: [{
89
+ title: "Westward light, eastbound overnight",
90
+ route: "San Francisco → London",
91
+ dates: "14–19 September 2026",
92
+ flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
93
+ lodging: "Bloomsbury House · refundable studio · 4 nights",
94
+ activities: ["British Museum timed entry", "Thames evening walk"],
95
+ estimatedTotalCents: 284e3,
96
+ currency: "USD",
97
+ quoteId: "quote-sfo-lhr-001",
98
+ assumptions: ["Two travelers sharing one studio", "Quote is read-only availability, not a reservation"],
99
+ unresolvedConstraints: ["Traveler names and accessibility requests are intentionally omitted"],
100
+ nextAction: "review"
101
+ }] });
102
+ const phase1HappyPathTurns = [{
103
+ _tag: "Stream",
104
+ parts: [
105
+ {
106
+ type: "tool-call",
107
+ id: "flight-call-1",
108
+ name: "search_flights",
109
+ params: {
110
+ origin: "SFO",
111
+ destination: "LHR",
112
+ departOn: "2026-09-14",
113
+ travelers: 2
114
+ }
115
+ },
116
+ {
117
+ type: "tool-call",
118
+ id: "lodging-call-1",
119
+ name: "search_lodging",
120
+ params: {
121
+ destination: "LHR",
122
+ departOn: "2026-09-14",
123
+ nights: 4,
124
+ travelers: 2
125
+ }
126
+ },
127
+ {
128
+ type: "tool-call",
129
+ id: "activity-call-1",
130
+ name: "search_activities",
131
+ params: {
132
+ destination: "LHR",
133
+ departOn: "2026-09-14",
134
+ nights: 4,
135
+ travelers: 2
136
+ }
137
+ },
138
+ {
139
+ type: "finish",
140
+ reason: "tool-calls",
141
+ usage
142
+ }
143
+ ],
144
+ termination: { _tag: "Complete" }
145
+ }, {
146
+ _tag: "Stream",
147
+ parts: [
148
+ {
149
+ type: "text-start",
150
+ id: "itinerary-json"
151
+ },
152
+ {
153
+ type: "text-delta",
154
+ id: "itinerary-json",
155
+ delta: JSON.stringify(Schema.encodeSync(TravelPlan)(expectedTravelPlan))
156
+ },
157
+ {
158
+ type: "text-end",
159
+ id: "itinerary-json"
160
+ },
161
+ {
162
+ type: "finish",
163
+ reason: "stop",
164
+ usage
165
+ }
166
+ ],
167
+ termination: { _tag: "Complete" }
168
+ }];
169
+ //#endregion
170
+ //#region src/fixtures/travel-planner/phase3.ts
171
+ const phase3TravelPlannerThreadId = Schema.decodeSync(ThreadId)("travel-planner-p3-thread");
172
+ const phase3TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p3-producer");
173
+ const phase3TravelPlannerRunId = Schema.decodeSync(RunId)("travel-planner-p3-run");
174
+ const deploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p3-scripted");
175
+ const agentId = Schema.decodeSync(AgentId)("travel-planner");
176
+ const submissionId = Schema.decodeSync(SubmissionId)("travel-planner-p3-submission");
177
+ const digest$2 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
178
+ /** Redacted, deterministic definition identities for the current fixture version. */
179
+ const phase3TravelPlannerDefinitionDigests = DefinitionDigests.make({
180
+ agent: digest$2("a"),
181
+ model: digest$2("b"),
182
+ tools: digest$2("c")
183
+ });
184
+ const tripInput = Schema.encodeSync(TripRequest)(phase1Trip);
185
+ const travelPlanOutput = Schema.encodeSync(TravelPlan)(expectedTravelPlan);
186
+ const record = (recordId, createdAt, payload) => Schema.decodeUnknownSync(RecordEnvelope)({
187
+ recordId: Schema.decodeSync(RecordId)(recordId),
188
+ family: "thread",
189
+ schemaVersion: 1,
190
+ createdAt,
191
+ deploymentId,
192
+ payload
193
+ });
194
+ /**
195
+ * The first atomic append establishes the Thread and records its input.
196
+ * Its encoded value is the redacted current-version persistence fixture.
197
+ */
198
+ const phase3TravelPlannerInitialBatch = CanonicalBatch.make({
199
+ batchId: Schema.decodeSync(BatchId)("travel-planner-p3-initial"),
200
+ producerId: phase3TravelPlannerProducerId,
201
+ records: [record("travel-planner-p3-created", "2026-09-01T00:00:00.000Z", {
202
+ _tag: "ThreadCreated",
203
+ agentId,
204
+ definitions: phase3TravelPlannerDefinitionDigests
205
+ }), record("travel-planner-p3-input", "2026-09-01T00:00:01.000Z", {
206
+ _tag: "UserInputRecorded",
207
+ submissionId,
208
+ kind: "user",
209
+ runId: phase3TravelPlannerRunId,
210
+ input: tripInput
211
+ })]
212
+ });
213
+ /** The second append records the Schema-decoded itinerary and terminal Run result. */
214
+ const phase3TravelPlannerCompletionBatch = CanonicalBatch.make({
215
+ batchId: Schema.decodeSync(BatchId)("travel-planner-p3-completion"),
216
+ producerId: phase3TravelPlannerProducerId,
217
+ records: [record("travel-planner-p3-model", "2026-09-01T00:00:02.000Z", {
218
+ _tag: "ModelCompleted",
219
+ runId: phase3TravelPlannerRunId,
220
+ output: travelPlanOutput
221
+ }), record("travel-planner-p3-completed", "2026-09-01T00:00:03.000Z", {
222
+ _tag: "RunCompleted",
223
+ runId: phase3TravelPlannerRunId,
224
+ output: travelPlanOutput
225
+ })]
226
+ });
227
+ const phase3TravelPlannerBatches = [phase3TravelPlannerInitialBatch, phase3TravelPlannerCompletionBatch];
228
+ /** Portable current-version fixture; it contains no passenger identity or credentials. */
229
+ const phase3TravelPlannerEncodedFixture = Schema.encodeSync(Schema.Array(CanonicalBatch))(phase3TravelPlannerBatches);
230
+ var TravelPlannerProjectionError = class extends Schema.TaggedError()("TravelPlannerProjectionError", { message: Schema.String }) {};
231
+ /** Decode the itinerary projection rebuilt from canonical model-completion records. */
232
+ const travelPlanFromProjection = (projection) => {
233
+ const output = projection.modelOutputs.at(-1);
234
+ if (output === void 0) return Effect.fail(TravelPlannerProjectionError.make({ message: "The canonical projection has no completed Travel Planner model output." }));
235
+ return Schema.decodeUnknownEffect(TravelPlan)(output).pipe(Effect.mapError((error) => TravelPlannerProjectionError.make({ message: error.message })));
236
+ };
237
+ /** Build a disposable checkpoint bound to a validated canonical prefix. */
238
+ const makePhase3TravelPlannerCheckpoint = (projection) => Schema.decodeSync(ThreadCheckpoint)({
239
+ schemaVersion: 1,
240
+ threadId: projection.threadId,
241
+ throughSequence: projection.throughSequence,
242
+ tailDigest: projection.tailDigest,
243
+ state: Schema.encodeSync(ThreadProjection)(projection),
244
+ createdAt: "2026-09-01T00:00:04.000Z"
245
+ });
246
+ //#endregion
247
+ //#region src/fixtures/travel-planner/phase4.ts
248
+ const phase4TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p4-deployment");
249
+ const phase4TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p4-producer");
250
+ const phase4TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-p4-principal");
251
+ const digest$1 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
252
+ /** Redacted, deterministic definition identities for the current fixture version. */
253
+ const phase4TravelPlannerDefinitionDigests = DefinitionDigests.make({
254
+ agent: digest$1("d"),
255
+ model: digest$1("e"),
256
+ tools: digest$1("f")
257
+ });
258
+ /** Durable admission options for one Travel Planner Submission on one trip lane. */
259
+ const phase4TravelPlannerSubmitOptions = (threadId, idempotencyKey) => ({
260
+ threadId,
261
+ principal: phase4TravelPlannerPrincipal,
262
+ idempotencyKey,
263
+ definitions: phase4TravelPlannerDefinitionDigests
264
+ });
265
+ /**
266
+ * The P4 read-only search Tools, calling the same deterministic catalogs as the P1 toolkit.
267
+ *
268
+ * The plain-Struct parameter shape is a historical remnant of the P4 carry-in workaround: since
269
+ * the P5 engine fix, official history carries Schema-ENCODED Tool-call parameters, so class-typed
270
+ * parameter codecs persist canonically too — the Structs simply need no change here. The
271
+ * `ToolExecutionClass` `readonly` annotation is the deliberate P5 migration (plan §4.3): these
272
+ * Tools perform no external mutation, so a crash between start and settlement is a free re-run
273
+ * and they never enter the prepared/settled uncertainty protocol — keeping the P4 canonical
274
+ * history byte-stable (an unannotated Tool fails closed to `uncertain`).
275
+ */
276
+ const DurableSearchFlights = Tool.make("search_flights", {
277
+ parameters: Schema.Struct(FlightQuery.fields),
278
+ success: FlightOption,
279
+ failure: FlightUnavailable,
280
+ failureMode: "error",
281
+ dependencies: [FlightCatalog]
282
+ }).annotate(ToolExecutionClass, "readonly");
283
+ const DurableSearchLodging = Tool.make("search_lodging", {
284
+ parameters: Schema.Struct(LodgingQuery.fields),
285
+ success: LodgingOption,
286
+ failure: LodgingUnavailable,
287
+ failureMode: "error",
288
+ dependencies: [LodgingCatalog]
289
+ }).annotate(ToolExecutionClass, "readonly");
290
+ const DurableSearchActivities = Tool.make("search_activities", {
291
+ parameters: Schema.Struct(ActivityQuery.fields),
292
+ success: ActivitySearchResult,
293
+ failure: ActivityUnavailable,
294
+ failureMode: "error",
295
+ dependencies: [ActivityCatalog]
296
+ }).annotate(ToolExecutionClass, "readonly");
297
+ const TravelPlannerPhase4Toolkit = Toolkit.make(DurableSearchFlights, DurableSearchLodging, DurableSearchActivities);
298
+ const TravelPlannerPhase4ToolkitLayer = TravelPlannerPhase4Toolkit.toLayer({
299
+ search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(FlightQuery.make(query))),
300
+ search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(LodgingQuery.make(query))),
301
+ search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(ActivityQuery.make(query)))
302
+ });
303
+ /**
304
+ * The cumulative Travel Planner, Phase 4: the P1 planning behavior on the durable Node/SQLite
305
+ * runtime. The searches are read-only and safe to repeat across Attempts (D6); supplier booking
306
+ * is deliberately absent because DN does NOT claim replay-safe external mutation (P5 scope).
307
+ */
308
+ const TravelPlannerPhase4 = Agent.make("travel-planner-phase-4", {
309
+ input: TripRequest,
310
+ output: TravelPlan,
311
+ instructions: (input) => Effect.flatMap(TravelGuidance, (guidance) => guidance.instructions(input)),
312
+ toolkit: TravelPlannerPhase4Toolkit,
313
+ policy: AgentPolicy.make({
314
+ maxTurns: 2,
315
+ maxToolCalls: 3,
316
+ maxDuration: "30 seconds",
317
+ toolConcurrency: 3
318
+ }),
319
+ description: "Durably plan one review-only itinerary from safe-to-repeat deterministic searches; supplier booking is not claimed safely replayable.",
320
+ metadata: {
321
+ deploymentClass: "DN",
322
+ phase: "P4"
323
+ }
324
+ });
325
+ /**
326
+ * The P4 Agent Binding: the durable Travel Planner definition bound to a finite scripted model.
327
+ * The scripted Layer is rebuilt per Run, so every Run of one Binding replays the same
328
+ * deterministic script.
329
+ */
330
+ const makePhase4TravelPlannerAgent = (turns = phase1HappyPathTurns) => Agent.withModel(TravelPlannerPhase4, Model.make("scripted", "travel-planner-phase-4", ScriptedModel.layer(turns)));
331
+ /**
332
+ * Everything a durable worker needs beyond the runtime stack, reusing the deterministic P1
333
+ * travel-service Layers. The durable coordinator supplies its own deterministic `IdGenerator`,
334
+ * so this Layer deliberately provides none.
335
+ */
336
+ const phase4TravelPlannerWorkerLayer = Layer.mergeAll(TravelPlannerPhase4ToolkitLayer, FlightCatalogLayer, LodgingCatalogLayer, ActivityCatalogLayer, TravelGuidanceLayer).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
337
+ var TravelPlannerDurableEvidenceError = class extends Schema.TaggedError()("TravelPlannerDurableEvidenceError", { message: Schema.String }) {};
338
+ /**
339
+ * Decode the completed itinerary from the canonical `SubmissionSettled` record. Canonical history
340
+ * is the outcome authority (DUR-015): the settled result — not any ledger cache — must decode
341
+ * through the trip output schema.
342
+ */
343
+ const travelPlanFromDurableSettlement = Effect.fn("TravelPlannerPhase4.travelPlanFromDurableSettlement")(function* (records) {
344
+ const settled = records.flatMap((envelope) => envelope.record.payload._tag === "SubmissionSettled" ? [envelope.record.payload] : []).at(0);
345
+ if (settled === void 0) return yield* TravelPlannerDurableEvidenceError.make({ message: "The canonical Thread Log has no SubmissionSettled record." });
346
+ if (settled.outcome !== "completed" || settled.result === void 0) return yield* TravelPlannerDurableEvidenceError.make({ message: `The Submission settled ${settled.outcome} without a completed itinerary result.` });
347
+ return yield* Schema.decodeUnknownEffect(TravelPlan)(settled.result).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `The settled result does not decode through the TravelPlan schema: ${error.message}` })));
348
+ });
349
+ const encodeEvidence = Schema.encodeEffect(Schema.Array(CanonicalRecordEnvelope));
350
+ const decodeComparableJson$1 = Schema.decodeUnknownEffect(Schema.Json);
351
+ /**
352
+ * Project canonical evidence into a Submission-identity-independent comparable form: batch
353
+ * identity, canonical sequence, and the full encoded record, with the ledger-minted
354
+ * `submissionId`/`receiptId` (and every identity derived from them: run, turn, batch, record,
355
+ * and settlement ids) replaced by stable placeholders. Two Threads whose normalized
356
+ * evidence is equal took byte-equivalent canonical histories, so restart-equivalence can compare
357
+ * a recovered run against an uninterrupted control run on a separate database.
358
+ */
359
+ const normalizeDurableTravelPlannerEvidence = Effect.fn("TravelPlannerPhase4.normalizeDurableTravelPlannerEvidence")(function* (records, receipt) {
360
+ const comparable = (yield* encodeEvidence(records).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Canonical evidence failed to encode: ${error.message}` })))).map((envelope) => ({
361
+ batchId: envelope.batchId,
362
+ sequence: envelope.sequence,
363
+ record: envelope.record
364
+ }));
365
+ const substituted = JSON.parse(JSON.stringify(comparable).replaceAll(receipt.submissionId, "{submissionId}").replaceAll(receipt.receiptId, "{receiptId}"));
366
+ return yield* decodeComparableJson$1(substituted).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Normalized evidence is not comparable JSON: ${error.message}` })));
367
+ });
368
+ //#endregion
369
+ //#region src/fixtures/travel-planner/phase5.ts
370
+ const phase5TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p5-deployment");
371
+ const phase5TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p5-producer");
372
+ const phase5TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-p5-principal");
373
+ const digest = (character) => Schema.decodeSync(Digest)(character.repeat(64));
374
+ /** Redacted, deterministic definition identities for the current fixture version. */
375
+ const phase5TravelPlannerDefinitionDigests = DefinitionDigests.make({
376
+ agent: digest("1"),
377
+ model: digest("2"),
378
+ tools: digest("3")
379
+ });
380
+ /** Durable admission options for one Travel Planner Submission on one trip lane. */
381
+ const phase5TravelPlannerSubmitOptions = (threadId, idempotencyKey) => ({
382
+ threadId,
383
+ principal: phase5TravelPlannerPrincipal,
384
+ idempotencyKey,
385
+ definitions: phase5TravelPlannerDefinitionDigests
386
+ });
387
+ const TravelerRef = Schema.NonEmptyString.pipe(Schema.brand("@effect-agent/testing/travel-planner/TravelerRef"));
388
+ /**
389
+ * Class-shaped booking parameters with branded fields: since the P5 engine fix, official history
390
+ * carries Schema-ENCODED Tool-call parameters, so class-typed parameter codecs persist
391
+ * canonically end-to-end (the P4 Struct workaround is gone for new Tools).
392
+ */
393
+ var FlightBookingRequest = class extends Schema.Class("FlightBookingRequest")({
394
+ quoteId: QuoteId,
395
+ travelerRef: TravelerRef,
396
+ departOn: Schema.String
397
+ }) {};
398
+ var SupplierBookingConfirmation = class extends Schema.Class("SupplierBookingConfirmation")({
399
+ bookingRef: BookingRef,
400
+ status: Schema.Literal("confirmed"),
401
+ detail: Schema.String
402
+ }) {};
403
+ var CancelBookingRequest = class extends Schema.Class("CancelBookingRequest")({
404
+ bookingRef: BookingRef,
405
+ travelerRef: TravelerRef
406
+ }) {};
407
+ var CancellationConfirmation = class extends Schema.Class("CancellationConfirmation")({
408
+ bookingRef: BookingRef,
409
+ status: Schema.Literal("cancelled")
410
+ }) {};
411
+ var ItineraryBookingRequest = class extends Schema.Class("ItineraryBookingRequest")({
412
+ quoteId: QuoteId,
413
+ destination: AirportCode,
414
+ nights: Schema.Int.check(Schema.isGreaterThan(0)),
415
+ travelerRef: TravelerRef
416
+ }) {};
417
+ var ItineraryConfirmation = class extends Schema.Class("ItineraryConfirmation")({
418
+ flightBookingRef: BookingRef,
419
+ lodgingBookingRef: BookingRef,
420
+ confirmationCode: Schema.String
421
+ }) {};
422
+ /** The P5 Run output: a booked (or explicitly not-booked) trip report. */
423
+ var TravelBookingReport = class extends Schema.Class("TravelBookingReport")({
424
+ summary: Schema.String,
425
+ bookingRefs: Schema.Array(BookingRef)
426
+ }) {};
427
+ /**
428
+ * Supplier idempotency-key derivations. The handler owns key derivation (the `idempotent` and
429
+ * `uncertain` execution classes carry no key), and the reconciler MUST use the same derivations
430
+ * to query external truth — both sides are exported so they cannot drift.
431
+ */
432
+ const bookFlightIdempotencyKey = (toolCallId) => `book-flight:${toolCallId}`;
433
+ const itineraryStepIdempotencyKey = (toolCallId, stepName) => `${toolCallId}:${stepName}`;
434
+ /**
435
+ * Approval-gated supplier booking, explicitly `uncertain`: a crash after the call may have
436
+ * mutated supplier state without a recorded outcome, so recovery must reconcile or stop at an
437
+ * Unknown Outcome — never replay automatically (DUR-009, ADR-0004).
438
+ */
439
+ const BookFlight = Tool.make("book_flight", {
440
+ parameters: FlightBookingRequest,
441
+ success: SupplierBookingConfirmation,
442
+ failure: SupplierUnavailable,
443
+ failureMode: "error",
444
+ needsApproval: true,
445
+ dependencies: [SupplierBookingDesk]
446
+ }).annotate(ToolExecutionClass, "uncertain");
447
+ /**
448
+ * Approval-gated cancellation, annotated `idempotent`: the DECLARED external contract is that
449
+ * cancellation is idempotent by `bookingRef` (the supplier desk enforces it), so recovery may
450
+ * re-execute a prepared-but-unsettled cancel without reconciliation proof. Repeats stay
451
+ * observable in the supplier call counters — the annotation never claims exactly-once execution.
452
+ */
453
+ const CancelBooking = Tool.make("cancel_booking", {
454
+ parameters: CancelBookingRequest,
455
+ success: CancellationConfirmation,
456
+ failure: SupplierUnavailable,
457
+ failureMode: "error",
458
+ needsApproval: true,
459
+ dependencies: [SupplierBookingDesk]
460
+ }).annotate(ToolExecutionClass, "idempotent");
461
+ /**
462
+ * The Durable Tool: declaring `DurableStep` in `dependencies` is what makes it durable
463
+ * (GLOSSARY.md). Its handler divides supplier mutation into three named Steps — `reserve-flight`,
464
+ * `reserve-lodging`, `issue-confirmation` — each deriving its supplier idempotency key from
465
+ * `(toolCallId, stepName)`, so re-entry after interruption replays recorded Step results and the
466
+ * supplier dedupes any honestly-repeated call. The Tool itself carries no execution-class
467
+ * annotation: it stays fail-closed `uncertain`, and `TravelSupplierReconcilerLayer` proves
468
+ * re-entry safe from the keyed-Step construction instead.
469
+ */
470
+ const BookItinerary = Tool.make("book_itinerary", {
471
+ parameters: ItineraryBookingRequest,
472
+ success: ItineraryConfirmation,
473
+ failure: Schema.Union([SupplierUnavailable, DurableStepError]),
474
+ failureMode: "error",
475
+ dependencies: [DurableStep, SupplierBookingDesk]
476
+ });
477
+ const TravelPlannerPhase5Toolkit = Toolkit.make(DurableSearchFlights, DurableSearchLodging, DurableSearchActivities, BookFlight, CancelBooking, BookItinerary);
478
+ const requireToolCallId = (toolName, toolCallId) => toolCallId === void 0 ? Effect.fail(SupplierUnavailable.make({ message: `${toolName} needs its stable Tool Call ID to derive the supplier idempotency key.` })) : Effect.succeed(toolCallId);
479
+ const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLayer({
480
+ search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(FlightQuery.make(query))),
481
+ search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(LodgingQuery.make(query))),
482
+ search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(ActivityQuery.make(query))),
483
+ book_flight: (request, context) => Effect.gen(function* () {
484
+ const desk = yield* SupplierBookingDesk;
485
+ const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
486
+ const record = yield* desk.book({
487
+ operation: "book-flight",
488
+ idempotencyKey: bookFlightIdempotencyKey(toolCallId),
489
+ detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`
490
+ });
491
+ return SupplierBookingConfirmation.make({
492
+ bookingRef: record.bookingRef,
493
+ status: "confirmed",
494
+ detail: record.detail
495
+ });
496
+ }),
497
+ cancel_booking: (request) => Effect.gen(function* () {
498
+ const record = yield* (yield* SupplierBookingDesk).cancel(request.bookingRef);
499
+ return CancellationConfirmation.make({
500
+ bookingRef: record.bookingRef,
501
+ status: "cancelled"
502
+ });
503
+ }),
504
+ book_itinerary: (request, context) => Effect.gen(function* () {
505
+ const desk = yield* SupplierBookingDesk;
506
+ const step = yield* DurableStep;
507
+ const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
508
+ const bookStep = (stepName, detail) => step.do(stepName, SupplierBookingRecord, desk.book({
509
+ operation: stepName,
510
+ idempotencyKey: itineraryStepIdempotencyKey(toolCallId, stepName),
511
+ detail
512
+ }));
513
+ const flight = yield* bookStep("reserve-flight", `flight ${request.quoteId} for ${request.travelerRef}`);
514
+ const lodging = yield* bookStep("reserve-lodging", `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`);
515
+ const confirmation = yield* bookStep("issue-confirmation", `itinerary confirmation for ${request.travelerRef}`);
516
+ return ItineraryConfirmation.make({
517
+ flightBookingRef: flight.bookingRef,
518
+ lodgingBookingRef: lodging.bookingRef,
519
+ confirmationCode: confirmation.bookingRef
520
+ });
521
+ })
522
+ });
523
+ const decodePersistedJson = Schema.decodeUnknownEffect(PersistedJson);
524
+ const encodeConfirmation = Schema.encodeEffect(SupplierBookingConfirmation);
525
+ /**
526
+ * The application reconciliation policy (durability §10): a claim about EXTERNAL truth, queried
527
+ * from the supplier desk by the same idempotency-key derivations the handlers use.
528
+ *
529
+ * - `book_flight`: a confirmed booking under `book-flight:{toolCallId}` is recovered supplier
530
+ * truth — `CompletedWithResult` settles it canonically without executing anything. Absence is
531
+ * NOT proof the call never started (a real supplier write could be in flight), so the desk
532
+ * answer stays fail-closed `Uncertain`.
533
+ * - `book_itinerary`: every external mutation inside is a named Step whose supplier idempotency
534
+ * key derives from `(toolCallId, stepName)`, so re-entry is provably safe by construction —
535
+ * `SafeToRetry`. Committed Steps replay from their records; repeated calls dedupe at the desk.
536
+ * - `cancel_booking`: declared `idempotent`, so the coordinator re-executes without consulting
537
+ * this policy; if ever asked, the bookingRef contract makes `SafeToRetry` honest.
538
+ * - anything else: fail-closed `Uncertain` (AGENTS rule 11).
539
+ */
540
+ const TravelSupplierReconcilerLayer = Layer.effect(ToolReconciler, Effect.gen(function* () {
541
+ const desk = yield* SupplierBookingDesk;
542
+ return ToolReconciler.of({ reconcile: (evidence) => Effect.gen(function* () {
543
+ switch (evidence.toolName) {
544
+ case "book_flight": {
545
+ const key = bookFlightIdempotencyKey(evidence.toolCallId);
546
+ const booking = yield* desk.lookup(key);
547
+ if (Option.isSome(booking) && booking.value.status === "confirmed") {
548
+ const confirmation = yield* encodeConfirmation(SupplierBookingConfirmation.make({
549
+ bookingRef: booking.value.bookingRef,
550
+ status: "confirmed",
551
+ detail: booking.value.detail
552
+ })).pipe(Effect.flatMap(decodePersistedJson));
553
+ return ReconciliationCompleted.make({
554
+ result: confirmation,
555
+ isFailure: false
556
+ });
557
+ }
558
+ return ReconciliationUncertain.make({ reason: `The supplier desk shows no confirmed booking under ${key}; a write may still be in flight.` });
559
+ }
560
+ case "book_itinerary": return ReconciliationSafeToRetry.make();
561
+ case "cancel_booking": return ReconciliationSafeToRetry.make();
562
+ default: return ReconciliationUncertain.make({ reason: `No supplier reconciliation exists for ${evidence.toolName}.` });
563
+ }
564
+ }).pipe(Effect.mapError((error) => ToolReconcilerError.make({
565
+ toolCallId: evidence.toolCallId,
566
+ message: `Supplier reconciliation failed: ${error.message}`
567
+ }))) });
568
+ }));
569
+ /**
570
+ * The cumulative Travel Planner, Phase 5: the durable planner now performs consequential
571
+ * supplier mutation under the full uncertainty protocol — approval-gated uncertain booking,
572
+ * idempotent-by-contract cancellation, and one Durable Tool whose Steps carry supplier
573
+ * idempotency keys.
574
+ */
575
+ const TravelPlannerPhase5 = Agent.make("travel-planner-phase-5", {
576
+ input: TripRequest,
577
+ output: TravelBookingReport,
578
+ instructions: [
579
+ "You are the Effect Agent Travel Planner P5 booking fixture.",
580
+ "Search with the read-only tools, then book with book_flight, book_itinerary, or",
581
+ "cancel_booking exactly as scripted. Every consequential mutation is approval-gated",
582
+ "or Step-structured. Return only a JSON object with summary and bookingRefs."
583
+ ].join(" "),
584
+ toolkit: TravelPlannerPhase5Toolkit,
585
+ policy: AgentPolicy.make({
586
+ maxTurns: 4,
587
+ maxToolCalls: 6,
588
+ maxDuration: "30 seconds",
589
+ toolConcurrency: 2
590
+ }),
591
+ description: "Durably book one itinerary with prepared/settled supplier records, Unknown Outcomes, named Steps, and joined traveler input.",
592
+ metadata: {
593
+ deploymentClass: "DN",
594
+ phase: "P5"
595
+ }
596
+ });
597
+ var TravelPlannerBookingEvidenceError = class extends Schema.TaggedError()("TravelPlannerBookingEvidenceError", { message: Schema.String }) {};
598
+ const bookingResultRefs = (result) => {
599
+ if (typeof result !== "object" || result === null) return [];
600
+ const refs = [];
601
+ for (const [field, value] of Object.entries(result)) if (typeof value === "string" && (field === "bookingRef" || field === "flightBookingRef" || field === "lodgingBookingRef" || field === "confirmationCode")) refs.push(value);
602
+ return refs;
603
+ };
604
+ const bookingToolNames = /* @__PURE__ */ new Set([
605
+ "book_flight",
606
+ "cancel_booking",
607
+ "book_itinerary"
608
+ ]);
609
+ /**
610
+ * Never-fabricate assertion (ROADMAP P5 exit gate): every successfully settled booking result in
611
+ * canonical history must reference a booking that actually exists in the supplier store. A
612
+ * `ToolCallSettled` whose bookingRef the supplier cannot produce would be a fabricated result —
613
+ * the exact lie the uncertainty protocol exists to prevent.
614
+ */
615
+ const assertSettledBookingsExistAtSupplier = Effect.fn("TravelPlannerPhase5.assertSettledBookingsExistAtSupplier")(function* (records) {
616
+ const bookings = yield* (yield* SupplierBookingDesk).bookings;
617
+ const knownRefs = new Set(bookings.map((booking) => booking.bookingRef));
618
+ for (const envelope of records) {
619
+ const payload = envelope.record.payload;
620
+ if (payload._tag !== "ToolCallSettled" || payload.isFailure || !bookingToolNames.has(payload.toolName)) continue;
621
+ for (const ref of bookingResultRefs(payload.result)) if (!knownRefs.has(ref)) return yield* TravelPlannerBookingEvidenceError.make({ message: `Canonical record ${envelope.record.recordId} settled bookingRef ${ref}, which the supplier store cannot produce — a fabricated result.` });
622
+ }
623
+ });
624
+ /**
625
+ * Everything a durable P5 worker needs beyond the runtime stack and the supplier desk: the
626
+ * booking toolkit plus the deterministic P1 travel-service Layers. `SupplierBookingDesk` is
627
+ * deliberately NOT provided here — tests own the desk's lifetime so its counters, bookings, and
628
+ * crash windows survive Tool-Layer rebuilds across Attempts.
629
+ */
630
+ const phase5TravelPlannerWorkerLayer = Layer.mergeAll(TravelPlannerPhase5ToolkitLayer, FlightCatalogLayer, LodgingCatalogLayer, ActivityCatalogLayer).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
631
+ //#endregion
632
+ //#region src/fixtures/travel-planner/subagents.ts
633
+ var DestinationQuery = class extends Schema.Class("DestinationQuery")({ destination: AirportCode }) {};
634
+ var DestinationFacts = class extends Schema.Class("DestinationFacts")({
635
+ destination: AirportCode,
636
+ highlights: Schema.Array(Schema.String),
637
+ advisory: Schema.NonEmptyString
638
+ }) {};
639
+ var DestinationGuideUnavailable = class extends Schema.TaggedError()("DestinationGuideUnavailable", {
640
+ destination: AirportCode,
641
+ message: Schema.String
642
+ }) {};
643
+ var DestinationGuide = class extends Context.Service()("@effect-agent/testing/travel-planner/DestinationGuide") {};
644
+ const LookupDestination = Tool.make("lookup_destination", {
645
+ parameters: DestinationQuery,
646
+ success: DestinationFacts,
647
+ failure: DestinationGuideUnavailable,
648
+ failureMode: "error",
649
+ dependencies: [DestinationGuide]
650
+ });
651
+ const DestinationResearcherToolkit = Toolkit.make(LookupDestination);
652
+ const DestinationResearcherToolkitLayer = DestinationResearcherToolkit.toLayer({ lookup_destination: (query) => Effect.flatMap(DestinationGuide, (guide) => guide.lookup(query)) });
653
+ var DestinationBrief = class extends Schema.Class("DestinationBrief")({
654
+ destination: AirportCode,
655
+ focus: Schema.NonEmptyString
656
+ }) {};
657
+ var DestinationReport = class extends Schema.Class("DestinationReport")({
658
+ destination: AirportCode,
659
+ highlights: Schema.Array(Schema.String),
660
+ advisory: Schema.NonEmptyString
661
+ }) {};
662
+ const DestinationResearcher = Agent.make("destination-researcher", {
663
+ input: DestinationBrief,
664
+ output: DestinationReport,
665
+ instructions: "Consult lookup_destination exactly once for the briefed airport, then return only a JSON destination report.",
666
+ toolkit: DestinationResearcherToolkit,
667
+ policy: AgentPolicy.make({
668
+ maxTurns: 2,
669
+ maxToolCalls: 1,
670
+ maxDuration: "30 seconds",
671
+ toolConcurrency: 1
672
+ }),
673
+ description: "Research one candidate destination with the deterministic travel guide.",
674
+ metadata: {
675
+ deploymentClass: "E",
676
+ phase: "S1"
677
+ }
678
+ });
679
+ const decodeAirportCode = Schema.decodeSync(AirportCode);
680
+ const guideFacts = /* @__PURE__ */ new Map([["LHR", DestinationFacts.make({
681
+ destination: decodeAirportCode("LHR"),
682
+ highlights: ["Barbican brutalism walk", "Kew glasshouse survey"],
683
+ advisory: "London favors museum mornings and riverside evenings."
684
+ })], ["CDG", DestinationFacts.make({
685
+ destination: decodeAirportCode("CDG"),
686
+ highlights: ["Marais passage crawl", "Seine bookstall loop"],
687
+ advisory: "Paris rewards early galleries and late cafes."
688
+ })]]);
689
+ /** Deterministic guide lookup shared by the default and test-local guide Layers. */
690
+ const destinationLookup = (query) => {
691
+ const facts = guideFacts.get(query.destination);
692
+ return facts === void 0 ? Effect.fail(DestinationGuideUnavailable.make({
693
+ destination: query.destination,
694
+ message: "No deterministic guide entry exists for this destination."
695
+ })) : Effect.succeed(facts);
696
+ };
697
+ const requireDestinationFacts = (destination) => {
698
+ const facts = guideFacts.get(destination);
699
+ if (facts === void 0) throw new Error(`No deterministic guide entry exists for destination ${destination}`);
700
+ return facts;
701
+ };
702
+ /** The report the scripted researcher writes after consulting the guide. */
703
+ const destinationReportFor = (destination) => {
704
+ const facts = requireDestinationFacts(destination);
705
+ return DestinationReport.make({
706
+ destination: facts.destination,
707
+ highlights: facts.highlights,
708
+ advisory: facts.advisory
709
+ });
710
+ };
711
+ const encodedDestinationReport = (destination) => JSON.stringify(Schema.encodeSync(DestinationReport)(destinationReportFor(destination)));
712
+ const DestinationGuideLayer = Layer.effect(DestinationGuide, Effect.gen(function* () {
713
+ const lifecycle = yield* CatalogLifecycle;
714
+ yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
715
+ return DestinationGuide.of({ lookup: destinationLookup });
716
+ }));
717
+ /** Child-side construction requirements of the delegation handler Layer. */
718
+ const DestinationResearchSupportLayer = Layer.mergeAll(DestinationResearcherToolkitLayer, DestinationGuideLayer);
719
+ var DestinationResearchRequest = class extends Schema.Class("DestinationResearchRequest")({
720
+ destination: AirportCode,
721
+ focus: Schema.NonEmptyString
722
+ }) {};
723
+ var DestinationResearchFindings = class extends Schema.Class("DestinationResearchFindings")({
724
+ destination: AirportCode,
725
+ summary: Schema.NonEmptyString
726
+ }) {};
727
+ var DestinationResearchFailed = class extends Schema.TaggedError()("DestinationResearchFailed", { childErrorTag: Schema.NonEmptyString }) {};
728
+ /**
729
+ * Deterministic delegation-admission choreography seam. `prepareInput` awaits
730
+ * this gate before the handler reserves budget or spawns, so tests can order
731
+ * concurrent delegation preflights without sleeps. It also keeps the
732
+ * projection's construction requirements honestly visible in the handler
733
+ * Layer's `R`. The open Layer never waits.
734
+ */
735
+ var ResearchDispatchGate = class ResearchDispatchGate extends Context.Service()("@effect-agent/testing/travel-planner/ResearchDispatchGate") {
736
+ static layerOpen = Layer.succeed(this, ResearchDispatchGate.of({ awaitDispatch: () => Effect.void }));
737
+ };
738
+ /**
739
+ * Finite per-invocation bounds (SUB-009): each child may use two Turns and
740
+ * one Tool Call; the parent Run may establish at most two children with at
741
+ * most two running concurrently.
742
+ */
743
+ const destinationResearchPolicy = SubagentPolicy.make({
744
+ maxChildren: 2,
745
+ maxConcurrency: 2,
746
+ maxTurns: 2,
747
+ maxToolCalls: 1,
748
+ maxDuration: "10 seconds"
749
+ });
750
+ const destinationResearchDelegation = Subagent.define("delegate_destination_research", {
751
+ description: "Research one candidate destination with the deterministic travel guide and return a bounded finding.",
752
+ target: DestinationResearcher,
753
+ parameters: DestinationResearchRequest,
754
+ success: DestinationResearchFindings,
755
+ failure: DestinationResearchFailed,
756
+ prepareInput: (request) => Effect.gen(function* () {
757
+ yield* (yield* ResearchDispatchGate).awaitDispatch(request.destination);
758
+ return DestinationBrief.make({
759
+ destination: request.destination,
760
+ focus: `research:${request.focus}`
761
+ });
762
+ }),
763
+ projectResult: (report) => Effect.succeed(DestinationResearchFindings.make({
764
+ destination: report.destination,
765
+ summary: report.advisory
766
+ })),
767
+ policy: destinationResearchPolicy
768
+ });
769
+ /** Total mapping from every expected child Run failure to the declared Tool failure (SUB-028). */
770
+ const mapResearchChildFailure = (failure) => DestinationResearchFailed.make({ childErrorTag: failure._tag });
771
+ /** Runtime wiring: pair the immutable delegation with one explicit child Binding. */
772
+ const destinationResearchHandlersLayer = (childBinding) => Subagent.layer(destinationResearchDelegation, childBinding, { mapChildFailure: mapResearchChildFailure });
773
+ var ResearchMission = class extends Schema.Class("ResearchMission")({
774
+ request: Schema.NonEmptyString,
775
+ candidates: Schema.Array(AirportCode).check(Schema.isMinLength(1))
776
+ }) {};
777
+ var DestinationRecommendation = class extends Schema.Class("DestinationRecommendation")({
778
+ destination: AirportCode,
779
+ summary: Schema.NonEmptyString
780
+ }) {};
781
+ var DestinationShortlist = class extends Schema.Class("DestinationShortlist")({
782
+ recommendations: Schema.Array(DestinationRecommendation),
783
+ nextAction: Schema.Literal("review")
784
+ }) {};
785
+ /** Parent-only transcript markers used to prove child context isolation (SUB-006/015). */
786
+ const coordinatorConfidentialMarker = "coordinator-vault-7q42";
787
+ const missionConfidentialMarker = "traveler-dossier-19f";
788
+ const TravelCoordinatorToolkit = Toolkit.make(destinationResearchDelegation.tool);
789
+ const TravelCoordinator = Agent.make("travel-coordinator", {
790
+ input: ResearchMission,
791
+ output: DestinationShortlist,
792
+ instructions: [
793
+ "You are the Effect Agent Travel Planner S1 delegation coordinator.",
794
+ `Coordinator-only context: ${coordinatorConfidentialMarker}.`,
795
+ "Call delegate_destination_research once per candidate in one Tool batch.",
796
+ "Return only a JSON shortlist built from the delegated findings. This is read-only planning."
797
+ ].join("\n"),
798
+ toolkit: TravelCoordinatorToolkit,
799
+ policy: AgentPolicy.make({
800
+ maxTurns: 2,
801
+ maxToolCalls: 3,
802
+ maxDuration: "30 seconds",
803
+ toolConcurrency: 3
804
+ }),
805
+ description: "Coordinate bounded destination research through one declared attached delegation Tool.",
806
+ metadata: {
807
+ deploymentClass: "E",
808
+ phase: "S1"
809
+ }
810
+ });
811
+ const researchMission = Schema.decodeSync(ResearchMission)({
812
+ request: `Shortlist one September culture city; keep ${missionConfidentialMarker} inside the coordinator thread.`,
813
+ candidates: ["LHR", "CDG"]
814
+ });
815
+ const expectedDestinationShortlist = DestinationShortlist.make({
816
+ recommendations: researchMission.candidates.map((destination) => DestinationRecommendation.make({
817
+ destination,
818
+ summary: requireDestinationFacts(destination).advisory
819
+ })),
820
+ nextAction: "review"
821
+ });
822
+ const scriptedUsage$2 = {
823
+ inputTokens: { total: 96 },
824
+ outputTokens: { total: 64 }
825
+ };
826
+ /** One coordinator Turn that declares the given delegation Tool Calls in order. */
827
+ const coordinatorResearchTurn = (calls) => ({
828
+ _tag: "Stream",
829
+ parts: [...calls.map((call) => ({
830
+ type: "tool-call",
831
+ id: call.id,
832
+ name: "delegate_destination_research",
833
+ params: {
834
+ destination: call.destination,
835
+ focus: call.focus
836
+ }
837
+ })), {
838
+ type: "finish",
839
+ reason: "tool-calls",
840
+ usage: scriptedUsage$2
841
+ }],
842
+ termination: { _tag: "Complete" }
843
+ });
844
+ /** The coordinator's final structured-output Turn. */
845
+ const coordinatorShortlistTurn = (shortlist) => ({
846
+ _tag: "Stream",
847
+ parts: [
848
+ {
849
+ type: "text-start",
850
+ id: "shortlist"
851
+ },
852
+ {
853
+ type: "text-delta",
854
+ id: "shortlist",
855
+ delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(shortlist))
856
+ },
857
+ {
858
+ type: "text-end",
859
+ id: "shortlist"
860
+ },
861
+ {
862
+ type: "finish",
863
+ reason: "stop",
864
+ usage: scriptedUsage$2
865
+ }
866
+ ],
867
+ termination: { _tag: "Complete" }
868
+ });
869
+ /** Static researcher script for single-child tests: one guide lookup, then the report. */
870
+ const researcherHappyPathTurns = (destination) => [{
871
+ _tag: "Stream",
872
+ parts: [{
873
+ type: "tool-call",
874
+ id: `lookup-${destination}`,
875
+ name: "lookup_destination",
876
+ params: { destination }
877
+ }, {
878
+ type: "finish",
879
+ reason: "tool-calls",
880
+ usage: scriptedUsage$2
881
+ }],
882
+ termination: { _tag: "Complete" }
883
+ }, {
884
+ _tag: "Stream",
885
+ parts: [
886
+ {
887
+ type: "text-start",
888
+ id: "destination-report"
889
+ },
890
+ {
891
+ type: "text-delta",
892
+ id: "destination-report",
893
+ delta: encodedDestinationReport(destination)
894
+ },
895
+ {
896
+ type: "text-end",
897
+ id: "destination-report"
898
+ },
899
+ {
900
+ type: "finish",
901
+ reason: "stop",
902
+ usage: scriptedUsage$2
903
+ }
904
+ ],
905
+ termination: { _tag: "Complete" }
906
+ }];
907
+ const researcherLookupParts$2 = (destination) => [{
908
+ type: "tool-call",
909
+ id: `lookup-${destination}`,
910
+ name: "lookup_destination",
911
+ params: { destination },
912
+ providerExecuted: false
913
+ }, {
914
+ type: "finish",
915
+ reason: "tool-calls",
916
+ usage: scriptedUsage$2
917
+ }];
918
+ const researcherReportParts$2 = (destination) => [
919
+ {
920
+ type: "text-start",
921
+ id: "destination-report"
922
+ },
923
+ {
924
+ type: "text-delta",
925
+ id: "destination-report",
926
+ delta: encodedDestinationReport(destination)
927
+ },
928
+ {
929
+ type: "text-end",
930
+ id: "destination-report"
931
+ },
932
+ {
933
+ type: "finish",
934
+ reason: "stop",
935
+ usage: scriptedUsage$2
936
+ }
937
+ ];
938
+ /**
939
+ * Build a deterministic researcher Model whose per-child behavior is keyed by
940
+ * the destination named in the child's own prompt: Turn one records the
941
+ * prompt, signals `started`, and calls the guide Tool; Turn two waits for the
942
+ * caller's `release` before writing the report. Each child Run builds the
943
+ * Model Layer inside its own scope, so one `CatalogLifecycle` acquisition and
944
+ * finalization is observed per child — the same acquire/release counting the
945
+ * catalog Layers use to prove interruption reaches every finalizer.
946
+ */
947
+ const makeDestinationResearcherModel = (destinations) => Effect.gen(function* () {
948
+ const lifecycle = yield* CatalogLifecycle;
949
+ const prompts = yield* Ref.make([]);
950
+ const gates = /* @__PURE__ */ new Map();
951
+ for (const destination of destinations) gates.set(destination, {
952
+ started: yield* Deferred.make(),
953
+ release: yield* Deferred.make()
954
+ });
955
+ const gatesFor = (destination) => Effect.suspend(() => {
956
+ const entry = gates.get(destination);
957
+ return entry === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No researcher gates exist for destination ${destination}`)) : Effect.succeed(entry);
958
+ });
959
+ return {
960
+ controls: {
961
+ awaitStarted: (destination) => gatesFor(destination).pipe(Effect.flatMap((entry) => Deferred.await(entry.started))),
962
+ release: (destination) => gatesFor(destination).pipe(Effect.flatMap((entry) => Deferred.succeed(entry.release, void 0)), Effect.asVoid),
963
+ prompts: Ref.get(prompts)
964
+ },
965
+ model: Model.make("scripted", "destination-researcher-scripted", Layer.effect(LanguageModel.LanguageModel, Effect.gen(function* () {
966
+ yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
967
+ const turn = yield* Ref.make(0);
968
+ return yield* LanguageModel.make({
969
+ generateText: () => Effect.succeed([]),
970
+ streamText: (options) => Stream.unwrap(Effect.gen(function* () {
971
+ const promptJson = JSON.stringify(options.prompt.content);
972
+ const destination = destinations.find((candidate) => promptJson.includes(candidate));
973
+ if (destination === void 0) return yield* Effect.die(/* @__PURE__ */ new Error("The researcher prompt names no scripted destination"));
974
+ const entry = yield* gatesFor(destination);
975
+ if ((yield* Ref.getAndUpdate(turn, (value) => value + 1)) === 0) {
976
+ yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
977
+ yield* Deferred.succeed(entry.started, void 0);
978
+ return Stream.fromIterable(researcherLookupParts$2(destination));
979
+ }
980
+ yield* Deferred.await(entry.release);
981
+ return Stream.fromIterable(researcherReportParts$2(destination));
982
+ }))
983
+ });
984
+ })))
985
+ };
986
+ });
987
+ //#endregion
988
+ //#region src/fixtures/travel-planner/subagents-durable.ts
989
+ /** Registered coordinator and researcher bindings for durable delegation tests. */
990
+ const s2TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-s2-deployment");
991
+ const s2TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-s2-producer");
992
+ const s2TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-s2-principal");
993
+ const digestOf$1 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
994
+ /** Redacted, deterministic parent (coordinator) definition digests for this fixture version. */
995
+ const s2CoordinatorDigests = DefinitionDigests.make({
996
+ agent: digestOf$1("a"),
997
+ model: digestOf$1("b"),
998
+ tools: digestOf$1("c")
999
+ });
1000
+ /**
1001
+ * The exact child Binding digest strings the application declares on
1002
+ * `SubagentRuntimeOptions.durable.targetDigests` AND the host registers with
1003
+ * the host's binding array for the researcher Binding. The coordinator
1004
+ * stores and verifies them byte-for-byte (SUB-023); a host registration under
1005
+ * different strings is a `ChildCompatibilityFailure`, never a substitution.
1006
+ */
1007
+ const s2ResearcherDigestStrings = {
1008
+ agent: "d".repeat(64),
1009
+ model: "e".repeat(64),
1010
+ tools: "f".repeat(64)
1011
+ };
1012
+ const s2ResearcherDigests = DefinitionDigests.make({
1013
+ agent: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.agent),
1014
+ model: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.model),
1015
+ tools: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.tools)
1016
+ });
1017
+ /** Durable admission options for one coordinator Submission on one mission lane. */
1018
+ const s2TravelPlannerSubmitOptions = (threadId, idempotencyKey) => ({
1019
+ threadId,
1020
+ principal: s2TravelPlannerPrincipal,
1021
+ idempotencyKey,
1022
+ definitions: s2CoordinatorDigests
1023
+ });
1024
+ /** The structural submit slice of the coordinator Binding (`DurableAgentRuntime.submit`). */
1025
+ const s2CoordinatorSubmitAgent = { definition: {
1026
+ id: TravelCoordinator.id,
1027
+ input: TravelCoordinator.input
1028
+ } };
1029
+ /**
1030
+ * The per-invocation reservation the durable handler computes from the S1
1031
+ * delegation policy (`delegationAllocationFromPolicy`): the conservation
1032
+ * evidence in the S2 tests checks the ledger reservation rows and the
1033
+ * canonical `SubagentJoined.finalAccounting` against exactly this value.
1034
+ */
1035
+ const durableResearchAllocation = delegationAllocationFromPolicy(destinationResearchPolicy);
1036
+ /** The one scripted delegation Tool Call id of the durable coordinator Run. */
1037
+ const durableResearchCallId = "research-lhr-1";
1038
+ /** The child's own scripted guide-lookup Tool Call id. */
1039
+ const durableChildLookupCallId = (destination) => `lookup-${destination}`;
1040
+ /** The projected finding the parent joins (only the advisory crosses, SUB-015). */
1041
+ const durableResearchFinding = (destination) => ({
1042
+ destination: destinationReportFor(destination).destination,
1043
+ summary: destinationReportFor(destination).advisory
1044
+ });
1045
+ /** The coordinator's expected final shortlist for one researched destination. */
1046
+ const durableResearchShortlist = (destination) => DestinationShortlist.make({
1047
+ recommendations: [DestinationRecommendation.make({
1048
+ destination: destinationReportFor(destination).destination,
1049
+ summary: destinationReportFor(destination).advisory
1050
+ })],
1051
+ nextAction: "review"
1052
+ });
1053
+ /**
1054
+ * The deterministic guide facts in encoded (wire) form: the "supplier truth"
1055
+ * an authorized operator records through `resolveUnknown` when a child guide
1056
+ * lookup stopped at an Unknown Outcome (DUR-017 — the framework never guesses
1057
+ * or replays it).
1058
+ */
1059
+ const encodedDestinationFacts = (destination) => {
1060
+ const report = destinationReportFor(destination);
1061
+ return Schema.encodeSync(DestinationFacts)(DestinationFacts.make({
1062
+ destination: report.destination,
1063
+ highlights: report.highlights,
1064
+ advisory: report.advisory
1065
+ }));
1066
+ };
1067
+ const scriptedUsage$1 = {
1068
+ inputTokens: { total: 96 },
1069
+ outputTokens: { total: 64 }
1070
+ };
1071
+ /** One scripted model whose behavior is keyed by the global invocation index. */
1072
+ const makeInvocationCountingModel = (name, script) => Effect.gen(function* () {
1073
+ const calls = yield* Ref.make(0);
1074
+ const prompts = yield* Ref.make([]);
1075
+ return {
1076
+ model: Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
1077
+ generateText: () => Effect.succeed([]),
1078
+ streamText: (request) => Stream.unwrap(Effect.gen(function* () {
1079
+ const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
1080
+ yield* Ref.update(prompts, (previous) => [...previous, JSON.stringify(request.prompt.content)]);
1081
+ return Stream.fromIterable(script(call));
1082
+ }))
1083
+ }))),
1084
+ calls: Ref.get(calls),
1085
+ prompts: Ref.get(prompts)
1086
+ };
1087
+ });
1088
+ const delegationTurnParts = (toolCallId, destination, focus) => [{
1089
+ type: "tool-call",
1090
+ id: toolCallId,
1091
+ name: "delegate_destination_research",
1092
+ params: {
1093
+ destination,
1094
+ focus
1095
+ },
1096
+ providerExecuted: false
1097
+ }, {
1098
+ type: "finish",
1099
+ reason: "tool-calls",
1100
+ usage: scriptedUsage$1
1101
+ }];
1102
+ const shortlistParts = (shortlist) => [
1103
+ {
1104
+ type: "text-start",
1105
+ id: "shortlist"
1106
+ },
1107
+ {
1108
+ type: "text-delta",
1109
+ id: "shortlist",
1110
+ delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(shortlist))
1111
+ },
1112
+ {
1113
+ type: "text-end",
1114
+ id: "shortlist"
1115
+ },
1116
+ {
1117
+ type: "finish",
1118
+ reason: "stop",
1119
+ usage: scriptedUsage$1
1120
+ }
1121
+ ];
1122
+ const researcherLookupParts$1 = (destination) => [{
1123
+ type: "tool-call",
1124
+ id: durableChildLookupCallId(destination),
1125
+ name: "lookup_destination",
1126
+ params: { destination },
1127
+ providerExecuted: false
1128
+ }, {
1129
+ type: "finish",
1130
+ reason: "tool-calls",
1131
+ usage: scriptedUsage$1
1132
+ }];
1133
+ const researcherReportParts$1 = (destination) => [
1134
+ {
1135
+ type: "text-start",
1136
+ id: "destination-report"
1137
+ },
1138
+ {
1139
+ type: "text-delta",
1140
+ id: "destination-report",
1141
+ delta: encodedDestinationReport(destination)
1142
+ },
1143
+ {
1144
+ type: "text-end",
1145
+ id: "destination-report"
1146
+ },
1147
+ {
1148
+ type: "finish",
1149
+ reason: "stop",
1150
+ usage: scriptedUsage$1
1151
+ }
1152
+ ];
1153
+ /** Runtime wiring for the durable slice: the S1 delegation plus the S2 digest declaration. */
1154
+ const durableDestinationResearchHandlersLayer = (childBinding) => Subagent.layer(destinationResearchDelegation, childBinding, {
1155
+ mapChildFailure: mapResearchChildFailure,
1156
+ durable: { targetDigests: s2ResearcherDigestStrings }
1157
+ });
1158
+ /**
1159
+ * Build the S2 Travel Planner harness: an invocation-counting scripted
1160
+ * coordinator (Turn 1 declares the one delegation call, Turn 2 writes the
1161
+ * shortlist), an invocation-counting scripted researcher (Turn 1 consults the
1162
+ * guide, Turn 2 writes the report), and both worker Bindings captured with
1163
+ * their requirement Contexts via `DurableWorkerBinding.make` under the exact
1164
+ * fixture digests. The returned `bindings` are plain values: they can be
1165
+ * registered with several `NodeDurableAgentRuntime` stacks over the same SQLite
1166
+ * file while the counters keep counting across all of them.
1167
+ */
1168
+ const makeDurableResearchHarness = (options) => Effect.gen(function* () {
1169
+ const destination = options?.destination ?? "LHR";
1170
+ const focus = options?.focus ?? "museums";
1171
+ const guideInvocations = yield* Ref.make(0);
1172
+ const guideLayer = Layer.succeed(DestinationGuide, DestinationGuide.of({ lookup: (query) => Ref.update(guideInvocations, (count) => count + 1).pipe(Effect.andThen(destinationLookup(query))) }));
1173
+ const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(guideLayer));
1174
+ const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) => call === 0 ? researcherLookupParts$1(destination) : researcherReportParts$1(destination));
1175
+ const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
1176
+ const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) => call === 0 ? delegationTurnParts(durableResearchCallId, destination, focus) : shortlistParts(durableResearchShortlist(destination)));
1177
+ const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
1178
+ const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(Layer.provide(Layer.mergeAll(childToolkitLayer, SubagentReservationsMemoryLive, DeterministicIdGeneratorLayer, ResearchDispatchGate.layerOpen)));
1179
+ return {
1180
+ bindings: [yield* DurableWorkerBinding.make(parentBinding, s2CoordinatorDigests).pipe(Effect.provide(delegationLayer)), yield* DurableWorkerBinding.make(childBinding, options?.childRegistrationDigests ?? s2ResearcherDigests).pipe(Effect.provide(childToolkitLayer))],
1181
+ parentModelCalls: parentModel.calls,
1182
+ parentPrompts: parentModel.prompts,
1183
+ childModelCalls: childModel.calls,
1184
+ childPrompts: childModel.prompts,
1185
+ guideInvocations: Ref.get(guideInvocations)
1186
+ };
1187
+ });
1188
+ //#endregion
1189
+ //#region src/fixtures/travel-planner/phase6.ts
1190
+ /** Platform-neutral bindings and canonical evidence shared by Node and Cloudflare tests. */
1191
+ const phase6TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p6-deployment");
1192
+ /** Producer prefix of the DC host; each Object mints `{prefix}:{threadId}`. */
1193
+ const phase6TravelPlannerProducerPrefix = "travel-planner-p6-producer";
1194
+ /** The full producer identity one DC Thread Object mints for itself. */
1195
+ const phase6TravelPlannerProducerId = (threadId) => Schema.decodeSync(ProducerId)(`${phase6TravelPlannerProducerPrefix}:${threadId}`);
1196
+ const digestOf = (character) => Schema.decodeSync(Digest)(character.repeat(64));
1197
+ /**
1198
+ * Registration digests of the GATED planner Binding: the same `TravelPlannerPhase4` definition
1199
+ * bound to a model whose first response waits on a test gate, addressable separately so the
1200
+ * admission-limits rows can hold a lane busy deterministically without touching the ordinary
1201
+ * planner registration.
1202
+ */
1203
+ const phase6GatedPlannerDefinitionDigests = DefinitionDigests.make({
1204
+ agent: digestOf("9"),
1205
+ model: digestOf("8"),
1206
+ tools: digestOf("7")
1207
+ });
1208
+ const decodeComparableJson = Schema.decodeUnknownEffect(Schema.Json);
1209
+ /** The base normal form's element shape, re-decoded so sequences can be renumbered. */
1210
+ const ComparableEnvelope = Schema.Struct({
1211
+ batchId: Schema.String,
1212
+ sequence: Schema.Number,
1213
+ record: Schema.Json
1214
+ });
1215
+ const decodeComparableEnvelopes = Schema.decodeUnknownEffect(Schema.Array(ComparableEnvelope));
1216
+ /**
1217
+ * The CROSS-PLATFORM extension of `normalizeDurableTravelPlannerEvidence` (D-P6-6): after the
1218
+ * base normalization replaces the two ledger-minted identities (which also normalizes the
1219
+ * DC-format routable `{uuidv7}:{threadId}` Submission identities and everything derived
1220
+ * from them), this form additionally scrubs everything that legitimately differs between a DN
1221
+ * process and a DC Durable Object over the same scenario:
1222
+ *
1223
+ * - `RepairAnnotated` audit records are dropped BEFORE normalization and the canonical
1224
+ * sequence is renumbered to the surviving order: repairs are DUR-013 evidence of recovery
1225
+ * itself, legally present in a recovered run and legally absent from an uninterrupted
1226
+ * control (on DC even a CLEAN run carries one, because every pass reconciles before it
1227
+ * claims, so the ready lane's input is applied through the recovery path). Canonical ORDER
1228
+ * is the durability §5 claim; sequence contiguity is a platform artifact of who appended;
1229
+ * - the Thread identity (DC lanes mint unique names per test run);
1230
+ * - the deployment and producer identities (host configuration, not canonical semantics);
1231
+ * - `createdAt` commit timestamps (wall clock);
1232
+ * - 64-hex digests (they hash RAW content that legally embeds run-specific identity, so they
1233
+ * can never be byte-equal across runs; chain integrity is asserted separately by the
1234
+ * adapters and the convergence helpers).
1235
+ *
1236
+ * Two runs whose cross-platform normalized evidence is byte-equal took canonically equivalent
1237
+ * histories — the exact sense in which durability §5 permits storage differences while
1238
+ * requiring the same observable ordering. Both the DN and DC suites assert equality against
1239
+ * the one committed `phase6TravelPlannerGoldenEvidence`, so DN ≡ DC transitively.
1240
+ */
1241
+ const normalizeCrossPlatformTravelPlannerEvidence = Effect.fn("TravelPlannerPhase6.normalizeCrossPlatformTravelPlannerEvidence")(function* (records, receipt, identity) {
1242
+ const canonical = records.filter((envelope) => envelope.record.payload._tag !== "RepairAnnotated");
1243
+ const base = yield* normalizeDurableTravelPlannerEvidence(canonical, receipt);
1244
+ const scrubbed = JSON.parse(JSON.stringify(base).replaceAll(identity.producerId, "{producerId}").replaceAll(identity.deploymentId, "{deploymentId}").replaceAll(identity.threadId, "{threadId}").replaceAll(/\d{4}-\d{2}-\d{2}T[0-9:.]+Z/g, "{timestamp}").replaceAll(/"[0-9a-f]{64}"/g, "\"{digest}\""));
1245
+ const renumbered = (yield* decodeComparableEnvelopes(scrubbed).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Cross-platform normalized evidence lost the comparable shape: ${error.message}` })))).map((entry, index) => ({
1246
+ batchId: entry.batchId,
1247
+ sequence: index + 1,
1248
+ record: entry.record
1249
+ }));
1250
+ return yield* decodeComparableJson(renumbered).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Cross-platform normalized evidence is not comparable JSON: ${error.message}` })));
1251
+ });
1252
+ const scriptedUsage = {
1253
+ inputTokens: { total: 128 },
1254
+ outputTokens: { total: 96 }
1255
+ };
1256
+ const promptAwareModel = (name, decide) => Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
1257
+ generateText: () => Effect.succeed([]),
1258
+ streamText: (options) => Stream.unwrap(Effect.sync(() => decide(JSON.stringify(options.prompt))))
1259
+ })));
1260
+ /** The P1/P4 happy-path Tool Call identities (scenarios.ts, byte-stable since P1). */
1261
+ const phase6FlightCallId = "flight-call-1";
1262
+ const phase6LodgingCallId = "lodging-call-1";
1263
+ const phase6ActivityCallId = "activity-call-1";
1264
+ /** Turn 1 of the planner: the SAME three search declarations as `phase1HappyPathTurns`. */
1265
+ const plannerSearchTurnParts = [
1266
+ {
1267
+ type: "tool-call",
1268
+ id: phase6FlightCallId,
1269
+ name: "search_flights",
1270
+ params: {
1271
+ origin: "SFO",
1272
+ destination: "LHR",
1273
+ departOn: "2026-09-14",
1274
+ travelers: 2
1275
+ }
1276
+ },
1277
+ {
1278
+ type: "tool-call",
1279
+ id: phase6LodgingCallId,
1280
+ name: "search_lodging",
1281
+ params: {
1282
+ destination: "LHR",
1283
+ departOn: "2026-09-14",
1284
+ nights: 4,
1285
+ travelers: 2
1286
+ }
1287
+ },
1288
+ {
1289
+ type: "tool-call",
1290
+ id: phase6ActivityCallId,
1291
+ name: "search_activities",
1292
+ params: {
1293
+ destination: "LHR",
1294
+ departOn: "2026-09-14",
1295
+ nights: 4,
1296
+ travelers: 2
1297
+ }
1298
+ },
1299
+ {
1300
+ type: "finish",
1301
+ reason: "tool-calls",
1302
+ usage: scriptedUsage
1303
+ }
1304
+ ];
1305
+ /** Turn 2 of the planner: the SAME itinerary text as `phase1HappyPathTurns`. */
1306
+ const plannerPlanTurnParts = [
1307
+ {
1308
+ type: "text-start",
1309
+ id: "itinerary-json"
1310
+ },
1311
+ {
1312
+ type: "text-delta",
1313
+ id: "itinerary-json",
1314
+ delta: JSON.stringify(Schema.encodeSync(TravelPlan)(expectedTravelPlan))
1315
+ },
1316
+ {
1317
+ type: "text-end",
1318
+ id: "itinerary-json"
1319
+ },
1320
+ {
1321
+ type: "finish",
1322
+ reason: "stop",
1323
+ usage: scriptedUsage
1324
+ }
1325
+ ];
1326
+ const plannerDecide = (promptJson) => promptJson.includes("flight-call-1") ? Stream.fromIterable(plannerPlanTurnParts) : Stream.fromIterable(plannerSearchTurnParts);
1327
+ /**
1328
+ * The P4 planner script (`phase1HappyPathTurns`) as a prompt-aware model: once the search
1329
+ * batch is committed history, every later request gets the plan — identical parts, so the DC
1330
+ * canonical evidence is byte-equivalent to the DN ScriptedModel run after normalization.
1331
+ */
1332
+ const phase6PlannerModel = promptAwareModel("travel-planner-phase-4", plannerDecide);
1333
+ const releasedPlannerGates = /* @__PURE__ */ new Set();
1334
+ /** Release the gated planner model for one `[gate:...]` marker. */
1335
+ const releasePhase6PlannerGate = (marker) => {
1336
+ releasedPlannerGates.add(marker);
1337
+ };
1338
+ /** Re-close one gate marker (fresh suites reuse markers safely). */
1339
+ const resetPhase6PlannerGate = (marker) => {
1340
+ releasedPlannerGates.delete(marker);
1341
+ };
1342
+ const awaitPlannerGate = (marker) => Effect.gen(function* () {
1343
+ while (!releasedPlannerGates.has(marker)) yield* Effect.sleep(Duration.millis(10));
1344
+ });
1345
+ const gateMarkerFromPrompt = (promptJson) => /\[gate:([^\]]+)\]/.exec(promptJson)?.[1] ?? "unknown-gate";
1346
+ /** A trip whose request text carries the gate marker the gated model waits on. */
1347
+ const phase6GatedTrip = (marker) => Schema.decodeSync(TripRequest)({
1348
+ request: `Plan a review-only London trip, but wait for the concierge. [gate:${marker}]`,
1349
+ origin: "SFO",
1350
+ destination: "LHR",
1351
+ departOn: "2026-09-14",
1352
+ nights: 4,
1353
+ travelers: 2,
1354
+ budgetCents: 35e4,
1355
+ currency: "USD"
1356
+ });
1357
+ /**
1358
+ * The SAME planner behavior with a hanging first response: the model waits on the released
1359
+ * gate before answering, keeping its lane durably busy so queue-depth admission limits can be
1360
+ * exercised deterministically.
1361
+ */
1362
+ const phase6GatedPlannerModel = Model.make("scripted", "travel-planner-phase-4-gated", Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
1363
+ generateText: () => Effect.succeed([]),
1364
+ streamText: (options) => Stream.unwrap(Effect.sync(() => {
1365
+ const promptJson = JSON.stringify(options.prompt);
1366
+ return promptJson.includes("flight-call-1") ? plannerDecide(promptJson) : Stream.fromEffectDrain(awaitPlannerGate(gateMarkerFromPrompt(promptJson))).pipe(Stream.concat(plannerDecide(promptJson)));
1367
+ }))
1368
+ })));
1369
+ const sharedSupplierDesk = Effect.runSync(Effect.flatMap(SupplierBookingDesk, Effect.succeed).pipe(Effect.provide(SupplierBookingDesk.layer)));
1370
+ /** The shared external supplier desk instance (module-level external truth). */
1371
+ const phase6SupplierDesk = sharedSupplierDesk;
1372
+ /** Layer handing the shared desk to Bindings, reconcilers, and assertions. */
1373
+ const phase6SupplierDeskLayer = Layer.succeed(SupplierBookingDesk, sharedSupplierDesk);
1374
+ /**
1375
+ * The REAL P5 supplier reconciliation policy over the shared desk, closed to no requirements
1376
+ * so a Thread Object can install it directly: `book_flight` recovers only from supplier
1377
+ * truth (absence stays fail-closed `Uncertain` → durable Unknown Outcome), keyed Steps are
1378
+ * provably re-enterable.
1379
+ */
1380
+ const phase6SupplierReconcilerLayer = TravelSupplierReconcilerLayer.pipe(Layer.provide(phase6SupplierDeskLayer));
1381
+ const bookingMarkerFromPrompt = (promptJson) => /\[case:([^\]]+)\]/.exec(promptJson)?.[1] ?? "unknown-case";
1382
+ /** The deterministic booking Tool Call identity for one `[case:...]` marker. */
1383
+ const phase6BookingToolCallId = (marker) => `book-${marker}`;
1384
+ /** The bookingRef the supplier desk mints for one marker's approved booking. */
1385
+ const phase6BookingRef = (marker) => supplierBookingRefFor(bookFlightIdempotencyKey(phase6BookingToolCallId(marker)));
1386
+ /** A trip whose request text carries the per-lane booking case marker. */
1387
+ const phase6BookingTrip = (marker) => Schema.decodeSync(TripRequest)({
1388
+ request: `Book the approved London flight for the traveler. [case:${marker}]`,
1389
+ origin: "SFO",
1390
+ destination: "LHR",
1391
+ departOn: "2026-09-14",
1392
+ nights: 4,
1393
+ travelers: 2,
1394
+ budgetCents: 35e4,
1395
+ currency: "USD"
1396
+ });
1397
+ const bookingCallParts = (marker) => [{
1398
+ type: "tool-call",
1399
+ id: phase6BookingToolCallId(marker),
1400
+ name: "book_flight",
1401
+ params: {
1402
+ quoteId: "quote-sfo-lhr-001",
1403
+ travelerRef: `traveler-${marker}`,
1404
+ departOn: "2026-09-14"
1405
+ },
1406
+ providerExecuted: false
1407
+ }, {
1408
+ type: "finish",
1409
+ reason: "tool-calls",
1410
+ usage: scriptedUsage
1411
+ }];
1412
+ const bookingReportParts = (marker) => [
1413
+ {
1414
+ type: "text-start",
1415
+ id: "booking-report"
1416
+ },
1417
+ {
1418
+ type: "text-delta",
1419
+ id: "booking-report",
1420
+ delta: JSON.stringify({
1421
+ summary: "trip booked",
1422
+ bookingRefs: [phase6BookingRef(marker)]
1423
+ })
1424
+ },
1425
+ {
1426
+ type: "text-end",
1427
+ id: "booking-report"
1428
+ },
1429
+ {
1430
+ type: "finish",
1431
+ reason: "stop",
1432
+ usage: scriptedUsage
1433
+ }
1434
+ ];
1435
+ /**
1436
+ * The P5 booking script as a prompt-aware model: request 1 declares the approval-gated
1437
+ * `book_flight` call (identity derived from the lane's `[case:...]` marker so supplier
1438
+ * idempotency keys never collide across lanes); once that call is committed history, the model
1439
+ * writes the booking report.
1440
+ */
1441
+ const phase6BookingModel = promptAwareModel("travel-planner-phase-5", (promptJson) => {
1442
+ const marker = bookingMarkerFromPrompt(promptJson);
1443
+ return promptJson.includes(phase6BookingToolCallId(marker)) ? Stream.fromIterable(bookingReportParts(marker)) : Stream.fromIterable(bookingCallParts(marker));
1444
+ });
1445
+ let guideInvocations = 0;
1446
+ /** Deterministic guide-lookup handler executions across every incarnation. */
1447
+ const phase6GuideInvocationCount = () => guideInvocations;
1448
+ const countingGuideLayer = Layer.succeed(DestinationGuide, DestinationGuide.of({ lookup: (query) => Effect.suspend(() => {
1449
+ guideInvocations += 1;
1450
+ return destinationLookup(query);
1451
+ }) }));
1452
+ /** The one-candidate research mission of the DC delegation slice. */
1453
+ const phase6ResearchMission = Schema.decodeSync(ResearchMission)({
1454
+ request: "Shortlist one September culture city for the DC delegation slice.",
1455
+ candidates: ["LHR"]
1456
+ });
1457
+ const phase6ResearchDestination = "LHR";
1458
+ /** The child's scripted guide-lookup Tool Call identity. */
1459
+ const phase6ChildLookupCallId = `lookup-LHR`;
1460
+ const coordinatorDelegationParts = [{
1461
+ type: "tool-call",
1462
+ id: durableResearchCallId,
1463
+ name: "delegate_destination_research",
1464
+ params: {
1465
+ destination: "LHR",
1466
+ focus: "museums"
1467
+ },
1468
+ providerExecuted: false
1469
+ }, {
1470
+ type: "finish",
1471
+ reason: "tool-calls",
1472
+ usage: scriptedUsage
1473
+ }];
1474
+ const coordinatorShortlistParts = [
1475
+ {
1476
+ type: "text-start",
1477
+ id: "shortlist"
1478
+ },
1479
+ {
1480
+ type: "text-delta",
1481
+ id: "shortlist",
1482
+ delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(durableResearchShortlist("LHR")))
1483
+ },
1484
+ {
1485
+ type: "text-end",
1486
+ id: "shortlist"
1487
+ },
1488
+ {
1489
+ type: "finish",
1490
+ reason: "stop",
1491
+ usage: scriptedUsage
1492
+ }
1493
+ ];
1494
+ const researcherLookupParts = [{
1495
+ type: "tool-call",
1496
+ id: phase6ChildLookupCallId,
1497
+ name: "lookup_destination",
1498
+ params: { destination: "LHR" },
1499
+ providerExecuted: false
1500
+ }, {
1501
+ type: "finish",
1502
+ reason: "tool-calls",
1503
+ usage: scriptedUsage
1504
+ }];
1505
+ const researcherReportParts = [
1506
+ {
1507
+ type: "text-start",
1508
+ id: "destination-report"
1509
+ },
1510
+ {
1511
+ type: "text-delta",
1512
+ id: "destination-report",
1513
+ delta: encodedDestinationReport("LHR")
1514
+ },
1515
+ {
1516
+ type: "text-end",
1517
+ id: "destination-report"
1518
+ },
1519
+ {
1520
+ type: "finish",
1521
+ reason: "stop",
1522
+ usage: scriptedUsage
1523
+ }
1524
+ ];
1525
+ /** Prompt-aware S2 coordinator: delegation call first, shortlist once it is history. */
1526
+ const phase6CoordinatorModel = promptAwareModel("travel-coordinator-p6", (promptJson) => promptJson.includes("research-lhr-1") ? Stream.fromIterable(coordinatorShortlistParts) : Stream.fromIterable(coordinatorDelegationParts));
1527
+ let researcherGateReleased = false;
1528
+ /** Allow the researcher's FIRST model response to proceed (sticky across incarnations). */
1529
+ const releasePhase6ResearcherGate = () => {
1530
+ researcherGateReleased = true;
1531
+ };
1532
+ /** Re-close the researcher gate (each delegation scenario starts gated). */
1533
+ const resetPhase6ResearcherGate = () => {
1534
+ researcherGateReleased = false;
1535
+ };
1536
+ const awaitResearcherGate = Effect.gen(function* () {
1537
+ while (!researcherGateReleased) yield* Effect.sleep(Duration.millis(10));
1538
+ });
1539
+ /**
1540
+ * Prompt-aware S2 researcher: guide lookup first, report once it is history. The FIRST
1541
+ * response waits on the researcher gate — a stand-in for real model latency. The child's own
1542
+ * Object may legally start its Attempt the moment its routed admission commits, while the
1543
+ * parent is still appending the lineage record into the child's log; a child whose first
1544
+ * batch commits during that window races the parent's append on one tail. Real models answer
1545
+ * in seconds, so establishment always wins that race in production; the gate reproduces that
1546
+ * timing deterministically instead of relying on scheduler luck.
1547
+ */
1548
+ const phase6ResearcherModel = promptAwareModel("destination-researcher-p6", (promptJson) => promptJson.includes(phase6ChildLookupCallId) ? Stream.fromIterable(researcherReportParts) : Stream.fromEffectDrain(awaitResearcherGate).pipe(Stream.concat(Stream.fromIterable(researcherLookupParts))));
1549
+ /**
1550
+ * Every phase-6 Travel Planner worker Binding, captured with its requirement Contexts:
1551
+ * the P4 planner and its gated twin, the P5 booking agent over the
1552
+ * shared supplier desk, and the S2 coordinator/researcher pair wired through the durable
1553
+ * delegation Layer. A Thread Object registers these via its `bindings` option; the
1554
+ * capture runs once per incarnation, and everything stateful the assertions rely on (desk,
1555
+ * guide counter, gates) lives at module level so it survives incarnation loss.
1556
+ */
1557
+ const makePhase6TravelPlannerBindings = Effect.gen(function* () {
1558
+ const planner = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase4, phase6PlannerModel), phase4TravelPlannerDefinitionDigests).pipe(Effect.provide(phase4TravelPlannerWorkerLayer));
1559
+ const gatedPlanner = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase4, phase6GatedPlannerModel), phase6GatedPlannerDefinitionDigests).pipe(Effect.provide(phase4TravelPlannerWorkerLayer));
1560
+ const booking = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase5, phase6BookingModel), phase5TravelPlannerDefinitionDigests).pipe(Effect.provide(phase5TravelPlannerWorkerLayer.pipe(Layer.provideMerge(phase6SupplierDeskLayer))));
1561
+ const researcherBinding = Agent.withModel(DestinationResearcher, phase6ResearcherModel);
1562
+ const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(countingGuideLayer));
1563
+ return [
1564
+ planner,
1565
+ gatedPlanner,
1566
+ booking,
1567
+ yield* DurableWorkerBinding.make(Agent.withModel(TravelCoordinator, phase6CoordinatorModel), s2CoordinatorDigests).pipe(Effect.provide(durableDestinationResearchHandlersLayer(researcherBinding).pipe(Layer.provide(Layer.mergeAll(childToolkitLayer, SubagentReservationsMemoryLive, DeterministicIdGeneratorLayer, ResearchDispatchGate.layerOpen))))),
1568
+ yield* DurableWorkerBinding.make(researcherBinding, s2ResearcherDigests).pipe(Effect.provide(childToolkitLayer))
1569
+ ];
1570
+ });
1571
+ /**
1572
+ * The committed cross-platform normalized canonical evidence of ONE uninterrupted Travel
1573
+ * Planner planning Submission (the P1/P4 happy path: canonical input, the search Turn, three
1574
+ * Tool settlements, the plan Turn, one Settlement). `travel-planner-phase6.test.ts` asserts
1575
+ * the DN run equals this value and `travel-planner-dc.test.ts` asserts the DC run equals this
1576
+ * value, so the two platforms' canonical outcomes are byte-equivalent transitively — the P6
1577
+ * exit gate "Travel Planner produces equivalent canonical outcomes under DN and DC".
1578
+ *
1579
+ * Regenerate ONLY when the Travel Planner scenario itself changes, by printing either suite's
1580
+ * normalized value; both suites must then agree on the new golden.
1581
+ */
1582
+ const phase6TravelPlannerGoldenEvidence = [
1583
+ {
1584
+ batchId: "thread-created:{threadId}",
1585
+ sequence: 1,
1586
+ record: {
1587
+ recordId: "thread-created:{threadId}",
1588
+ family: "thread",
1589
+ schemaVersion: 1,
1590
+ createdAt: "{timestamp}",
1591
+ deploymentId: "{deploymentId}",
1592
+ payload: {
1593
+ _tag: "ThreadCreated",
1594
+ agentId: "travel-planner-phase-4",
1595
+ definitions: {
1596
+ agent: "{digest}",
1597
+ model: "{digest}",
1598
+ tools: "{digest}"
1599
+ }
1600
+ }
1601
+ }
1602
+ },
1603
+ {
1604
+ batchId: "submission-input:{submissionId}",
1605
+ sequence: 2,
1606
+ record: {
1607
+ recordId: "input:{submissionId}",
1608
+ family: "thread",
1609
+ schemaVersion: 1,
1610
+ createdAt: "{timestamp}",
1611
+ deploymentId: "{deploymentId}",
1612
+ payload: {
1613
+ _tag: "UserInputRecorded",
1614
+ submissionId: "{submissionId}",
1615
+ kind: "user",
1616
+ runId: "run:{submissionId}",
1617
+ input: {
1618
+ request: "Plan a review-only London trip using the deterministic flight, lodging, and activity searches.",
1619
+ origin: "SFO",
1620
+ destination: "LHR",
1621
+ departOn: "2026-09-14",
1622
+ nights: 4,
1623
+ travelers: 2,
1624
+ budgetCents: 35e4,
1625
+ currency: "USD"
1626
+ }
1627
+ }
1628
+ }
1629
+ },
1630
+ {
1631
+ batchId: "run-start:run:{submissionId}",
1632
+ sequence: 3,
1633
+ record: {
1634
+ recordId: "run-start:run:{submissionId}",
1635
+ family: "thread",
1636
+ schemaVersion: 1,
1637
+ createdAt: "{timestamp}",
1638
+ deploymentId: "{deploymentId}",
1639
+ payload: {
1640
+ _tag: "RunStarted",
1641
+ runId: "run:{submissionId}",
1642
+ policyAccountingVersion: 1,
1643
+ maxDurationMillis: 3e4
1644
+ }
1645
+ }
1646
+ },
1647
+ {
1648
+ batchId: "turn-response:run:{submissionId}:1",
1649
+ sequence: 4,
1650
+ record: {
1651
+ recordId: "model-response:run:{submissionId}:1",
1652
+ family: "thread",
1653
+ schemaVersion: 1,
1654
+ createdAt: "{timestamp}",
1655
+ deploymentId: "{deploymentId}",
1656
+ payload: {
1657
+ _tag: "ModelResponseRecorded",
1658
+ runId: "run:{submissionId}",
1659
+ turnId: "turn:run:{submissionId}:1",
1660
+ turn: 1,
1661
+ inputTokens: 128,
1662
+ outputTokens: 96,
1663
+ runScopedPrefixLength: 2,
1664
+ modelUsage: [{
1665
+ provider: "scripted",
1666
+ model: "travel-planner-phase-4",
1667
+ purpose: "turn",
1668
+ usageStatus: "partial",
1669
+ pricingStatus: "unknown",
1670
+ inputTokens: {
1671
+ total: 128,
1672
+ uncached: 128,
1673
+ cacheRead: 0,
1674
+ cacheWrite: 0
1675
+ },
1676
+ outputTokens: {
1677
+ total: 96,
1678
+ text: 96,
1679
+ reasoning: 0
1680
+ },
1681
+ costMicrousd: 0
1682
+ }],
1683
+ messages: { content: [
1684
+ {
1685
+ options: {},
1686
+ role: "system",
1687
+ content: "You are the Effect Agent Travel Planner P1 interpreter fixture.\nThe user asked: Plan a review-only London trip using the deterministic flight, lodging, and activity searches.\nCall search_flights, search_lodging, and search_activities exactly once in one Tool batch.\nThen return only a JSON object of exactly this shape, no prose:\n{\"itineraries\": [{\"title\": \"<short itinerary name>\", \"route\": \"<origin-destination>\", \"dates\": \"<date range>\", \"flight\": \"<flight description from the Tool result>\", \"lodging\": \"<lodging description from the Tool result>\", \"activities\": [\"<activity>\", \"...\"], \"estimatedTotalCents\": <positive integer total in cents>, \"currency\": \"USD\", \"quoteId\": \"<quoteId from the flight Tool result>\", \"assumptions\": [\"<assumption>\", \"...\"], \"unresolvedConstraints\": [], \"nextAction\": \"review\"}]}\nUse the Tool results verbatim; activity results may legitimately be an empty array.\nThis is read-only planning. Require review before any mutation."
1688
+ },
1689
+ {
1690
+ options: {},
1691
+ role: "user",
1692
+ content: "{\"request\":\"Plan a review-only London trip using the deterministic flight, lodging, and activity searches.\",\"origin\":\"SFO\",\"destination\":\"LHR\",\"departOn\":\"2026-09-14\",\"nights\":4,\"travelers\":2,\"budgetCents\":350000,\"currency\":\"USD\"}"
1693
+ },
1694
+ {
1695
+ options: {},
1696
+ role: "assistant",
1697
+ content: [
1698
+ {
1699
+ options: {},
1700
+ type: "tool-call",
1701
+ id: "flight-call-1",
1702
+ name: "search_flights",
1703
+ params: {
1704
+ origin: "SFO",
1705
+ destination: "LHR",
1706
+ departOn: "2026-09-14",
1707
+ travelers: 2
1708
+ },
1709
+ providerExecuted: false
1710
+ },
1711
+ {
1712
+ options: {},
1713
+ type: "tool-call",
1714
+ id: "lodging-call-1",
1715
+ name: "search_lodging",
1716
+ params: {
1717
+ destination: "LHR",
1718
+ departOn: "2026-09-14",
1719
+ nights: 4,
1720
+ travelers: 2
1721
+ },
1722
+ providerExecuted: false
1723
+ },
1724
+ {
1725
+ options: {},
1726
+ type: "tool-call",
1727
+ id: "activity-call-1",
1728
+ name: "search_activities",
1729
+ params: {
1730
+ destination: "LHR",
1731
+ departOn: "2026-09-14",
1732
+ nights: 4,
1733
+ travelers: 2
1734
+ },
1735
+ providerExecuted: false
1736
+ }
1737
+ ]
1738
+ }
1739
+ ] },
1740
+ messagesDigest: "{digest}"
1741
+ }
1742
+ }
1743
+ },
1744
+ {
1745
+ batchId: "turn-results:run:{submissionId}:1",
1746
+ sequence: 5,
1747
+ record: {
1748
+ recordId: "tool-settled:run:{submissionId}:1:flight-call-1",
1749
+ family: "thread",
1750
+ schemaVersion: 1,
1751
+ createdAt: "{timestamp}",
1752
+ deploymentId: "{deploymentId}",
1753
+ payload: {
1754
+ _tag: "ToolCallSettled",
1755
+ runId: "run:{submissionId}",
1756
+ toolCallId: "flight-call-1",
1757
+ toolName: "search_flights",
1758
+ result: {
1759
+ quoteId: "quote-sfo-lhr-001",
1760
+ flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
1761
+ estimatedCents: 18e4,
1762
+ currency: "USD"
1763
+ },
1764
+ isFailure: false
1765
+ }
1766
+ }
1767
+ },
1768
+ {
1769
+ batchId: "turn-results:run:{submissionId}:1",
1770
+ sequence: 6,
1771
+ record: {
1772
+ recordId: "tool-settled:run:{submissionId}:1:lodging-call-1",
1773
+ family: "thread",
1774
+ schemaVersion: 1,
1775
+ createdAt: "{timestamp}",
1776
+ deploymentId: "{deploymentId}",
1777
+ payload: {
1778
+ _tag: "ToolCallSettled",
1779
+ runId: "run:{submissionId}",
1780
+ toolCallId: "lodging-call-1",
1781
+ toolName: "search_lodging",
1782
+ result: {
1783
+ lodging: "Bloomsbury House · refundable studio · 4 nights",
1784
+ estimatedCents: 104e3,
1785
+ currency: "USD"
1786
+ },
1787
+ isFailure: false
1788
+ }
1789
+ }
1790
+ },
1791
+ {
1792
+ batchId: "turn-results:run:{submissionId}:1",
1793
+ sequence: 7,
1794
+ record: {
1795
+ recordId: "tool-settled:run:{submissionId}:1:activity-call-1",
1796
+ family: "thread",
1797
+ schemaVersion: 1,
1798
+ createdAt: "{timestamp}",
1799
+ deploymentId: "{deploymentId}",
1800
+ payload: {
1801
+ _tag: "ToolCallSettled",
1802
+ runId: "run:{submissionId}",
1803
+ toolCallId: "activity-call-1",
1804
+ toolName: "search_activities",
1805
+ result: { activities: ["British Museum timed entry", "Thames evening walk"] },
1806
+ isFailure: false
1807
+ }
1808
+ }
1809
+ },
1810
+ {
1811
+ batchId: "turn:run:{submissionId}:2",
1812
+ sequence: 8,
1813
+ record: {
1814
+ recordId: "model-response:run:{submissionId}:2",
1815
+ family: "thread",
1816
+ schemaVersion: 1,
1817
+ createdAt: "{timestamp}",
1818
+ deploymentId: "{deploymentId}",
1819
+ payload: {
1820
+ _tag: "ModelResponseRecorded",
1821
+ runId: "run:{submissionId}",
1822
+ turnId: "turn:run:{submissionId}:2",
1823
+ turn: 2,
1824
+ inputTokens: 128,
1825
+ outputTokens: 96,
1826
+ modelUsage: [{
1827
+ provider: "scripted",
1828
+ model: "travel-planner-phase-4",
1829
+ purpose: "turn",
1830
+ usageStatus: "partial",
1831
+ pricingStatus: "unknown",
1832
+ inputTokens: {
1833
+ total: 128,
1834
+ uncached: 128,
1835
+ cacheRead: 0,
1836
+ cacheWrite: 0
1837
+ },
1838
+ outputTokens: {
1839
+ total: 96,
1840
+ text: 96,
1841
+ reasoning: 0
1842
+ },
1843
+ costMicrousd: 0
1844
+ }],
1845
+ messages: { content: [{
1846
+ options: {},
1847
+ role: "assistant",
1848
+ content: "{\"itineraries\":[{\"title\":\"Westward light, eastbound overnight\",\"route\":\"San Francisco → London\",\"dates\":\"14–19 September 2026\",\"flight\":\"EA 218 · nonstop · SFO 18:40 → LHR 13:05+1\",\"lodging\":\"Bloomsbury House · refundable studio · 4 nights\",\"activities\":[\"British Museum timed entry\",\"Thames evening walk\"],\"estimatedTotalCents\":284000,\"currency\":\"USD\",\"quoteId\":\"quote-sfo-lhr-001\",\"assumptions\":[\"Two travelers sharing one studio\",\"Quote is read-only availability, not a reservation\"],\"unresolvedConstraints\":[\"Traveler names and accessibility requests are intentionally omitted\"],\"nextAction\":\"review\"}]}"
1849
+ }] },
1850
+ messagesDigest: "{digest}"
1851
+ }
1852
+ }
1853
+ },
1854
+ {
1855
+ batchId: "turn:run:{submissionId}:2",
1856
+ sequence: 9,
1857
+ record: {
1858
+ recordId: "run-completed:run:{submissionId}",
1859
+ family: "thread",
1860
+ schemaVersion: 1,
1861
+ createdAt: "{timestamp}",
1862
+ deploymentId: "{deploymentId}",
1863
+ payload: {
1864
+ _tag: "RunCompleted",
1865
+ runId: "run:{submissionId}",
1866
+ output: { itineraries: [{
1867
+ title: "Westward light, eastbound overnight",
1868
+ route: "San Francisco → London",
1869
+ dates: "14–19 September 2026",
1870
+ flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
1871
+ lodging: "Bloomsbury House · refundable studio · 4 nights",
1872
+ activities: ["British Museum timed entry", "Thames evening walk"],
1873
+ estimatedTotalCents: 284e3,
1874
+ currency: "USD",
1875
+ quoteId: "quote-sfo-lhr-001",
1876
+ assumptions: ["Two travelers sharing one studio", "Quote is read-only availability, not a reservation"],
1877
+ unresolvedConstraints: ["Traveler names and accessibility requests are intentionally omitted"],
1878
+ nextAction: "review"
1879
+ }] }
1880
+ }
1881
+ }
1882
+ },
1883
+ {
1884
+ batchId: "submission-settlement:{submissionId}",
1885
+ sequence: 10,
1886
+ record: {
1887
+ recordId: "settlement:{submissionId}",
1888
+ family: "thread",
1889
+ schemaVersion: 1,
1890
+ createdAt: "{timestamp}",
1891
+ deploymentId: "{deploymentId}",
1892
+ payload: {
1893
+ _tag: "SubmissionSettled",
1894
+ submissionId: "{submissionId}",
1895
+ settlementId: "settlement:{submissionId}",
1896
+ receiptId: "{receiptId}",
1897
+ outcome: "completed",
1898
+ runId: "run:{submissionId}",
1899
+ result: { itineraries: [{
1900
+ title: "Westward light, eastbound overnight",
1901
+ route: "San Francisco → London",
1902
+ dates: "14–19 September 2026",
1903
+ flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
1904
+ lodging: "Bloomsbury House · refundable studio · 4 nights",
1905
+ activities: ["British Museum timed entry", "Thames evening walk"],
1906
+ estimatedTotalCents: 284e3,
1907
+ currency: "USD",
1908
+ quoteId: "quote-sfo-lhr-001",
1909
+ assumptions: ["Two travelers sharing one studio", "Quote is read-only availability, not a reservation"],
1910
+ unresolvedConstraints: ["Traveler names and accessibility requests are intentionally omitted"],
1911
+ nextAction: "review"
1912
+ }] },
1913
+ usageSummary: {
1914
+ usageStatus: "partial",
1915
+ pricingStatus: "unknown",
1916
+ unobservedModelCalls: 0,
1917
+ modelCalls: 2,
1918
+ inputTokens: {
1919
+ total: 256,
1920
+ uncached: 256,
1921
+ cacheRead: 0,
1922
+ cacheWrite: 0
1923
+ },
1924
+ outputTokens: {
1925
+ total: 192,
1926
+ text: 192,
1927
+ reasoning: 0
1928
+ },
1929
+ costMicrousd: 0,
1930
+ byModel: [{
1931
+ provider: "scripted",
1932
+ model: "travel-planner-phase-4",
1933
+ modelCalls: 2,
1934
+ inputTokens: {
1935
+ total: 256,
1936
+ uncached: 256,
1937
+ cacheRead: 0,
1938
+ cacheWrite: 0
1939
+ },
1940
+ outputTokens: {
1941
+ total: 192,
1942
+ text: 192,
1943
+ reasoning: 0
1944
+ },
1945
+ costMicrousd: 0
1946
+ }]
1947
+ }
1948
+ }
1949
+ }
1950
+ }
1951
+ ];
1952
+ //#endregion
1953
+ //#region src/fixtures/travel-planner/phase7.ts
1954
+ /** Explicit opt-in switch for live integration tests. */
1955
+ const PHASE7_LIVE_GATE_ENV = "EFFECT_AGENT_LIVE";
1956
+ /** The credential required by the Travel Planner live-model tests. */
1957
+ const PHASE7_LIVE_CREDENTIAL_ENV = "OPENAI_API_KEY";
1958
+ /** Enable live tests only when both the opt-in flag and credential are present. */
1959
+ const phase7LiveProfileEnabled = (env) => env["EFFECT_AGENT_LIVE"] === "1" && (env["OPENAI_API_KEY"] ?? "") !== "";
1960
+ //#endregion
1961
+ export { ActivityCatalog, ActivityCatalogLayer, ActivityQuery, ActivitySearchResult, ActivityUnavailable, AirportCode, BookFlight, BookItinerary, BookingRef, CancelBooking, CancelBookingRequest, CancellationConfirmation, CatalogLifecycle, CatalogLifecycleCounts, DestinationBrief, DestinationFacts, DestinationGuide, DestinationGuideLayer, DestinationGuideUnavailable, DestinationQuery, DestinationRecommendation, DestinationReport, DestinationResearchFailed, DestinationResearchFindings, DestinationResearchRequest, DestinationResearchSupportLayer, DestinationResearcher, DestinationResearcherToolkit, DestinationResearcherToolkitLayer, DestinationShortlist, DeterministicIdGeneratorLayer, DurableSearchActivities, DurableSearchFlights, DurableSearchLodging, FlightBookingRequest, FlightCatalog, FlightCatalogLayer, FlightOption, FlightQuery, FlightUnavailable, GuidanceFailure, HoldItinerary, Itinerary, ItineraryBookingRequest, ItineraryConfirmation, ItineraryHold, ItineraryHoldGateway, ItineraryHoldRequest, ItineraryHoldUnavailable, LodgingCatalog, LodgingCatalogLayer, LodgingOption, LodgingQuery, LodgingUnavailable, LookupDestination, PHASE7_LIVE_CREDENTIAL_ENV, PHASE7_LIVE_GATE_ENV, QuoteId, ResearchDispatchGate, ResearchMission, ReverseCompletionToolkitLayer, SearchActivities, SearchFlights, SearchLodging, SupplierBookingConfirmation, SupplierBookingDesk, SupplierBookingRecord, SupplierOperation, SupplierUnavailable, TravelBookingReport, TravelCoordinator, TravelCoordinatorToolkit, TravelGuidance, TravelGuidanceLayer, TravelPlan, TravelPlanner, TravelPlannerBookingEvidenceError, TravelPlannerDurableEvidenceError, TravelPlannerPhase2, TravelPlannerPhase2Toolkit, TravelPlannerPhase2ToolkitLayer, TravelPlannerPhase4, TravelPlannerPhase4Toolkit, TravelPlannerPhase4ToolkitLayer, TravelPlannerPhase5, TravelPlannerPhase5Toolkit, TravelPlannerPhase5ToolkitLayer, TravelPlannerProjectionError, TravelPlannerRuntimeLayer, TravelPlannerToolkit, TravelPlannerToolkitLayer, TravelSupplierReconcilerLayer, TravelerRef, TripRequest, assertSettledBookingsExistAtSupplier, bookFlightIdempotencyKey, cancelBookingIdempotencyKey, coordinatorConfidentialMarker, coordinatorResearchTurn, coordinatorShortlistTurn, destinationLookup, destinationReportFor, destinationResearchDelegation, destinationResearchHandlersLayer, destinationResearchPolicy, durableChildLookupCallId, durableDestinationResearchHandlersLayer, durableResearchAllocation, durableResearchCallId, durableResearchFinding, durableResearchShortlist, encodedDestinationFacts, encodedDestinationReport, expectedDestinationShortlist, expectedTravelPlan, itineraryStepIdempotencyKey, makeDestinationResearcherModel, makeDurableResearchHarness, makeInvocationCountingModel, makePhase3TravelPlannerCheckpoint, makePhase4TravelPlannerAgent, makePhase6TravelPlannerBindings, mapResearchChildFailure, missionConfidentialMarker, normalizeCrossPlatformTravelPlannerEvidence, normalizeDurableTravelPlannerEvidence, phase1HappyPathTurns, phase1Trip, phase3TravelPlannerBatches, phase3TravelPlannerCompletionBatch, phase3TravelPlannerDefinitionDigests, phase3TravelPlannerEncodedFixture, phase3TravelPlannerInitialBatch, phase3TravelPlannerProducerId, phase3TravelPlannerRunId, phase3TravelPlannerThreadId, phase4TravelPlannerDefinitionDigests, phase4TravelPlannerDeploymentId, phase4TravelPlannerPrincipal, phase4TravelPlannerProducerId, phase4TravelPlannerSubmitOptions, phase4TravelPlannerWorkerLayer, phase5TravelPlannerDefinitionDigests, phase5TravelPlannerDeploymentId, phase5TravelPlannerPrincipal, phase5TravelPlannerProducerId, phase5TravelPlannerSubmitOptions, phase5TravelPlannerWorkerLayer, phase6ActivityCallId, phase6BookingModel, phase6BookingRef, phase6BookingToolCallId, phase6BookingTrip, phase6ChildLookupCallId, phase6CoordinatorModel, phase6FlightCallId, phase6GatedPlannerDefinitionDigests, phase6GatedPlannerModel, phase6GatedTrip, phase6GuideInvocationCount, phase6LodgingCallId, phase6PlannerModel, phase6ResearchDestination, phase6ResearchMission, phase6ResearcherModel, phase6SupplierDesk, phase6SupplierDeskLayer, phase6SupplierReconcilerLayer, phase6TravelPlannerDeploymentId, phase6TravelPlannerGoldenEvidence, phase6TravelPlannerProducerId, phase6TravelPlannerProducerPrefix, phase7LiveProfileEnabled, releasePhase6PlannerGate, releasePhase6ResearcherGate, researchMission, researcherHappyPathTurns, resetPhase6PlannerGate, resetPhase6ResearcherGate, s2CoordinatorDigests, s2CoordinatorSubmitAgent, s2ResearcherDigestStrings, s2ResearcherDigests, s2TravelPlannerDeploymentId, s2TravelPlannerPrincipal, s2TravelPlannerProducerId, s2TravelPlannerSubmitOptions, supplierBookingRefFor, travelPlanFromDurableSettlement, travelPlanFromProjection };
1962
+
1963
+ //# sourceMappingURL=TravelPlanner.mjs.map