@effect-agent/testing 0.1.0-beta.8 → 0.1.0-beta.81

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 (54) hide show
  1. package/dist/Certification.d.mts +105 -0
  2. package/dist/Certification.mjs +592 -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 +270 -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 +1665 -0
  21. package/dist/TravelPlanner.mjs +1963 -0
  22. package/dist/TravelPlanner.mjs.map +1 -0
  23. package/dist/deterministic-layers-Eka0fMZq.mjs +358 -0
  24. package/dist/deterministic-layers-Eka0fMZq.mjs.map +1 -0
  25. package/dist/index.d.mts +2 -3406
  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} +251 -117
  30. package/src/{chaos.ts → Chaos.ts} +246 -122
  31. package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +29 -6
  32. package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +112 -45
  33. package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
  34. package/src/{scripted-model.ts → ScriptedModel.ts} +22 -25
  35. package/src/TravelPlanner.ts +232 -0
  36. package/src/fixtures/docs-researcher/definition.ts +19 -10
  37. package/src/fixtures/docs-researcher/harness.ts +41 -30
  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 +18 -41
  43. package/src/fixtures/travel-planner/phase4.ts +23 -36
  44. package/src/fixtures/travel-planner/phase5.ts +38 -41
  45. package/src/fixtures/travel-planner/phase6.ts +187 -84
  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 +33 -57
  49. package/src/fixtures/travel-planner/subagents.ts +35 -13
  50. package/src/index.ts +1 -11
  51. package/dist/index.mjs.map +0 -1
  52. package/src/code-executor-conformance.d.ts +0 -30
  53. package/src/fixtures/travel-planner/index.ts +0 -11
  54. package/src/fixtures/warehouse/index.ts +0 -412
package/dist/index.mjs CHANGED
@@ -1,4771 +1,2 @@
1
- import { McpConnectionRequest, McpConnector, McpServerIdentity, McpToolkitMismatch, Redactor, Subagent, SubagentPolicy, SubagentReservationsMemoryLive, SubagentRuntime, connectMcp, delegationAllocationFromPolicy } from "@effect-agent/capabilities";
2
- import { Agent, AgentId, AgentPolicy, ConversationId, IdGenerator, RunId, SubmissionId, ToolCallId, TurnId } from "@effect-agent/core";
3
- import { DurableStep, DurableStepError, ToolExecutionClass } from "@effect-agent/engine";
4
- import { AbortCommand, AgentBindingResolver, ApprovalDecisionCommand, BatchId, CanonicalBatch, CanonicalRecordEnvelope, CertificationReport, CertificationSweepResult, CertificationTierThreeReport, CertifiedAdapterIdentity, ConversationCheckpoint, ConversationExportRequest, ConversationProjection, ConversationStore, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, Digest, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, DurableRuntimeFailpointError, DurableRuntimeFailpointLocation, DurableRuntimeFailpointTestControl, DurableWorkerBinding, IdempotencyKey, LoadCheckpointRequest, ObligationThresholds, PersistedJson, Principal, ProducerId, ReconciliationCompleted, ReconciliationSafeToRetry, ReconciliationUncertain, RecordEnvelope, RecordId, ResolutionAbortSubmission, ResolutionCompletedWithResult, ResolutionNeverHappened, ResolutionSafeToRetry, SubmissionLedger, SubmissionLookupById, ToolReconciler, ToolReconcilerError, UnknownResolutionCommand, WakeScheduler, certifyPorts, childConversationIdFor, verifyConversationInvariants } from "@effect-agent/session";
5
- import { Cause, Clock, Context, DateTime, Deferred, Duration, Effect, Exit, Fiber, Layer, Option, Queue, Ref, Schema, Stream } from "effect";
6
- import { FastCheck, TestClock } from "effect/testing";
7
- import { AiError, LanguageModel, Model, Response, Tool, Toolkit } from "effect/unstable/ai";
8
- import { CodeExecutionHost, CodeExecutionLimits, CodeExecutionNamespace, CodeExecutionProtocolError, CodeExecutionRequest, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallFailure, CodeHostCallLimitError, CodeHostCallResult, CodeHostCallSuccess, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, NetworkAllowlist, NetworkDisabled, SandboxImplementation } from "@effect-agent/sandbox";
9
- import * as McpSchema from "effect/unstable/ai/McpSchema";
10
- //#region src/certification.ts
11
- /** The six Tier-2 scenario shapes in sweep order. */
12
- const CERTIFICATION_SCENARIOS = [
13
- "plain",
14
- "uncertain-tool",
15
- "durable-steps",
16
- "approval",
17
- "join",
18
- "delegation"
19
- ];
20
- /**
21
- * Coordinator failpoint locations that none of the six scenario shapes can reach, recorded
22
- * honestly instead of silently claimed: all three sit on operator/abort paths the shapes do
23
- * not take. They are pinned in-process by the P5/S2 suites
24
- * (`packages/testing/test/durable-tools.test.ts` "resolveUnknown is idempotent across the
25
- * intent failpoint", `durable-runtime.test.ts` abort rows,
26
- * `durable-subagents.test.ts` abort propagation) and by the process-kill/eviction crash
27
- * matrices. Runner tests assert the observed never-fired set equals EXACTLY this list, so a
28
- * protocol change that silently stops exercising a location fails the certification.
29
- */
30
- const TIER2_UNREACHED_LOCATIONS = [
31
- "abort:after-intent",
32
- "compaction:after-canonical-append",
33
- "resolve:after-intent",
34
- "subagent:after-child-abort-intent"
35
- ];
36
- /** Locations of `tier2` rows whose armed fault never fired in ANY scenario, sorted. */
37
- const tier2NeverFiredLocations = (tier2) => {
38
- const fired = /* @__PURE__ */ new Set();
39
- for (const row of tier2) if (row.failpointFired) fired.add(row.location);
40
- return DurableRuntimeFailpointLocation.literals.filter((location) => !fired.has(location)).sort();
41
- };
42
- const SHA_A = Schema.decodeSync(Digest)("a".repeat(64));
43
- const DIGESTS = DefinitionDigests.make({
44
- agent: SHA_A,
45
- model: SHA_A,
46
- tools: SHA_A
47
- });
48
- const CHILD_DIGEST_STRINGS = {
49
- agent: "b".repeat(64),
50
- model: "c".repeat(64),
51
- tools: "d".repeat(64)
52
- };
53
- const CHILD_DIGESTS = DefinitionDigests.make({
54
- agent: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.agent),
55
- model: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.model),
56
- tools: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.tools)
57
- });
58
- const PRINCIPAL = Schema.decodeSync(Principal)("principal-certification");
59
- const decodeConversationId$1 = Schema.decodeSync(ConversationId);
60
- const decodeIdempotencyKey$1 = Schema.decodeSync(IdempotencyKey);
61
- const decodeToolCallId$1 = Schema.decodeSync(ToolCallId);
62
- const usage$2 = {
63
- inputTokens: {},
64
- outputTokens: {}
65
- };
66
- const finalParts$1 = (text) => [
67
- {
68
- type: "text-start",
69
- id: "answer"
70
- },
71
- {
72
- type: "text-delta",
73
- id: "answer",
74
- delta: text
75
- },
76
- {
77
- type: "text-end",
78
- id: "answer"
79
- },
80
- {
81
- type: "finish",
82
- reason: "stop",
83
- usage: usage$2
84
- }
85
- ];
86
- const toolCallPart$1 = (id, name, params) => ({
87
- type: "tool-call",
88
- id,
89
- name,
90
- params,
91
- providerExecuted: false
92
- });
93
- const toolTurn$1 = (...calls) => [...calls, {
94
- type: "finish",
95
- reason: "tool-calls",
96
- usage: usage$2
97
- }];
98
- /**
99
- * Stateless scripted model that decides by PROMPT SHAPE instead of call count: while the
100
- * prompt carries no committed tool result the model declares `toolParts` (when given),
101
- * otherwise it answers with the final text. Deciding on the canonical prompt keeps every cell
102
- * deterministic regardless of where the injected fault fell — a re-invoked Turn re-declares
103
- * the same batch and a resumed batch flows into the final answer, so every scenario always
104
- * exercises its tool path and always converges.
105
- */
106
- const promptShapeModel = (name, finalText, toolParts) => Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
107
- generateText: () => Effect.succeed([]),
108
- streamText: (request) => {
109
- const hasToolResult = request.prompt.content.some((message) => message.role === "tool");
110
- const parts = toolParts === void 0 || hasToolResult ? finalParts$1(finalText) : toolParts;
111
- return Stream.fromIterable(parts);
112
- }
113
- })));
114
- const policy$1 = AgentPolicy.make({
115
- maxTurns: 4,
116
- maxToolCalls: 4,
117
- maxDuration: "30 seconds",
118
- toolConcurrency: 2
119
- });
120
- const QuestionInput = Schema.Struct({ question: Schema.String });
121
- const AnswerOutput = Schema.Struct({ answer: Schema.String });
122
- /** plain / join: no tools — the pure Turn/submission/join seams. */
123
- const plainDefinition$1 = Agent.define("certify-plain", {
124
- input: QuestionInput,
125
- output: AnswerOutput,
126
- instructions: "Answer as JSON.",
127
- toolkit: Toolkit.empty,
128
- policy: policy$1
129
- });
130
- /** uncertain-tool: unannotated → fail-closed `uncertain`, enters the prepared/settled protocol. */
131
- const Book = Tool.make("book", {
132
- parameters: Schema.Struct({ ref: Schema.String }),
133
- success: Schema.Struct({ confirmation: Schema.String })
134
- });
135
- const bookToolkit = Toolkit.make(Book);
136
- const uncertainDefinition = Agent.define("certify-uncertain", {
137
- input: QuestionInput,
138
- output: AnswerOutput,
139
- instructions: "Book it.",
140
- toolkit: bookToolkit,
141
- policy: policy$1
142
- });
143
- /** durable-steps: declaring `DurableStep` as a dependency is what makes the Tool durable. */
144
- const Itinerary$2 = Tool.make("itinerary", {
145
- parameters: Schema.Struct({ ref: Schema.String }),
146
- success: Schema.Struct({ state: Schema.String }),
147
- failure: DurableStepError,
148
- dependencies: [DurableStep]
149
- });
150
- const itineraryToolkit = Toolkit.make(Itinerary$2);
151
- const stepsDefinition = Agent.define("certify-steps", {
152
- input: QuestionInput,
153
- output: AnswerOutput,
154
- instructions: "Reserve the itinerary.",
155
- toolkit: itineraryToolkit,
156
- policy: policy$1
157
- });
158
- /** approval: fail-closed — no `DurableApprovalResolver` Layer, so undecided approvals suspend. */
159
- const BookApproval$1 = Tool.make("book", {
160
- parameters: Schema.Struct({ ref: Schema.String }),
161
- success: Schema.Struct({ confirmation: Schema.String }),
162
- needsApproval: true
163
- });
164
- const approvalToolkit = Toolkit.make(BookApproval$1);
165
- const approvalDefinition$1 = Agent.define("certify-approval", {
166
- input: QuestionInput,
167
- output: AnswerOutput,
168
- instructions: "Book after approval.",
169
- toolkit: approvalToolkit,
170
- policy: policy$1
171
- });
172
- /** delegation: durable attached child plus an ordinary uncertain sibling in ONE batch. */
173
- const childDefinition$1 = Agent.define("certify-child", {
174
- input: QuestionInput,
175
- output: AnswerOutput,
176
- instructions: "Answer as JSON.",
177
- toolkit: Toolkit.empty,
178
- policy: AgentPolicy.make({
179
- maxTurns: 2,
180
- maxToolCalls: 1,
181
- maxDuration: "30 seconds",
182
- toolConcurrency: 1
183
- })
184
- });
185
- var CertifyDelegationFailed = class extends Schema.TaggedError()("CertifyDelegationFailed", { childErrorTag: Schema.String }) {};
186
- const researchDelegation = Subagent.define("delegate_research", {
187
- description: "Research one bounded question and return findings.",
188
- target: childDefinition$1,
189
- parameters: Schema.Struct({ topic: Schema.String }),
190
- success: Schema.Struct({ summary: Schema.String }),
191
- failure: CertifyDelegationFailed,
192
- prepareInput: ({ topic }) => Effect.succeed({ question: `research:${topic}` }),
193
- projectResult: (output) => Effect.succeed({ summary: `finding:${output.answer}` }),
194
- policy: SubagentPolicy.make({
195
- maxChildren: 2,
196
- maxConcurrency: 2,
197
- maxTurns: 4,
198
- maxToolCalls: 4,
199
- maxDuration: "10 seconds"
200
- })
201
- });
202
- const Lookup = Tool.make("lookup", {
203
- parameters: Schema.Struct({ key: Schema.String }),
204
- success: Schema.Struct({ value: Schema.String })
205
- });
206
- const coordinatorDefinition$1 = Agent.define("certify-coordinator", {
207
- input: Schema.Struct({ mission: Schema.String }),
208
- output: Schema.Struct({ report: Schema.String }),
209
- instructions: "Delegate and look up, then answer as JSON.",
210
- toolkit: Toolkit.make(researchDelegation.tool, Lookup),
211
- policy: AgentPolicy.make({
212
- maxTurns: 4,
213
- maxToolCalls: 3,
214
- maxDuration: "30 seconds",
215
- toolConcurrency: 2
216
- })
217
- });
218
- const mapChildFailure = (failure) => CertifyDelegationFailed.make({ childErrorTag: failure._tag });
219
- const DELEGATE_CALL = decodeToolCallId$1("delegate-1");
220
- /** Fixture-only identity source consumed by the delegation Layer's ephemeral capture. */
221
- const identifiers = Layer.effect(IdGenerator, Effect.gen(function* () {
222
- const counter = yield* Ref.make(0);
223
- const next = (decode, prefix) => Ref.getAndUpdate(counter, (value) => value + 1).pipe(Effect.map((value) => decode(`${prefix}-${value}`)));
224
- return {
225
- nextConversationId: next(decodeConversationId$1, "certify-fixture-conversation"),
226
- nextRunId: next(Schema.decodeSync(RunId), "certify-fixture-run"),
227
- nextTurnId: next(Schema.decodeSync(TurnId), "certify-fixture-turn")
228
- };
229
- }));
230
- const delegationSupport$1 = Layer.mergeAll(SubagentReservationsMemoryLive, identifiers);
231
- const submitOptionsFor = (slug, conversationId) => ({
232
- conversationId,
233
- principal: PRINCIPAL,
234
- idempotencyKey: decodeIdempotencyKey$1(`certify-key-${slug}`),
235
- definitions: DIGESTS
236
- });
237
- /** One single-agent cell: one lane, one Submission, one registered exact-digest binding. */
238
- const makeSingleAgentCell = (definition, resolved, slug) => {
239
- const conversationId = decodeConversationId$1(`certify-${slug}`);
240
- const submit = Effect.gen(function* () {
241
- return [yield* (yield* DurableAgentRuntime).submit({ definition: {
242
- id: definition.id,
243
- input: definition.input
244
- } }, { question: `certify ${slug}` }, submitOptionsFor(slug, conversationId))];
245
- });
246
- return {
247
- resolver: AgentBindingResolver.fromBindings([resolved]),
248
- submit,
249
- lanes: () => [conversationId]
250
- };
251
- };
252
- const makeCell = Effect.fn("Certification.makeCell")(function* (scenario, slug) {
253
- switch (scenario) {
254
- case "plain": {
255
- const binding = Agent.withModel(plainDefinition$1, promptShapeModel("certify-plain", "{\"answer\":\"done\"}"));
256
- const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
257
- return makeSingleAgentCell(plainDefinition$1, resolved, slug);
258
- }
259
- case "uncertain-tool": {
260
- const binding = Agent.withModel(uncertainDefinition, promptShapeModel("certify-uncertain", "{\"answer\":\"booked\"}", toolTurn$1(toolCallPart$1("book-1", "book", { ref: `r-${slug}` }))));
261
- const toolLayer = bookToolkit.toLayer({ book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }) });
262
- const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(Effect.provide(toolLayer));
263
- return makeSingleAgentCell(uncertainDefinition, resolved, slug);
264
- }
265
- case "durable-steps": {
266
- const binding = Agent.withModel(stepsDefinition, promptShapeModel("certify-steps", "{\"answer\":\"reserved\"}", toolTurn$1(toolCallPart$1("itinerary-1", "itinerary", { ref: `trip-${slug}` }))));
267
- const toolLayer = itineraryToolkit.toLayer({ itinerary: ({ ref }) => Effect.gen(function* () {
268
- const step = yield* DurableStep;
269
- return { state: `${yield* step.do("reserve-flight", Schema.String, Effect.succeed(`flight-${ref}`))}+${yield* step.do("reserve-lodging", Schema.String, Effect.succeed(`lodging-${ref}`))}` };
270
- }) });
271
- const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(Effect.provide(toolLayer));
272
- return makeSingleAgentCell(stepsDefinition, resolved, slug);
273
- }
274
- case "approval": {
275
- const binding = Agent.withModel(approvalDefinition$1, promptShapeModel("certify-approval", "{\"answer\":\"approved\"}", toolTurn$1(toolCallPart$1("book-1", "book", { ref: `r-${slug}` }))));
276
- const toolLayer = approvalToolkit.toLayer({ book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }) });
277
- const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(Effect.provide(toolLayer));
278
- return makeSingleAgentCell(approvalDefinition$1, resolved, slug);
279
- }
280
- case "join": {
281
- const binding = Agent.withModel(plainDefinition$1, promptShapeModel("certify-join", "{\"answer\":\"host answer\"}"));
282
- const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
283
- const conversationId = decodeConversationId$1(`certify-${slug}`);
284
- const submitOne = (key, question) => Effect.gen(function* () {
285
- return yield* (yield* DurableAgentRuntime).submit({ definition: {
286
- id: plainDefinition$1.id,
287
- input: plainDefinition$1.input
288
- } }, { question }, {
289
- conversationId,
290
- principal: PRINCIPAL,
291
- idempotencyKey: decodeIdempotencyKey$1(key),
292
- definitions: DIGESTS
293
- });
294
- });
295
- return {
296
- resolver: AgentBindingResolver.fromBindings([resolved]),
297
- submit: Effect.gen(function* () {
298
- return [yield* submitOne(`certify-key-${slug}-host`, "host question"), yield* submitOne(`certify-key-${slug}-queued`, "queued question")];
299
- }),
300
- lanes: () => [conversationId]
301
- };
302
- }
303
- case "delegation": {
304
- const childBinding = Agent.withModel(childDefinition$1, promptShapeModel("certify-child", "{\"answer\":\"child-answer\"}"));
305
- const parentBinding = Agent.withModel(coordinatorDefinition$1, promptShapeModel("certify-parent", "{\"report\":\"done\"}", toolTurn$1(toolCallPart$1("delegate-1", "delegate_research", { topic: "paris" }), toolCallPart$1("lookup-1", "lookup", { key: "hotels" }))));
306
- const delegationLayer = SubagentRuntime.layer(researchDelegation, childBinding, {
307
- mapChildFailure,
308
- durable: { targetDigests: CHILD_DIGEST_STRINGS }
309
- }).pipe(Layer.provide(delegationSupport$1));
310
- const lookupLayer = Toolkit.make(Lookup).toLayer({ lookup: ({ key }) => Effect.succeed({ value: `found-${key}` }) });
311
- const parentResolved = yield* DurableWorkerBinding.make(parentBinding, DIGESTS).pipe(Effect.provide(Layer.mergeAll(delegationLayer, lookupLayer)));
312
- const childResolved = yield* DurableWorkerBinding.make(childBinding, CHILD_DIGESTS);
313
- const conversationId = decodeConversationId$1(`certify-${slug}`);
314
- return {
315
- resolver: AgentBindingResolver.fromBindings([parentResolved, childResolved]),
316
- submit: Effect.gen(function* () {
317
- return [yield* (yield* DurableAgentRuntime).submit({ definition: {
318
- id: coordinatorDefinition$1.id,
319
- input: coordinatorDefinition$1.input
320
- } }, { mission: "plan" }, submitOptionsFor(slug, conversationId))];
321
- }),
322
- lanes: (receipts) => {
323
- const parent = receipts.at(0);
324
- return parent === void 0 ? [conversationId] : [conversationId, childConversationIdFor(parent.submissionId, DELEGATE_CALL)];
325
- }
326
- };
327
- }
328
- }
329
- });
330
- /** Maximum recovery/drive/unblock rounds before a cell is reported non-convergent. */
331
- const MAX_REDRIVE_ROUNDS = 8;
332
- /**
333
- * Verify one lane after convergence: canonical export + every lane Submission the ledger or
334
- * the log names (the same collection rule as the admin `verify` member), fed to the shared
335
- * invariant checker in convergence mode WITH the captured per-batch producer directory, so
336
- * the digest chain is fully recomputed instead of skipped.
337
- */
338
- const verifyLane = Effect.fn("Certification.verifyLane")(function* (lane, batchProducers) {
339
- const store = yield* ConversationStore;
340
- const ledger = yield* SubmissionLedger;
341
- const exported = yield* store.export(ConversationExportRequest.make({ conversationId: lane }));
342
- const rows = /* @__PURE__ */ new Map();
343
- const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
344
- for (const submission of nonterminal) if (submission.conversationId === lane) rows.set(submission.submissionId, submission);
345
- const named = /* @__PURE__ */ new Set();
346
- for (const envelope of exported.records) {
347
- const payload = envelope.record.payload;
348
- if (payload._tag === "UserInputRecorded" || payload._tag === "SubmissionSettled" || payload._tag === "AbortRequested") named.add(payload.submissionId);
349
- }
350
- for (const submissionId of named) {
351
- if (rows.has(submissionId)) continue;
352
- const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId }));
353
- if (Option.isSome(found) && found.value.conversationId === lane) rows.set(submissionId, found.value);
354
- }
355
- const checkpoint = yield* store.loadCheckpoint(LoadCheckpointRequest.make({ conversationId: lane }));
356
- return yield* verifyConversationInvariants({
357
- export: exported,
358
- submissions: [...rows.values()],
359
- batchProducers,
360
- checkpoint: Option.getOrUndefined(checkpoint),
361
- requireAllSettled: true
362
- });
363
- });
364
- const failureTagOf = (cause) => {
365
- const failure = Cause.findErrorOption(cause);
366
- if (Option.isSome(failure)) {
367
- const error = failure.value;
368
- if (typeof error === "object" && error !== null && "_tag" in error) return String(error._tag);
369
- return String(error).slice(0, 256);
370
- }
371
- return "defect";
372
- };
373
- /** One Tier-2 sweep cell: arm `location` one-shot, drive `scenario`, converge, verify. */
374
- const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (scenario, location, batchProducers, leaseAdvance) {
375
- const runtime = yield* DurableAgentRuntime;
376
- const ledger = yield* SubmissionLedger;
377
- const control = yield* DurableRuntimeFailpointTestControl;
378
- const slug = `${scenario}-${location.replaceAll(":", "-")}`;
379
- const failed = (detail, fired) => CertificationSweepResult.make({
380
- scenario,
381
- location,
382
- failpointFired: fired,
383
- status: "failed",
384
- digestChainVerified: false,
385
- detail: detail.slice(0, 4096)
386
- });
387
- const cell = yield* makeCell(scenario, slug);
388
- const fired = yield* Ref.make(false);
389
- yield* control.setHandler((hit) => hit !== location ? Effect.void : Ref.getAndSet(fired, true).pipe(Effect.flatMap((already) => already ? Effect.void : Effect.fail(DurableRuntimeFailpointError.make({ location: hit })))));
390
- let receipts;
391
- const firstSubmit = yield* Effect.exit(cell.submit);
392
- if (Exit.isSuccess(firstSubmit)) receipts = firstSubmit.value;
393
- else {
394
- const secondSubmit = yield* Effect.exit(cell.submit);
395
- if (Exit.isFailure(secondSubmit)) {
396
- yield* control.clear;
397
- return failed(`submission replay did not recover: ${failureTagOf(secondSubmit.cause)}`, yield* Ref.get(fired));
398
- }
399
- receipts = secondSubmit.value;
400
- }
401
- const lanes = cell.lanes(receipts);
402
- const driveLane = (lane) => runtime.processConversationResolved(lane).pipe(Effect.provideService(AgentBindingResolver, cell.resolver));
403
- const allSettled = Effect.gen(function* () {
404
- for (const receipt of receipts) {
405
- const snapshot = yield* ledger.lookup(SubmissionLookupById.make({ submissionId: receipt.submissionId }));
406
- if (Option.isNone(snapshot) || snapshot.value.state !== "settled") return false;
407
- }
408
- return true;
409
- });
410
- let converged = false;
411
- for (let round = 0; round < MAX_REDRIVE_ROUNDS && !converged; round++) {
412
- yield* TestClock.adjust(leaseAdvance);
413
- yield* Effect.exit(runtime.runRecovery);
414
- for (const lane of lanes) yield* Effect.exit(driveLane(lane));
415
- for (const lane of lanes) {
416
- const explains = yield* Effect.exit(runtime.explainConversation(lane));
417
- if (Exit.isFailure(explains)) continue;
418
- for (const explanation of explains.value) {
419
- for (const unknown of explanation.evidence.unknownCalls) {
420
- if (unknown.resolved) continue;
421
- yield* Effect.exit(runtime.resolveUnknown(UnknownResolutionCommand.make({
422
- submissionId: explanation.submission.submissionId,
423
- toolCallId: unknown.toolCallId,
424
- author: "certification-runner",
425
- reason: `re-drive after injected fault at ${location}`,
426
- resolution: ResolutionSafeToRetry.make()
427
- })));
428
- }
429
- for (const pending of explanation.evidence.approvalsPending) {
430
- if (explanation.evidence.approvalDecisions.some((decision) => decision.toolCallId === pending.toolCallId)) continue;
431
- yield* Effect.exit(runtime.resolveApproval(ApprovalDecisionCommand.make({
432
- submissionId: explanation.submission.submissionId,
433
- toolCallId: pending.toolCallId,
434
- decision: "approved",
435
- resolver: "certification-runner",
436
- reason: `re-drive after injected fault at ${location}`
437
- })));
438
- }
439
- }
440
- }
441
- const settled = yield* Effect.exit(allSettled);
442
- converged = Exit.isSuccess(settled) && settled.value;
443
- }
444
- yield* control.clear;
445
- const wasFired = yield* Ref.get(fired);
446
- if (!converged) return failed(`did not converge within ${MAX_REDRIVE_ROUNDS} re-drive rounds`, wasFired);
447
- let digestChainVerified = true;
448
- const failedChecks = [];
449
- for (const lane of lanes) {
450
- const verdict = yield* Effect.exit(verifyLane(lane, batchProducers));
451
- if (Exit.isFailure(verdict)) return failed(`lane ${lane} could not be verified: ${failureTagOf(verdict.cause)}`, wasFired);
452
- for (const check of verdict.value.checks) {
453
- if (check.status === "failed") failedChecks.push(`${lane}:${check.name}${check.detail === void 0 ? "" : ` (${check.detail})`}`);
454
- if (check.name === "digest-chain" && check.status !== "passed") digestChainVerified = false;
455
- }
456
- }
457
- if (failedChecks.length > 0 || !digestChainVerified) return failed(failedChecks.length > 0 ? `invariant checks failed: ${failedChecks.join("; ")}` : "the digest chain was not fully recomputed", wasFired);
458
- return CertificationSweepResult.make({
459
- scenario,
460
- location,
461
- failpointFired: wasFired,
462
- status: wasFired ? "converged" : "not-triggered",
463
- digestChainVerified
464
- });
465
- });
466
- /**
467
- * Resolve the Tier-3 record honestly (plan §1): a non-durable reference adapter has no real
468
- * loss to exercise (`not-applicable`); a supplied lever runs NOW (`exercised`); committed
469
- * real-loss citations are recorded (`recorded-evidence`); otherwise the certificate says
470
- * `not-exercised` — a scoped statement, never a silent claim.
471
- */
472
- const resolveTierThree = Effect.fn("Certification.resolveTierThree")(function* (durability, options) {
473
- if (durability === "non-durable") return CertificationTierThreeReport.make({
474
- status: "not-applicable",
475
- evidence: [],
476
- cases: [],
477
- detail: "the adapter declares non-durable state (reference/conformance adapter); there is no real loss to exercise"
478
- });
479
- if (options.crashLever !== void 0) {
480
- const cases = yield* options.crashLever;
481
- return CertificationTierThreeReport.make({
482
- status: "exercised",
483
- evidence: options.tierThreeEvidence ?? [],
484
- cases
485
- });
486
- }
487
- if (options.tierThreeEvidence !== void 0 && options.tierThreeEvidence.length > 0) return CertificationTierThreeReport.make({
488
- status: "recorded-evidence",
489
- evidence: options.tierThreeEvidence,
490
- cases: []
491
- });
492
- return CertificationTierThreeReport.make({
493
- status: "not-exercised",
494
- evidence: [],
495
- cases: [],
496
- detail: "no crash lever was supplied and no committed real-loss evidence was cited; Tier 3 is NOT discharged for this adapter"
497
- });
498
- });
499
- const nowUtc = Effect.map(Clock.currentTimeMillis, (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis)));
500
- /**
501
- * Certify one durable adapter pair (plan §1, §8 WP2). Runs Tier 2 FIRST over pristine
502
- * storage (each cell converges to all-settled before the next starts, so the recovery scan
503
- * never sees foreign leftovers), then Tier 1's port contract cases (whose lanes deliberately
504
- * end in every nonterminal shape), then records Tier 3. Requires `Crypto.Crypto` and a
505
- * TestClock-backed environment; the candidate Layers are built exactly once.
506
- */
507
- const certifyDurableAdapters = (options) => {
508
- const batchProducers = /* @__PURE__ */ new Map();
509
- const capturingStore = Layer.effect(ConversationStore)(Effect.gen(function* () {
510
- const inner = yield* ConversationStore;
511
- return ConversationStore.of({
512
- ...inner,
513
- append: (request) => Effect.sync(() => {
514
- batchProducers.set(request.batch.batchId, request.batch.producerId);
515
- }).pipe(Effect.andThen(inner.append(request)))
516
- });
517
- })).pipe(Layer.provide(options.conversationStore));
518
- const support = Layer.mergeAll(options.submissionLedger, capturingStore, options.wakeScheduler ?? WakeScheduler.layerNoop, DurableRuntimeFailpoint.layerTest, ToolReconciler.uncertain, DurableRuntimeConfig.layer({
519
- deploymentId: Schema.decodeSync(DeploymentId)("deployment-certification"),
520
- producerId: Schema.decodeSync(ProducerId)("producer-certification"),
521
- settlementPollInterval: Duration.millis(50),
522
- leaseRenewalInterval: Duration.seconds(5),
523
- abortPollInterval: Duration.millis(50)
524
- }));
525
- const environment = DurableAgentRuntime.layer.pipe(Layer.provideMerge(support));
526
- const leaseAdvance = Duration.millis(Duration.toMillis(options.ownershipLeaseDuration ?? DEFAULT_OWNERSHIP_LEASE_DURATION) + 1e3);
527
- return Effect.gen(function* () {
528
- const ledger = yield* SubmissionLedger;
529
- const tier2 = [];
530
- for (const scenario of CERTIFICATION_SCENARIOS) for (const location of DurableRuntimeFailpointLocation.literals) tier2.push(yield* runSweepCell(scenario, location, batchProducers, leaseAdvance));
531
- const tier1 = yield* certifyPorts();
532
- const capabilities = yield* ledger.capabilities;
533
- const tier3 = yield* resolveTierThree(capabilities.durability, options);
534
- const generatedAt = yield* nowUtc;
535
- const ok = tier1.every((result) => result.status === "passed") && tier2.every((result) => result.status !== "failed") && tier3.cases.every((result) => result.status === "passed");
536
- return CertificationReport.make({
537
- format: "effect-agent/certification@1",
538
- adapter: CertifiedAdapterIdentity.make({
539
- name: options.adapter.name,
540
- ...options.adapter.version === void 0 ? {} : { version: options.adapter.version },
541
- durability: capabilities.durability
542
- }),
543
- generatedAt,
544
- tier1,
545
- tier2,
546
- tier3,
547
- ok
548
- });
549
- }).pipe(Effect.provide(environment));
550
- };
551
- //#endregion
552
- //#region src/chaos.ts
553
- /**
554
- * P7 WP4 chaos machinery (plan §5): a Schema-first `ChaosPlan`, a seeded generator over
555
- * `effect/testing/FastCheck` (already inside the pinned Effect — no new dependency), and a
556
- * deterministic runner that drives the durable coordinator over whatever adapter pair the test
557
- * provides. Every plan ends in the SAME claims the crash matrices make:
558
- *
559
- * 1. `verifyConversationInvariants` in convergence mode over every touched Conversation (the
560
- * shared WP1 checker — one set of claims for admin verify, certification, chaos, and soak);
561
- * 2. `scanObligations` returning ZERO entries (everything settled; nothing invisibly stuck);
562
- * 3. supplier non-fabrication wherever the deterministic desk was in play (durability §10: no
563
- * canonical Tool success exists that the external store did not actually produce).
564
- *
565
- * Replay contract: the memory/SQLite chaos tests derive every plan from one root seed
566
- * (`CHAOS_SEED` env override; see `chaosSeedFromEnv`) and print that seed plus the failing
567
- * plan's own seed in the failure output, so any red run is replayable byte-for-byte.
568
- */
569
- /** The six durable scenario flavors a chaos lane can exercise (plan §5). */
570
- const ChaosScenarioKind = Schema.Literals([
571
- "plain",
572
- "uncertain-tool",
573
- "durable-steps",
574
- "approval",
575
- "join",
576
- "delegation"
577
- ]);
578
- const LaneIndex = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(7));
579
- /** One Submission of a plan: which lane it queues into and that lane's scenario flavor. */
580
- var ChaosSubmissionSpec = class extends Schema.Class("@effect-agent/testing/ChaosSubmissionSpec")({
581
- lane: LaneIndex,
582
- /** The lane's flavor; the FIRST spec of a lane fixes the lane's agent. */
583
- kind: ChaosScenarioKind
584
- }) {};
585
- /** How the runner resolves a durable Unknown Outcome it encounters (DUR-017 driver). */
586
- const ChaosResolutionKind = Schema.Literals([
587
- "never-happened",
588
- "completed-from-supplier",
589
- "abort-submission"
590
- ]);
591
- const ChaosApprovalDecision = Schema.Literals(["approved", "denied"]);
592
- const BoundedAdapterArm = Schema.String.check(Schema.isMaxLength(128));
593
- /**
594
- * One seeded chaos plan (plan §5): the full fault schedule is data, so a failing run replays
595
- * from the plan alone. `failpointArms` are coordinator locations; `adapterArms` are
596
- * adapter-owned location names the adapter test validates (the memory runner has none).
597
- */
598
- var ChaosPlan = class extends Schema.Class("@effect-agent/testing/ChaosPlan")({
599
- /** Identifies this plan in failure output; derived from the root seed plus the plan index. */
600
- seed: Schema.Int,
601
- /** Lane count; submissions address lanes `0..lanes-1`. */
602
- lanes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(8)),
603
- submissions: Schema.NonEmptyArray(ChaosSubmissionSpec),
604
- /** Coordinator failpoint arms, consumed one per round (each fails every hit that round). */
605
- failpointArms: Schema.Array(DurableRuntimeFailpointLocation),
606
- /** Adapter-owned failpoint arms (e.g. SQLite `ledger:*`/`append:*` locations). */
607
- adapterArms: Schema.Array(BoundedAdapterArm),
608
- /** Flattened submission indices to abort mid-plan (modulo the submission count). */
609
- abortInjections: Schema.Array(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
610
- /** Resolution choices for Unknown Outcomes, indexed deterministically per open call. */
611
- resolutionInjections: Schema.Array(ChaosResolutionKind),
612
- /** Approval decisions for suspended approval lanes, indexed deterministically per call. */
613
- approvalDecisions: Schema.Array(ChaosApprovalDecision)
614
- }) {};
615
- /** Per-lane verification result inside a plan report. */
616
- var ChaosLaneReport = class extends Schema.Class("@effect-agent/testing/ChaosLaneReport")({
617
- conversationId: ConversationId,
618
- kind: ChaosScenarioKind,
619
- submissionCount: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
620
- /** Verdict of `verifyConversationInvariants` in convergence mode. */
621
- verified: Schema.Boolean
622
- }) {};
623
- /** The Schema-first outcome of one executed chaos plan. */
624
- var ChaosPlanReport = class extends Schema.Class("@effect-agent/testing/ChaosPlanReport")({
625
- seed: Schema.Int,
626
- rounds: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
627
- lanes: Schema.Array(ChaosLaneReport),
628
- /** `scanObligations` entries after convergence — MUST be zero. */
629
- openObligations: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
630
- }) {};
631
- /** Typed convergence/verification failure of one chaos plan (never a bare defect). */
632
- var ChaosConvergenceFailure = class extends Schema.TaggedError()("ChaosConvergenceFailure", {
633
- seed: Schema.Int,
634
- message: Schema.String.check(Schema.isMaxLength(16384))
635
- }) {};
636
- /** Default root seed for chaos suites; override with the `CHAOS_SEED` environment variable. */
637
- const DEFAULT_CHAOS_SEED = 20260813;
638
- /** The root seed for this run: `CHAOS_SEED` when set to an integer, the default otherwise. */
639
- const chaosSeedFromEnv = (env) => {
640
- const raw = env["CHAOS_SEED"];
641
- if (raw === void 0 || raw === "") return DEFAULT_CHAOS_SEED;
642
- const parsed = Number.parseInt(raw, 10);
643
- return Number.isSafeInteger(parsed) ? parsed : DEFAULT_CHAOS_SEED;
644
- };
645
- const laneArbitrary = FastCheck.constantFrom("plain", "uncertain-tool", "durable-steps", "approval", "join", "delegation").chain((kind) => kind === "join" ? FastCheck.integer({
646
- min: 2,
647
- max: 3
648
- }).map((depth) => ({
649
- kind,
650
- depth
651
- })) : kind === "plain" ? FastCheck.integer({
652
- min: 1,
653
- max: 2
654
- }).map((depth) => ({
655
- kind,
656
- depth
657
- })) : FastCheck.constant({
658
- kind,
659
- depth: 1
660
- }));
661
- const planShapeArbitrary = (adapterArms) => FastCheck.record({
662
- lanes: FastCheck.array(laneArbitrary, {
663
- minLength: 1,
664
- maxLength: 3
665
- }),
666
- failpointArms: FastCheck.uniqueArray(FastCheck.constantFrom(...DurableRuntimeFailpointLocation.literals), { maxLength: 3 }),
667
- adapterArms: adapterArms.length === 0 ? FastCheck.constant([]) : FastCheck.uniqueArray(FastCheck.constantFrom(...adapterArms), { maxLength: 2 }),
668
- abortInjections: FastCheck.uniqueArray(FastCheck.integer({
669
- min: 0,
670
- max: 15
671
- }), { maxLength: 2 }),
672
- resolutionInjections: FastCheck.array(FastCheck.constantFrom("never-happened", "completed-from-supplier", "abort-submission"), { maxLength: 4 }),
673
- approvalDecisions: FastCheck.array(FastCheck.constantFrom("approved", "denied"), { maxLength: 2 })
674
- }).map((shape) => {
675
- const [first, ...rest] = shape.lanes.flatMap((lane, index) => Array.from({ length: lane.depth }, () => ChaosSubmissionSpec.make({
676
- lane: index,
677
- kind: lane.kind
678
- })));
679
- if (first === void 0) throw new Error("chaos generator produced an empty plan");
680
- return {
681
- lanes: shape.lanes.length,
682
- submissions: [first, ...rest],
683
- failpointArms: shape.failpointArms,
684
- adapterArms: shape.adapterArms,
685
- abortInjections: shape.abortInjections,
686
- resolutionInjections: shape.resolutionInjections,
687
- approvalDecisions: shape.approvalDecisions
688
- };
689
- });
690
- /**
691
- * Derive `count` chaos plans deterministically from one root seed. The same
692
- * `{seed, count, adapterArms}` triple always yields byte-identical plans, so a failure line
693
- * `CHAOS_SEED=<seed>` replays the exact schedule.
694
- */
695
- const generateChaosPlans = (options) => {
696
- return FastCheck.sample(planShapeArbitrary(options.adapterArms ?? []), {
697
- seed: options.seed,
698
- numRuns: options.count
699
- }).map((shape, index) => ChaosPlan.make({
700
- ...shape,
701
- seed: Math.imul(options.seed, 31) + index | 0
702
- }));
703
- };
704
- /** Deterministic PRNG for the runner's small ordering choices (lane drive order). */
705
- const mulberry32 = (seed) => {
706
- let state = seed | 0;
707
- return () => {
708
- state = state + 1831565813 | 0;
709
- let t = Math.imul(state ^ state >>> 15, 1 | state);
710
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
711
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
712
- };
713
- };
714
- const usage$1 = {
715
- inputTokens: {},
716
- outputTokens: {}
717
- };
718
- const finalParts = (text) => [
719
- {
720
- type: "text-start",
721
- id: "answer"
722
- },
723
- {
724
- type: "text-delta",
725
- id: "answer",
726
- delta: text
727
- },
728
- {
729
- type: "text-end",
730
- id: "answer"
731
- },
732
- {
733
- type: "finish",
734
- reason: "stop",
735
- usage: usage$1
736
- }
737
- ];
738
- const toolTurn = (...calls) => [...calls, {
739
- type: "finish",
740
- reason: "tool-calls",
741
- usage: usage$1
742
- }];
743
- const toolCallPart = (id, name, params) => ({
744
- type: "tool-call",
745
- id,
746
- name,
747
- params,
748
- providerExecuted: false
749
- });
750
- /**
751
- * Prompt-shaped scripted model: the response depends ONLY on the request prompt, so it stays
752
- * deterministic across Attempt re-invocations, batch resumes, and joined steering — no counter
753
- * to drift when chaos re-enters a Turn.
754
- */
755
- const promptScriptedModel = (label, script) => Model.make("scripted", label, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
756
- generateText: () => Effect.succeed([]),
757
- streamText: (request) => Stream.fromIterable(script(request.prompt))
758
- })));
759
- const lastRole = (prompt) => prompt.content.at(-1)?.role;
760
- const policy = AgentPolicy.make({
761
- maxTurns: 3,
762
- maxToolCalls: 4,
763
- maxDuration: "30 seconds",
764
- toolConcurrency: 2
765
- });
766
- const PlainInput = Schema.Struct({ question: Schema.String });
767
- const PlainOutput = Schema.Struct({ answer: Schema.String });
768
- const plainDefinition = Agent.define("chaos-plain", {
769
- input: PlainInput,
770
- output: PlainOutput,
771
- instructions: "Answer as JSON.",
772
- toolkit: Toolkit.empty,
773
- policy
774
- });
775
- /** Unannotated → fail-closed `uncertain`: enters the prepared/settled protocol (DUR-009). */
776
- const BookUncertain = Tool.make("book", {
777
- parameters: Schema.Struct({ ref: Schema.String }),
778
- success: Schema.Struct({ confirmation: Schema.String })
779
- });
780
- const bookTools = Toolkit.make(BookUncertain);
781
- const bookDefinition = Agent.define("chaos-book", {
782
- input: PlainInput,
783
- output: PlainOutput,
784
- instructions: "Book it.",
785
- toolkit: bookTools,
786
- policy
787
- });
788
- const BookApproval = Tool.make("book", {
789
- parameters: Schema.Struct({ ref: Schema.String }),
790
- success: Schema.Struct({ confirmation: Schema.String }),
791
- needsApproval: true
792
- });
793
- const approvalTools = Toolkit.make(BookApproval);
794
- const approvalDefinition = Agent.define("chaos-approval", {
795
- input: PlainInput,
796
- output: PlainOutput,
797
- instructions: "Book after approval.",
798
- toolkit: approvalTools,
799
- policy
800
- });
801
- const Itinerary$1 = Tool.make("itinerary", {
802
- parameters: Schema.Struct({ ref: Schema.String }),
803
- success: Schema.Struct({ state: Schema.String }),
804
- failure: DurableStepError,
805
- dependencies: [DurableStep]
806
- }).annotate(ToolExecutionClass, "uncertain");
807
- const itineraryTools = Toolkit.make(Itinerary$1);
808
- const itineraryDefinition = Agent.define("chaos-itinerary", {
809
- input: PlainInput,
810
- output: PlainOutput,
811
- instructions: "Reserve the itinerary.",
812
- toolkit: itineraryTools,
813
- policy
814
- });
815
- const childDefinition = Agent.define("chaos-child", {
816
- input: PlainInput,
817
- output: PlainOutput,
818
- instructions: "Answer as JSON.",
819
- toolkit: Toolkit.empty,
820
- policy: AgentPolicy.make({
821
- maxTurns: 2,
822
- maxToolCalls: 1,
823
- maxDuration: "30 seconds",
824
- toolConcurrency: 1
825
- })
826
- });
827
- var ChaosDelegationFailed = class extends Schema.TaggedError()("ChaosDelegationFailed", { childErrorTag: Schema.String }) {};
828
- const chaosDelegation = Subagent.define("delegate_chaos", {
829
- description: "Delegate one bounded chaos question.",
830
- target: childDefinition,
831
- parameters: Schema.Struct({ topic: Schema.String }),
832
- success: Schema.Struct({ summary: Schema.String }),
833
- failure: ChaosDelegationFailed,
834
- prepareInput: ({ topic }) => Effect.succeed({ question: `chaos:${topic}` }),
835
- projectResult: (output) => Effect.succeed({ summary: `finding:${output.answer}` }),
836
- policy: SubagentPolicy.make({
837
- maxChildren: 2,
838
- maxConcurrency: 2,
839
- maxTurns: 4,
840
- maxToolCalls: 4,
841
- maxDuration: "30 seconds"
842
- })
843
- });
844
- const coordinatorDefinition = Agent.define("chaos-coordinator", {
845
- input: Schema.Struct({ mission: Schema.String }),
846
- output: Schema.Struct({ report: Schema.String }),
847
- instructions: "Delegate, then report as JSON.",
848
- toolkit: Toolkit.make(chaosDelegation.tool),
849
- policy
850
- });
851
- const DELEGATE_CALL_ID = "chaos-delegate-1";
852
- const HEX = "0123456789abcdef";
853
- const decodeDigest = Schema.decodeSync(Digest);
854
- const laneDigests = (lane) => {
855
- const digest = decodeDigest(HEX[lane % 8].repeat(64));
856
- return DefinitionDigests.make({
857
- agent: digest,
858
- model: digest,
859
- tools: digest
860
- });
861
- };
862
- const childDigestStrings = (lane) => {
863
- const char = HEX[8 + lane % 8];
864
- return {
865
- agent: char.repeat(64),
866
- model: char.repeat(64),
867
- tools: char.repeat(64)
868
- };
869
- };
870
- const childLaneDigests = (lane) => {
871
- const strings = childDigestStrings(lane);
872
- return DefinitionDigests.make({
873
- agent: decodeDigest(strings.agent),
874
- model: decodeDigest(strings.model),
875
- tools: decodeDigest(strings.tools)
876
- });
877
- };
878
- const CHAOS_PRINCIPAL = Schema.decodeSync(Principal)("principal-chaos");
879
- const decodeConversationId = Schema.decodeSync(ConversationId);
880
- const decodeIdempotencyKey = Schema.decodeSync(IdempotencyKey);
881
- const decodeToolCallId = Schema.decodeSync(ToolCallId);
882
- const decodeRunId = Schema.decodeSync(RunId);
883
- const decodeTurnId = Schema.decodeSync(TurnId);
884
- /** Fixture-only identity source consumed by the delegation Layer's ephemeral capture. */
885
- const chaosIdentifiers = Layer.effect(IdGenerator, Effect.gen(function* () {
886
- const counter = yield* Ref.make(0);
887
- const next = (decode, prefix) => Ref.getAndUpdate(counter, (value) => value + 1).pipe(Effect.map((value) => decode(`${prefix}-${value}`)));
888
- return {
889
- nextConversationId: next(decodeConversationId, "chaos-fixture-conversation"),
890
- nextRunId: next(decodeRunId, "chaos-fixture-run"),
891
- nextTurnId: next(decodeTurnId, "chaos-fixture-turn")
892
- };
893
- }));
894
- const delegationSupport = Layer.mergeAll(SubagentReservationsMemoryLive, chaosIdentifiers);
895
- const makeChaosDesk = Effect.gen(function* () {
896
- const produced = yield* Ref.make(/* @__PURE__ */ new Set());
897
- return {
898
- produced: Ref.get(produced),
899
- record: (value) => Ref.update(produced, (current) => new Set(current).add(value))
900
- };
901
- });
902
- const bookConfirmation = (ref) => `confirmed-${ref}`;
903
- const flightValue = (ref) => `flight-${ref}`;
904
- const lodgingValue = (ref) => `lodging-${ref}`;
905
- /** Success → Some; typed failure → None (chaos tolerates it); defect → rethrown loudly. */
906
- const tolerateTyped = (effect) => effect.pipe(Effect.exit, Effect.flatMap((exit) => {
907
- if (Exit.isSuccess(exit)) return Effect.succeed(Option.some(exit.value));
908
- if (Option.isSome(Cause.findErrorOption(exit.cause))) return Effect.succeed(Option.none());
909
- return Effect.die(/* @__PURE__ */ new Error(`chaos step died: ${Cause.pretty(exit.cause)}`));
910
- }));
911
- const scriptFor = (kind, ref) => {
912
- switch (kind) {
913
- case "plain":
914
- case "join": return () => finalParts("{\"answer\":\"chaos\"}");
915
- case "uncertain-tool":
916
- case "approval": return (prompt) => lastRole(prompt) === "tool" ? finalParts("{\"answer\":\"booked\"}") : toolTurn(toolCallPart(`book-${ref}`, "book", { ref }));
917
- case "durable-steps": return (prompt) => lastRole(prompt) === "tool" ? finalParts("{\"answer\":\"reserved\"}") : toolTurn(toolCallPart(`itinerary-${ref}`, "itinerary", { ref }));
918
- case "delegation": return (prompt) => lastRole(prompt) === "tool" ? finalParts("{\"report\":\"done\"}") : toolTurn(toolCallPart(DELEGATE_CALL_ID, "delegate_chaos", { topic: ref }));
919
- }
920
- };
921
- const makeLaneFixture = Effect.fn("Chaos.makeLaneFixture")(function* (plan, laneIndex, kind, submissionIndexes, desk) {
922
- const runtime = yield* DurableAgentRuntime;
923
- const conversationId = decodeConversationId(`chaos-${plan.seed}-lane-${laneIndex}`);
924
- const ref = `ref-l${laneIndex}`;
925
- const script = scriptFor(kind, ref);
926
- const model = promptScriptedModel(`chaos-${kind}-${laneIndex}`, script);
927
- const digests = laneDigests(laneIndex);
928
- const submitOptionsFor = (flatIndex) => ({
929
- conversationId,
930
- principal: CHAOS_PRINCIPAL,
931
- idempotencyKey: decodeIdempotencyKey(`chaos-${plan.seed}-s${flatIndex}`),
932
- definitions: digests
933
- });
934
- const bookToolLayerFor = (tools) => tools.toLayer({ book: ({ ref: called }) => desk.record(bookConfirmation(called)).pipe(Effect.as({ confirmation: bookConfirmation(called) })) });
935
- const plainLaneFixture = (deskInPlay, drive, submitOne) => ({
936
- index: laneIndex,
937
- kind,
938
- conversationId,
939
- ref,
940
- deskInPlay,
941
- submissionIndexes,
942
- submitOne,
943
- drives: () => [drive],
944
- childConversationOf: () => void 0
945
- });
946
- switch (kind) {
947
- case "plain":
948
- case "join": {
949
- const agent = Agent.withModel(plainDefinition, model);
950
- return plainLaneFixture(false, runtime.processConversation(agent, conversationId), (flatIndex) => runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)));
951
- }
952
- case "uncertain-tool": {
953
- const agent = Agent.withModel(bookDefinition, model);
954
- return plainLaneFixture(true, runtime.processConversation(agent, conversationId).pipe(Effect.provide(bookToolLayerFor(bookTools))), (flatIndex) => runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)));
955
- }
956
- case "approval": {
957
- const agent = Agent.withModel(approvalDefinition, model);
958
- return plainLaneFixture(true, runtime.processConversation(agent, conversationId).pipe(Effect.provide(bookToolLayerFor(approvalTools))), (flatIndex) => runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)));
959
- }
960
- case "durable-steps": {
961
- const agent = Agent.withModel(itineraryDefinition, model);
962
- const toolLayer = itineraryTools.toLayer({ itinerary: ({ ref: called }) => Effect.gen(function* () {
963
- const step = yield* DurableStep;
964
- return { state: `${yield* step.do("reserve-flight", Schema.String, desk.record(flightValue(called)).pipe(Effect.as(flightValue(called))))}+${yield* step.do("reserve-lodging", Schema.String, desk.record(lodgingValue(called)).pipe(Effect.as(lodgingValue(called))))}` };
965
- }) });
966
- return plainLaneFixture(true, runtime.processConversation(agent, conversationId).pipe(Effect.provide(toolLayer)), (flatIndex) => runtime.submit(agent, { question: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)));
967
- }
968
- case "delegation": {
969
- const parentBinding = Agent.withModel(coordinatorDefinition, model);
970
- const childModel = promptScriptedModel(`chaos-child-${laneIndex}`, () => finalParts("{\"answer\":\"child\"}"));
971
- const childBinding = Agent.withModel(childDefinition, childModel);
972
- const delegationLayer = SubagentRuntime.layer(chaosDelegation, childBinding, {
973
- mapChildFailure: (failure) => ChaosDelegationFailed.make({ childErrorTag: failure._tag }),
974
- durable: { targetDigests: childDigestStrings(laneIndex) }
975
- }).pipe(Layer.provide(delegationSupport));
976
- const parentResolved = yield* DurableWorkerBinding.make(parentBinding, digests).pipe(Effect.provide(delegationLayer));
977
- const childResolved = yield* DurableWorkerBinding.make(childBinding, childLaneDigests(laneIndex));
978
- const resolver = AgentBindingResolver.fromBindings([parentResolved, childResolved]);
979
- const driveResolved = (conversation) => runtime.processConversationResolved(conversation).pipe(Effect.provideService(AgentBindingResolver, resolver));
980
- return {
981
- index: laneIndex,
982
- kind,
983
- conversationId,
984
- ref,
985
- deskInPlay: false,
986
- submissionIndexes,
987
- submitOne: (flatIndex) => runtime.submit({ definition: {
988
- id: coordinatorDefinition.id,
989
- input: coordinatorDefinition.input
990
- } }, { mission: `chaos ${flatIndex}` }, submitOptionsFor(flatIndex)),
991
- drives: (firstReceipt) => {
992
- const drives = [driveResolved(conversationId)];
993
- if (firstReceipt !== void 0) drives.push(driveResolved(childConversationIdFor(firstReceipt.submissionId, decodeToolCallId(DELEGATE_CALL_ID))));
994
- return drives;
995
- },
996
- childConversationOf: (firstReceipt) => childConversationIdFor(firstReceipt.submissionId, decodeToolCallId(DELEGATE_CALL_ID))
997
- };
998
- }
999
- }
1000
- });
1001
- /** Stable per-call index into an injection list (identical across resolution passes). */
1002
- const injectionIndex = (submissionFlatIndex, callId, length) => {
1003
- let hash = submissionFlatIndex + 1;
1004
- for (const char of callId) hash = Math.imul(hash, 31) + char.charCodeAt(0) | 0;
1005
- return (hash % length + length) % length;
1006
- };
1007
- const resolutionFor = (kind, toolName, ref, produced) => {
1008
- switch (kind) {
1009
- case "abort-submission": return ResolutionAbortSubmission.make();
1010
- case "completed-from-supplier":
1011
- if (toolName === "book" && produced.has(bookConfirmation(ref))) return ResolutionCompletedWithResult.make({
1012
- result: { confirmation: bookConfirmation(ref) },
1013
- isFailure: false
1014
- });
1015
- if (toolName === "itinerary" && produced.has(flightValue(ref)) && produced.has(lodgingValue(ref))) return ResolutionCompletedWithResult.make({
1016
- result: { state: `${flightValue(ref)}+${lodgingValue(ref)}` },
1017
- isFailure: false
1018
- });
1019
- return ResolutionNeverHappened.make();
1020
- case "never-happened": return ResolutionNeverHappened.make();
1021
- }
1022
- };
1023
- /** Drive one DUR-017 pass: resolve Unknown Outcomes and pending approvals from the plan. */
1024
- const resolutionPass = Effect.fn("Chaos.resolutionPass")(function* (plan, states, desk) {
1025
- const runtime = yield* DurableAgentRuntime;
1026
- const ledger = yield* SubmissionLedger;
1027
- const produced = yield* desk.produced;
1028
- const nonterminal = yield* tolerateTyped(Stream.runCollect(ledger.scanNonterminal));
1029
- if (Option.isNone(nonterminal)) return;
1030
- const byId = /* @__PURE__ */ new Map();
1031
- for (const state of states) if (state.receipt !== void 0) byId.set(state.receipt.submissionId, state);
1032
- for (const row of nonterminal.value) {
1033
- if (row.state !== "unknown" && row.state !== "suspended") continue;
1034
- const state = byId.get(row.submissionId);
1035
- const explanation = yield* tolerateTyped(runtime.explain(row.submissionId));
1036
- if (Option.isNone(explanation)) continue;
1037
- const flatIndex = state?.flatIndex ?? 0;
1038
- const ref = state?.lane.ref ?? "ref-child";
1039
- if (row.state === "unknown") for (const call of explanation.value.evidence.unknownCalls) {
1040
- if (call.resolved) continue;
1041
- const kind = plan.resolutionInjections.length === 0 ? "never-happened" : plan.resolutionInjections[injectionIndex(flatIndex, call.toolCallId, plan.resolutionInjections.length)];
1042
- yield* tolerateTyped(runtime.resolveUnknown(UnknownResolutionCommand.make({
1043
- submissionId: row.submissionId,
1044
- toolCallId: call.toolCallId,
1045
- author: "chaos-runner",
1046
- reason: `chaos plan ${plan.seed} resolution (${kind})`,
1047
- resolution: resolutionFor(kind, call.toolName, ref, produced)
1048
- })));
1049
- }
1050
- else for (const pending of explanation.value.evidence.approvalsPending) {
1051
- const decision = plan.approvalDecisions.length === 0 ? "approved" : plan.approvalDecisions[injectionIndex(flatIndex, pending.toolCallId, plan.approvalDecisions.length)];
1052
- yield* tolerateTyped(runtime.resolveApproval(ApprovalDecisionCommand.make({
1053
- submissionId: row.submissionId,
1054
- toolCallId: pending.toolCallId,
1055
- decision,
1056
- resolver: "chaos-runner",
1057
- reason: `chaos plan ${plan.seed} approval (${decision})`
1058
- })));
1059
- }
1060
- }
1061
- });
1062
- const submissionIdsNamedBy = (records) => {
1063
- const named = /* @__PURE__ */ new Set();
1064
- for (const envelope of records) {
1065
- const payload = envelope.record.payload;
1066
- if (payload._tag === "UserInputRecorded" || payload._tag === "SubmissionSettled" || payload._tag === "AbortRequested") named.add(payload.submissionId);
1067
- }
1068
- return named;
1069
- };
1070
- /**
1071
- * The final non-fabrication sweep (durability §10): every canonical Tool success recorded on a
1072
- * desk-backed lane must be a value the desk actually produced.
1073
- */
1074
- const BookResult = Schema.Struct({ confirmation: Schema.String });
1075
- const ItineraryResult = Schema.Struct({ state: Schema.String });
1076
- const decodeBookResult = Schema.decodeUnknownOption(BookResult);
1077
- const decodeItineraryResult = Schema.decodeUnknownOption(ItineraryResult);
1078
- const decodeStepOutput = Schema.decodeUnknownOption(Schema.String);
1079
- const assertNoFabrication = (plan, records, produced) => {
1080
- const fabricated = [];
1081
- const requireProduced = (value, label) => {
1082
- if (!produced.has(value)) fabricated.push(`${label} "${value}"`);
1083
- };
1084
- for (const envelope of records) {
1085
- const payload = envelope.record.payload;
1086
- if (payload._tag === "ToolCallSettled" && !payload.isFailure) {
1087
- if (payload.toolName === "book") {
1088
- const result = decodeBookResult(payload.result);
1089
- if (Option.isSome(result)) requireProduced(result.value.confirmation, "book result");
1090
- }
1091
- if (payload.toolName === "itinerary") {
1092
- const result = decodeItineraryResult(payload.result);
1093
- if (Option.isSome(result)) for (const part of result.value.state.split("+")) requireProduced(part, "itinerary step result");
1094
- }
1095
- }
1096
- if (payload._tag === "ToolStepSettled") {
1097
- const output = decodeStepOutput(payload.output);
1098
- if (Option.isSome(output)) requireProduced(output.value, "step output");
1099
- }
1100
- }
1101
- return fabricated.length === 0 ? Effect.void : Effect.fail(ChaosConvergenceFailure.make({
1102
- seed: plan.seed,
1103
- message: `fabricated Tool results absent from the desk: ${fabricated.join(", ")}`
1104
- }));
1105
- };
1106
- /**
1107
- * Execute one chaos plan against whatever adapters the ambient Layer provides and end in the
1108
- * shared invariant claims. Deterministic: same plan + same adapters → same schedule.
1109
- */
1110
- const runChaosPlan = Effect.fn("Chaos.runChaosPlan")(function* (plan, options) {
1111
- const runtime = yield* DurableAgentRuntime;
1112
- const ledger = yield* SubmissionLedger;
1113
- const store = yield* ConversationStore;
1114
- const config = yield* DurableRuntimeConfig;
1115
- const failpoints = yield* DurableRuntimeFailpointTestControl;
1116
- const random = mulberry32(plan.seed);
1117
- const desk = yield* makeChaosDesk;
1118
- const laneKinds = /* @__PURE__ */ new Map();
1119
- const laneSubmissions = /* @__PURE__ */ new Map();
1120
- plan.submissions.forEach((spec, flatIndex) => {
1121
- const lane = spec.lane % plan.lanes;
1122
- if (!laneKinds.has(lane)) laneKinds.set(lane, spec.kind);
1123
- const list = laneSubmissions.get(lane) ?? [];
1124
- list.push(flatIndex);
1125
- laneSubmissions.set(lane, list);
1126
- });
1127
- const lanes = [];
1128
- for (const [lane, kind] of laneKinds) lanes.push(yield* makeLaneFixture(plan, lane, kind, laneSubmissions.get(lane) ?? [], desk));
1129
- const states = plan.submissions.map((spec, flatIndex) => ({
1130
- flatIndex,
1131
- lane: lanes.find((fixture) => fixture.index === spec.lane % plan.lanes),
1132
- receipt: void 0
1133
- }));
1134
- const appliedAborts = /* @__PURE__ */ new Set();
1135
- const armQueue = [...plan.failpointArms.map((location) => ({
1136
- family: "coordinator",
1137
- location
1138
- })), ...plan.adapterArms.map((location) => ({
1139
- family: "adapter",
1140
- location
1141
- }))];
1142
- const allSettled = Effect.gen(function* () {
1143
- if (states.some((state) => state.receipt === void 0)) return false;
1144
- const nonterminal = yield* tolerateTyped(Stream.runCollect(ledger.scanNonterminal));
1145
- return Option.isSome(nonterminal) && Array.from(nonterminal.value).length === 0;
1146
- });
1147
- const maxRounds = armQueue.length + states.length * 2 + 12;
1148
- let rounds = 0;
1149
- let converged = false;
1150
- for (let round = 0; round < maxRounds; round++) {
1151
- rounds = round + 1;
1152
- const arm = armQueue[round];
1153
- if (arm?.family === "coordinator") {
1154
- const location = arm.location;
1155
- yield* failpoints.setHandler((hit) => hit === location ? Effect.fail(DurableRuntimeFailpointError.make({ location: hit })) : Effect.void);
1156
- } else if (arm?.family === "adapter" && options?.adapterFailpoints !== void 0) yield* options.adapterFailpoints.arm(arm.location);
1157
- for (const state of states) {
1158
- if (state.receipt !== void 0) continue;
1159
- const receipt = yield* tolerateTyped(state.lane.submitOne(state.flatIndex));
1160
- if (Option.isSome(receipt)) state.receipt = receipt.value;
1161
- }
1162
- const order = [...lanes].sort(() => random() - .5);
1163
- for (const lane of order) {
1164
- const firstFlat = lane.submissionIndexes[0];
1165
- const firstReceipt = firstFlat === void 0 ? void 0 : states[firstFlat]?.receipt;
1166
- for (const drive of lane.drives(firstReceipt)) yield* tolerateTyped(drive);
1167
- }
1168
- if (round >= 1) for (const rawIndex of plan.abortInjections) {
1169
- const index = rawIndex % states.length;
1170
- if (appliedAborts.has(index)) continue;
1171
- const receipt = states[index]?.receipt;
1172
- if (receipt === void 0) continue;
1173
- appliedAborts.add(index);
1174
- yield* tolerateTyped(runtime.abort(AbortCommand.make({
1175
- submissionId: receipt.submissionId,
1176
- author: "chaos-runner",
1177
- reason: `chaos plan ${plan.seed} abort injection`
1178
- })));
1179
- }
1180
- yield* resolutionPass(plan, states, desk);
1181
- yield* failpoints.clear;
1182
- if (options?.adapterFailpoints !== void 0) yield* options.adapterFailpoints.clear;
1183
- yield* tolerateTyped(runtime.runRecovery);
1184
- yield* resolutionPass(plan, states, desk);
1185
- if (yield* allSettled) {
1186
- converged = true;
1187
- break;
1188
- }
1189
- if (options?.betweenRounds !== void 0) yield* options.betweenRounds;
1190
- }
1191
- if (!converged) {
1192
- const nonterminal = yield* tolerateTyped(Stream.runCollect(ledger.scanNonterminal));
1193
- const detail = Option.isSome(nonterminal) ? Array.from(nonterminal.value).map((row) => `${row.submissionId}(${row.state})`).join(", ") : "ledger scan failed";
1194
- return yield* ChaosConvergenceFailure.make({
1195
- seed: plan.seed,
1196
- message: `plan did not converge within ${maxRounds} rounds; nonterminal: [${detail}]; pending receipts: ${states.filter((state) => state.receipt === void 0).length}`
1197
- });
1198
- }
1199
- const produced = yield* desk.produced;
1200
- const laneReports = [];
1201
- const verifyConversation = Effect.fn("Chaos.verifyConversation")(function* (conversationId, kind, deskInPlay) {
1202
- const exported = yield* store.export(ConversationExportRequest.make({ conversationId })).pipe(Effect.mapError((error) => ChaosConvergenceFailure.make({
1203
- seed: plan.seed,
1204
- message: `export of ${conversationId} failed: ${String(error)}`
1205
- })));
1206
- const rows = [];
1207
- for (const submissionId of submissionIdsNamedBy(exported.records)) {
1208
- const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId })).pipe(Effect.mapError((error) => ChaosConvergenceFailure.make({
1209
- seed: plan.seed,
1210
- message: `lookup of ${submissionId} failed: ${String(error)}`
1211
- })));
1212
- if (Option.isSome(found)) rows.push(found.value);
1213
- }
1214
- const report = yield* verifyConversationInvariants({
1215
- export: exported,
1216
- submissions: rows,
1217
- batchProducers: new Map(exported.records.map((envelope) => [envelope.batchId, config.producerId])),
1218
- requireAllSettled: true
1219
- });
1220
- if (!report.ok) {
1221
- const failed = report.checks.filter((check) => check.status === "failed").map((check) => `${check.name}: ${check.detail ?? "failed"}`).join("; ");
1222
- return yield* ChaosConvergenceFailure.make({
1223
- seed: plan.seed,
1224
- message: `invariants failed for ${conversationId} (${kind}): ${failed}`
1225
- });
1226
- }
1227
- if (deskInPlay) yield* assertNoFabrication(plan, exported.records, produced);
1228
- laneReports.push(ChaosLaneReport.make({
1229
- conversationId,
1230
- kind,
1231
- submissionCount: rows.length,
1232
- verified: report.ok
1233
- }));
1234
- });
1235
- for (const lane of lanes) {
1236
- yield* verifyConversation(lane.conversationId, lane.kind, lane.deskInPlay);
1237
- for (const flatIndex of lane.submissionIndexes) {
1238
- const receipt = states[flatIndex]?.receipt;
1239
- if (receipt === void 0) continue;
1240
- const child = lane.childConversationOf(receipt);
1241
- if (child === void 0) continue;
1242
- const childExport = yield* Effect.exit(store.export(ConversationExportRequest.make({ conversationId: child })));
1243
- if (Exit.isSuccess(childExport) && childExport.value.records.length > 0) yield* verifyConversation(child, "plain", false);
1244
- }
1245
- }
1246
- const obligations = yield* runtime.scanObligations(ObligationThresholds.make({
1247
- agingSeconds: 0,
1248
- overdueSeconds: 0
1249
- })).pipe(Effect.mapError((error) => ChaosConvergenceFailure.make({
1250
- seed: plan.seed,
1251
- message: `scanObligations failed: ${String(error)}`
1252
- })));
1253
- if (obligations.entries.length > 0) return yield* ChaosConvergenceFailure.make({
1254
- seed: plan.seed,
1255
- message: `open obligations after convergence: ${obligations.entries.map((entry) => `${entry.submissionId}(${entry.blockedOn})`).join(", ")}`
1256
- });
1257
- return ChaosPlanReport.make({
1258
- seed: plan.seed,
1259
- rounds,
1260
- lanes: laneReports,
1261
- openObligations: obligations.entries.length
1262
- });
1263
- });
1264
- //#endregion
1265
- //#region src/code-executor-conformance.ts
1266
- /**
1267
- * Shared `CodeExecutor` conformance (TEST-015). Every adapter — the
1268
- * deterministic `unisolated` substitute and each isolated adapter — runs
1269
- * `codeExecutorConformanceCases` verbatim. Enforcement cases that only genuine
1270
- * isolation can prove (ambient network denial, synchronous CPU runaway
1271
- * termination) are NOT here; they belong to isolated adapters only
1272
- * (testing spec §8.1).
1273
- *
1274
- * Cases assume the live `Clock` (the wall-clock case uses a short real
1275
- * deadline) and take one fresh executor pass per case, so a suite may share
1276
- * one executor Layer across cases.
1277
- */
1278
- var CodeExecutorConformanceViolation = class extends Schema.TaggedError()("CodeExecutorConformanceViolation", {
1279
- caseName: Schema.String,
1280
- message: Schema.String
1281
- }) {};
1282
- const baseLimits = CodeExecutionLimits.make({
1283
- maxSourceBytes: 64 * 1024,
1284
- maxWallTime: Duration.seconds(10),
1285
- maxLogBytes: 16 * 1024,
1286
- maxResultBytes: 64 * 1024,
1287
- maxHostCalls: 8,
1288
- maxHostCallArgumentBytes: 16 * 1024,
1289
- maxHostCallResultBytes: 32 * 1024
1290
- });
1291
- const warehouseNamespace = CodeExecutionNamespace.make({
1292
- name: "warehouse",
1293
- methods: ["query", "count"]
1294
- });
1295
- const makeRequest = (source, overrides) => CodeExecutionRequest.make({
1296
- language: "javascript",
1297
- source,
1298
- namespaces: overrides?.namespaces ?? [],
1299
- network: overrides?.network ?? NetworkDisabled.make(),
1300
- limits: overrides?.limits ?? baseLimits
1301
- });
1302
- const unusedHost = { call: () => Effect.die(/* @__PURE__ */ new Error("this conformance case expected no host call to reach the CodeExecutionHost")) };
1303
- const respondingHost = (respond) => {
1304
- const calls = [];
1305
- return {
1306
- calls,
1307
- host: { call: (call) => Effect.sync(() => {
1308
- calls.push(call);
1309
- return respond(call);
1310
- }) }
1311
- };
1312
- };
1313
- const runPass = (request, host) => Effect.gen(function* () {
1314
- return yield* (yield* CodeExecutor).execute(request).pipe(Effect.provideService(CodeExecutionHost, CodeExecutionHost.of(host)));
1315
- }).pipe(Effect.scoped);
1316
- const violation = (caseName, message) => CodeExecutorConformanceViolation.make({
1317
- caseName,
1318
- message
1319
- });
1320
- const preview = (value) => {
1321
- try {
1322
- return JSON.stringify(value)?.slice(0, 200) ?? String(value).slice(0, 200);
1323
- } catch {
1324
- return String(value).slice(0, 200);
1325
- }
1326
- };
1327
- const expectSuccess = (caseName, request, host, check) => runPass(request, host).pipe(Effect.mapError((error) => violation(caseName, `expected success, got ${error._tag}: ${preview(error)}`)), Effect.flatMap((result) => {
1328
- const complaint = check(result);
1329
- return complaint === void 0 ? Effect.void : Effect.fail(violation(caseName, complaint));
1330
- }));
1331
- const expectFailure = (caseName, request, host, tag, check) => runPass(request, host).pipe(Effect.flip, Effect.mapError((result) => violation(caseName, `expected ${tag}, but the pass succeeded with ${preview(result.value)}`)), Effect.flatMap((error) => {
1332
- if (error._tag !== tag) return Effect.fail(violation(caseName, `expected ${tag}, got ${error._tag}: ${preview(error)}`));
1333
- const complaint = check?.(error);
1334
- return complaint === void 0 ? Effect.void : Effect.fail(violation(caseName, complaint));
1335
- }));
1336
- const codeExecutorConformanceCases = (options) => {
1337
- const posture = options.implementation;
1338
- return [
1339
- {
1340
- name: "TEST-015 executes bounded JSON computation and returns the program value",
1341
- run: expectSuccess("TEST-015 executes bounded JSON computation and returns the program value", makeRequest("async () => { const xs = [1, 2, 3].map((n) => n * 2); return { xs, sum: xs.reduce((a, b) => a + b, 0) }; }"), unusedHost, (result) => JSON.stringify(result.value) === JSON.stringify({
1342
- xs: [
1343
- 2,
1344
- 4,
1345
- 6
1346
- ],
1347
- sum: 12
1348
- }) ? void 0 : `unexpected program value ${preview(result.value)}`)
1349
- },
1350
- {
1351
- name: "CAP-015 reports its isolation posture honestly in results and errors",
1352
- run: Effect.gen(function* () {
1353
- const caseName = "CAP-015 reports its isolation posture honestly in results and errors";
1354
- const result = yield* runPass(makeRequest("async () => 1"), unusedHost).pipe(Effect.mapError((error) => violation(caseName, `expected success, got ${error._tag}`)));
1355
- if (result.implementation.isolation !== posture.isolation || result.implementation.identity !== posture.identity) return yield* violation(caseName, `result posture ${preview(result.implementation)} does not match the declared ${preview(posture)}`);
1356
- const error = yield* runPass(makeRequest("async () => {"), unusedHost).pipe(Effect.flip, Effect.mapError(() => violation(caseName, "expected the invalid-source pass to fail")));
1357
- if (error.implementation === void 0 || error.implementation.isolation !== posture.isolation || error.implementation.identity !== posture.identity) return yield* violation(caseName, `error posture ${preview(error.implementation)} does not match the declared ${preview(posture)}`);
1358
- })
1359
- },
1360
- {
1361
- name: "TEST-015 routes host calls through the CodeExecutionHost in program order",
1362
- run: Effect.gen(function* () {
1363
- const caseName = "TEST-015 routes host calls through the CodeExecutionHost in program order";
1364
- const { host, calls } = respondingHost((call) => call.method === "query" ? CodeHostCallSuccess.make({ value: { rows: [
1365
- 1,
1366
- 2,
1367
- 3
1368
- ] } }) : CodeHostCallSuccess.make({ value: 3 }));
1369
- const result = yield* runPass(makeRequest("async () => { const q = await warehouse.query({ sql: 'select' }); const c = await warehouse.count({ table: 't' }); return { rows: q.rows, count: c }; }", { namespaces: [warehouseNamespace] }), host).pipe(Effect.mapError((error) => violation(caseName, `expected success, got ${error._tag}: ${preview(error)}`)));
1370
- if (JSON.stringify(result.value) !== JSON.stringify({
1371
- rows: [
1372
- 1,
1373
- 2,
1374
- 3
1375
- ],
1376
- count: 3
1377
- })) return yield* violation(caseName, `unexpected value ${preview(result.value)}`);
1378
- const observed = calls.map((call) => `${call.namespace}.${call.method}`);
1379
- if (JSON.stringify(observed) !== JSON.stringify(["warehouse.query", "warehouse.count"])) return yield* violation(caseName, `unexpected host call order ${preview(observed)}`);
1380
- if (result.resourceUse.hostCalls !== 2) return yield* violation(caseName, `expected 2 accounted host calls, got ${result.resourceUse.hostCalls}`);
1381
- })
1382
- },
1383
- {
1384
- name: "TEST-015 a caught failed host call lets the program branch on the envelope",
1385
- run: expectSuccess("TEST-015 a caught failed host call lets the program branch on the envelope", makeRequest("async () => { try { await warehouse.query({ sql: 'x' }); return 'unreachable'; } catch (envelope) { return { caught: envelope }; } }", { namespaces: [warehouseNamespace] }), respondingHost(() => CodeHostCallFailure.make({ error: {
1386
- _tag: "ToolInputError",
1387
- message: "bad input"
1388
- } })).host, (result) => JSON.stringify(result.value) === JSON.stringify({ caught: {
1389
- _tag: "ToolInputError",
1390
- message: "bad input"
1391
- } }) ? void 0 : `the envelope did not round-trip: ${preview(result.value)}`)
1392
- },
1393
- {
1394
- name: "TEST-015 an uncaught failed host call fails the program with the envelope",
1395
- run: expectFailure("TEST-015 an uncaught failed host call fails the program with the envelope", makeRequest("async () => warehouse.query({ sql: 'x' })", { namespaces: [warehouseNamespace] }), respondingHost(() => CodeHostCallFailure.make({ error: {
1396
- _tag: "PolicyDenied",
1397
- message: "denied"
1398
- } })).host, "CodeProgramFailedError", (error) => error._tag === "CodeProgramFailedError" && error.reason === "rejected" && JSON.stringify(error.thrown) === JSON.stringify({
1399
- _tag: "PolicyDenied",
1400
- message: "denied"
1401
- }) ? void 0 : `unexpected failure detail ${preview(error)}`)
1402
- },
1403
- {
1404
- name: "TEST-015 fails typed on syntactically invalid source",
1405
- run: expectFailure("TEST-015 fails typed on syntactically invalid source", makeRequest("async () => {"), unusedHost, "CodeSourceError", (error) => error._tag === "CodeSourceError" && error.reason === "invalid" ? void 0 : `expected reason invalid, got ${preview(error)}`)
1406
- },
1407
- {
1408
- name: "TEST-015 fails typed when the expression is not one async function",
1409
- run: expectFailure("TEST-015 fails typed when the expression is not one async function", makeRequest("1 + 1"), unusedHost, "CodeSourceError", (error) => error._tag === "CodeSourceError" && error.reason === "not-a-function" ? void 0 : `expected reason not-a-function, got ${preview(error)}`)
1410
- },
1411
- {
1412
- name: "TEST-015 fails typed on source larger than the declared byte limit",
1413
- run: expectFailure("TEST-015 fails typed on source larger than the declared byte limit", makeRequest(`async () => "${"x".repeat(2e3)}"`, { limits: CodeExecutionLimits.make({
1414
- ...baseLimits,
1415
- maxSourceBytes: 256
1416
- }) }), unusedHost, "CodeSourceError", (error) => error._tag === "CodeSourceError" && error.reason === "oversized" ? void 0 : `expected reason oversized, got ${preview(error)}`)
1417
- },
1418
- {
1419
- name: "TEST-015 terminates a never-settling program at the wall-clock deadline",
1420
- run: expectFailure("TEST-015 terminates a never-settling program at the wall-clock deadline", makeRequest("async () => { await new Promise(() => {}); return 1; }", { limits: CodeExecutionLimits.make({
1421
- ...baseLimits,
1422
- maxWallTime: Duration.millis(250)
1423
- }) }), unusedHost, "CodeExecutionTimeoutError")
1424
- },
1425
- {
1426
- name: "TEST-015 fails typed when console output exceeds its byte budget",
1427
- run: expectFailure("TEST-015 fails typed when console output exceeds its byte budget", makeRequest("async () => { for (let i = 0; i < 64; i += 1) { console.log('x'.repeat(256)); } return 1; }", { limits: CodeExecutionLimits.make({
1428
- ...baseLimits,
1429
- maxLogBytes: 2048
1430
- }) }), unusedHost, "CodeOutputLimitError", (error) => error._tag === "CodeOutputLimitError" && error.surface === "logs" ? void 0 : `expected surface logs, got ${preview(error)}`)
1431
- },
1432
- {
1433
- name: "TEST-015 fails typed when the final result exceeds its byte budget",
1434
- run: expectFailure("TEST-015 fails typed when the final result exceeds its byte budget", makeRequest("async () => 'y'.repeat(4096)", { limits: CodeExecutionLimits.make({
1435
- ...baseLimits,
1436
- maxResultBytes: 1024
1437
- }) }), unusedHost, "CodeOutputLimitError", (error) => error._tag === "CodeOutputLimitError" && error.surface === "result" ? void 0 : `expected surface result, got ${preview(error)}`)
1438
- },
1439
- {
1440
- name: "TEST-015 fails typed when host calls exceed the executor cap",
1441
- run: Effect.gen(function* () {
1442
- const caseName = "TEST-015 fails typed when host calls exceed the executor cap";
1443
- const { host, calls } = respondingHost(() => CodeHostCallSuccess.make({ value: null }));
1444
- yield* expectFailure(caseName, makeRequest("async () => { await warehouse.query({}); await warehouse.query({}); await warehouse.query({}); return 1; }", {
1445
- namespaces: [warehouseNamespace],
1446
- limits: CodeExecutionLimits.make({
1447
- ...baseLimits,
1448
- maxHostCalls: 2
1449
- })
1450
- }), host, "CodeHostCallLimitError");
1451
- if (calls.length !== 2) return yield* violation(caseName, `expected exactly 2 dispatched host calls under a cap of 2, observed ${calls.length}`);
1452
- })
1453
- },
1454
- {
1455
- name: "TEST-015 fails typed on a host outcome outside the protocol schema",
1456
- run: expectFailure("TEST-015 fails typed on a host outcome outside the protocol schema", makeRequest("async () => warehouse.query({})", { namespaces: [warehouseNamespace] }), { call: () => Effect.succeed({ bogus: true }) }, "CodeExecutionProtocolError")
1457
- },
1458
- {
1459
- name: "TEST-015 surfaces an uncaught program throw with its bounded log capture",
1460
- run: expectFailure("TEST-015 surfaces an uncaught program throw with its bounded log capture", makeRequest("async () => { console.log('before the failure'); throw new Error('deliberate'); }"), unusedHost, "CodeProgramFailedError", (error) => error._tag === "CodeProgramFailedError" && error.reason === "threw" && error.logs.some((line) => line.includes("before the failure")) ? void 0 : `expected a threw failure carrying the log capture, got ${preview(error)}`)
1461
- },
1462
- {
1463
- name: "TEST-015 fails typed when the program returns a non-JSON value",
1464
- run: expectFailure("TEST-015 fails typed when the program returns a non-JSON value", makeRequest("async () => (() => 1)"), unusedHost, "CodeProgramFailedError", (error) => error._tag === "CodeProgramFailedError" && error.reason === "non-json-result" ? void 0 : `expected reason non-json-result, got ${preview(error)}`)
1465
- },
1466
- {
1467
- name: "CAP-015 rejects a network allowlist it cannot enforce with a typed unsupported error",
1468
- run: expectFailure("CAP-015 rejects a network allowlist it cannot enforce with a typed unsupported error", makeRequest("async () => 1", { network: NetworkAllowlist.make({
1469
- domains: ["example.com"],
1470
- ports: [443]
1471
- }) }), unusedHost, "CodeExecutorUnsupportedError", (error) => error._tag === "CodeExecutorUnsupportedError" && error.feature === "network" ? void 0 : `expected feature network, got ${preview(error)}`)
1472
- },
1473
- {
1474
- name: "TEST-015 interruption reaches in-flight host calls and pass teardown",
1475
- run: Effect.gen(function* () {
1476
- const caseName = "TEST-015 interruption reaches in-flight host calls and pass teardown";
1477
- const started = yield* Deferred.make();
1478
- const witness = { hostCallInterrupted: false };
1479
- const fiber = yield* runPass(makeRequest("async () => warehouse.query({})", { namespaces: [warehouseNamespace] }), { call: () => Deferred.succeed(started, void 0).pipe(Effect.andThen(Effect.never), Effect.ensuring(Effect.sync(() => {
1480
- witness.hostCallInterrupted = true;
1481
- }))) }).pipe(Effect.forkChild);
1482
- if ((yield* Effect.raceFirst(Deferred.await(started).pipe(Effect.as("started")), Fiber.join(fiber).pipe(Effect.exit, Effect.as("exited")))) === "exited") return yield* violation(caseName, "the pass settled before any host call reached the CodeExecutionHost");
1483
- yield* Fiber.interrupt(fiber);
1484
- if (!witness.hostCallInterrupted) return yield* violation(caseName, "interrupting the pass did not interrupt the in-flight host call");
1485
- })
1486
- }
1487
- ];
1488
- };
1489
- //#endregion
1490
- //#region src/code-executor-substitute.ts
1491
- /**
1492
- * The deterministic in-process executor substitute (C1 of ADR-0017). It runs
1493
- * the generated program on the host JavaScript engine with best-effort global
1494
- * shadowing only, so it self-identifies as `unisolated` and is never a
1495
- * security boundary (CAP-010, CAP-015). It exists to prove the public
1496
- * `CodeExecutor` contract and to drive deterministic capability tests.
1497
- */
1498
- const inProcessCodeExecutorImplementation = SandboxImplementation.make({
1499
- isolation: "unisolated",
1500
- identity: "in-process-javascript"
1501
- });
1502
- const MAX_LOG_LINES = 4096;
1503
- const MAX_LOG_LINE_CHARACTERS = 16e3;
1504
- const MAX_THROWN_CHARACTERS = 4e3;
1505
- const utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
1506
- /**
1507
- * Ambient globals shadowed inside the harness. Shadowing blocks the obvious
1508
- * identifier paths only; a determined program can still escape, which is
1509
- * exactly why this executor reports `unisolated` and the isolated network and
1510
- * CPU enforcement conformance cases run only against isolated adapters.
1511
- */
1512
- const shadowedGlobals = [
1513
- "fetch",
1514
- "process",
1515
- "require",
1516
- "module",
1517
- "exports",
1518
- "global",
1519
- "globalThis",
1520
- "XMLHttpRequest",
1521
- "WebSocket",
1522
- "Deno",
1523
- "Bun"
1524
- ];
1525
- var LogLimitSignal = class {
1526
- observed;
1527
- constructor(observed) {
1528
- this.observed = observed;
1529
- }
1530
- };
1531
- var EvaluationThrew = class {
1532
- inner;
1533
- constructor(inner) {
1534
- this.inner = inner;
1535
- }
1536
- };
1537
- var NotAFunction = class {
1538
- actual;
1539
- constructor(actual) {
1540
- this.actual = actual;
1541
- }
1542
- };
1543
- /**
1544
- * Total, defect-free rendering of untrusted values: a hostile Proxy can throw
1545
- * from property access, `toString`, and `Symbol.toPrimitive`, and an expected
1546
- * program failure must never escape the typed channel as a defect while its
1547
- * diagnostics are being serialized.
1548
- */
1549
- const formatLogValue = (value) => {
1550
- try {
1551
- if (typeof value === "string") return value;
1552
- return JSON.stringify(value) ?? String(value);
1553
- } catch {
1554
- try {
1555
- return String(value);
1556
- } catch {
1557
- return "[unprintable value]";
1558
- }
1559
- }
1560
- };
1561
- const makeConsole = (capture, limits) => {
1562
- const write = (...values) => {
1563
- const joined = values.map(formatLogValue).join(" ");
1564
- const line = joined.length > MAX_LOG_LINE_CHARACTERS ? `${joined.slice(0, MAX_LOG_LINE_CHARACTERS - 1)}…` : joined;
1565
- const bytes = utf8ByteLength(line);
1566
- if (capture.lines.length >= MAX_LOG_LINES || capture.bytes + bytes > limits.maxLogBytes) throw new LogLimitSignal(capture.bytes + bytes);
1567
- capture.lines.push(line);
1568
- capture.bytes += bytes;
1569
- };
1570
- return {
1571
- debug: write,
1572
- error: write,
1573
- info: write,
1574
- log: write,
1575
- warn: write
1576
- };
1577
- };
1578
- const buildNamespaceObject = (namespace, offer) => {
1579
- const methods = {};
1580
- for (const method of namespace.methods) methods[method] = (argument) => new Promise((resolve, reject) => {
1581
- offer({
1582
- namespace: namespace.name,
1583
- method,
1584
- argument,
1585
- resolve,
1586
- reject
1587
- });
1588
- });
1589
- return methods;
1590
- };
1591
- const boundedText = (value) => {
1592
- try {
1593
- return (value instanceof Error ? `${value.name}: ${value.message}` : formatLogValue(value)).slice(0, MAX_THROWN_CHARACTERS);
1594
- } catch {
1595
- return "[unserializable thrown value]";
1596
- }
1597
- };
1598
- /** Schema decoding of hostile values may itself throw through trap getters. */
1599
- const safeDecodeJson = (value) => {
1600
- try {
1601
- return Schema.decodeUnknownOption(Schema.Json)(value);
1602
- } catch {
1603
- return Option.none();
1604
- }
1605
- };
1606
- const boundedThrown = (value) => {
1607
- const decoded = safeDecodeJson(value);
1608
- if (Option.isSome(decoded)) try {
1609
- const encoded = JSON.stringify(decoded.value);
1610
- if (encoded !== void 0 && encoded.length <= MAX_THROWN_CHARACTERS) return decoded.value;
1611
- } catch {}
1612
- return boundedText(value);
1613
- };
1614
- const encodedJsonByteLength = (value) => {
1615
- try {
1616
- const encoded = JSON.stringify(value);
1617
- return encoded === void 0 ? void 0 : utf8ByteLength(encoded);
1618
- } catch {
1619
- return;
1620
- }
1621
- };
1622
- /** Host outcomes are protocol input; a hostile value must not defect mid-decode. */
1623
- const decodeHostOutcome = (value) => {
1624
- try {
1625
- return Schema.decodeUnknownOption(CodeHostCallResult)(value);
1626
- } catch {
1627
- return Option.none();
1628
- }
1629
- };
1630
- const validateRequest = (request) => Effect.gen(function* () {
1631
- if (request.network._tag !== "NetworkDisabled") return yield* CodeExecutorUnsupportedError.make({
1632
- implementation: inProcessCodeExecutorImplementation,
1633
- feature: "network",
1634
- message: "The unisolated in-process executor cannot enforce an egress allowlist; only NetworkDisabled is accepted, and even that is shadowed rather than enforced"
1635
- });
1636
- if (request.limits.cpuMillis !== void 0) return yield* CodeExecutorUnsupportedError.make({
1637
- implementation: inProcessCodeExecutorImplementation,
1638
- feature: "cpu-limit",
1639
- message: "The unisolated in-process executor shares the host engine and cannot enforce a CPU limit"
1640
- });
1641
- const reservedNames = /* @__PURE__ */ new Set([...shadowedGlobals, "console"]);
1642
- const seen = /* @__PURE__ */ new Set();
1643
- for (const namespace of request.namespaces) {
1644
- if (reservedNames.has(namespace.name) || seen.has(namespace.name)) return yield* CodeExecutorUnsupportedError.make({
1645
- implementation: inProcessCodeExecutorImplementation,
1646
- feature: "namespaces",
1647
- message: `Namespace ${namespace.name} collides with a harness binding or another namespace`
1648
- });
1649
- seen.add(namespace.name);
1650
- }
1651
- const sourceBytes = utf8ByteLength(request.source);
1652
- if (sourceBytes > request.limits.maxSourceBytes) return yield* CodeSourceError.make({
1653
- implementation: inProcessCodeExecutorImplementation,
1654
- reason: "oversized",
1655
- message: `Source is ${sourceBytes} bytes; the request allows ${request.limits.maxSourceBytes}`
1656
- });
1657
- });
1658
- const serveHostCalls = (host, queue, limits, capture, counter) => Effect.gen(function* () {
1659
- while (true) {
1660
- const pending = yield* Queue.take(queue);
1661
- counter.calls += 1;
1662
- if (counter.calls > limits.maxHostCalls) return yield* CodeHostCallLimitError.make({
1663
- implementation: inProcessCodeExecutorImplementation,
1664
- limit: limits.maxHostCalls,
1665
- logs: [...capture.lines]
1666
- });
1667
- const argument = safeDecodeJson(pending.argument);
1668
- if (Option.isNone(argument)) {
1669
- pending.reject(/* @__PURE__ */ new TypeError("host call arguments must be JSON values"));
1670
- continue;
1671
- }
1672
- const argumentBytes = encodedJsonByteLength(argument.value);
1673
- if (argumentBytes === void 0 || argumentBytes > limits.maxHostCallArgumentBytes) return yield* CodeOutputLimitError.make({
1674
- implementation: inProcessCodeExecutorImplementation,
1675
- surface: "host-call-argument",
1676
- limit: limits.maxHostCallArgumentBytes,
1677
- observed: argumentBytes ?? 0,
1678
- logs: [...capture.lines]
1679
- });
1680
- const rawOutcome = yield* host.call(CodeHostCall.make({
1681
- namespace: pending.namespace,
1682
- method: pending.method,
1683
- argument: argument.value
1684
- }));
1685
- const outcome = decodeHostOutcome(rawOutcome);
1686
- if (Option.isNone(outcome)) return yield* CodeExecutionProtocolError.make({
1687
- implementation: inProcessCodeExecutorImplementation,
1688
- message: "The execution host returned a value outside the CodeHostCallResult schema"
1689
- });
1690
- if (outcome.value._tag === "CodeHostCallFailure") {
1691
- pending.reject(outcome.value.error);
1692
- continue;
1693
- }
1694
- const resultBytes = encodedJsonByteLength(outcome.value.value);
1695
- if (resultBytes === void 0 || resultBytes > limits.maxHostCallResultBytes) return yield* CodeOutputLimitError.make({
1696
- implementation: inProcessCodeExecutorImplementation,
1697
- surface: "host-call-result",
1698
- limit: limits.maxHostCallResultBytes,
1699
- observed: resultBytes ?? 0,
1700
- logs: [...capture.lines]
1701
- });
1702
- pending.resolve(outcome.value.value);
1703
- }
1704
- });
1705
- const classifyProgramFailure = (thrown, limits, capture) => {
1706
- const inner = thrown instanceof EvaluationThrew ? thrown.inner : thrown;
1707
- if (inner instanceof LogLimitSignal) return CodeOutputLimitError.make({
1708
- implementation: inProcessCodeExecutorImplementation,
1709
- surface: "logs",
1710
- limit: limits.maxLogBytes,
1711
- observed: inner.observed,
1712
- logs: [...capture.lines]
1713
- });
1714
- if (inner instanceof NotAFunction) return CodeSourceError.make({
1715
- implementation: inProcessCodeExecutorImplementation,
1716
- reason: "not-a-function",
1717
- message: `The source expression evaluated to ${inner.actual}; it must evaluate to one async function`
1718
- });
1719
- const reason = thrown instanceof EvaluationThrew || inner instanceof Error ? "threw" : "rejected";
1720
- return CodeProgramFailedError.make({
1721
- implementation: inProcessCodeExecutorImplementation,
1722
- reason,
1723
- thrown: boundedThrown(inner),
1724
- message: boundedText(inner),
1725
- logs: [...capture.lines]
1726
- });
1727
- };
1728
- const executeInProcess = Effect.fn("InProcessCodeExecutor.execute")(function* (request) {
1729
- yield* validateRequest(request);
1730
- const host = yield* CodeExecutionHost;
1731
- const capture = {
1732
- lines: [],
1733
- bytes: 0
1734
- };
1735
- const counter = { calls: 0 };
1736
- const queue = yield* Queue.unbounded();
1737
- const factory = yield* Effect.try({
1738
- try: () => new Function(...shadowedGlobals, "console", ...request.namespaces.map((namespace) => namespace.name), `"use strict";\nreturn (\n${request.source}\n);`),
1739
- catch: (cause) => CodeSourceError.make({
1740
- implementation: inProcessCodeExecutorImplementation,
1741
- reason: "invalid",
1742
- message: boundedText(cause)
1743
- })
1744
- });
1745
- const harnessConsole = makeConsole(capture, request.limits);
1746
- let issuedHostCalls = 0;
1747
- const namespaceObjects = request.namespaces.map((namespace) => buildNamespaceObject(namespace, (pending) => {
1748
- issuedHostCalls += 1;
1749
- if (issuedHostCalls > request.limits.maxHostCalls + 1) {
1750
- pending.reject(/* @__PURE__ */ new Error(`host-call limit of ${request.limits.maxHostCalls} exceeded`));
1751
- return;
1752
- }
1753
- Queue.offerUnsafe(queue, pending);
1754
- }));
1755
- const server = yield* serveHostCalls(host, queue, request.limits, capture, counter).pipe(Effect.forkScoped);
1756
- const program = Effect.tryPromise({
1757
- try: async () => {
1758
- let candidate;
1759
- try {
1760
- candidate = factory(...shadowedGlobals.map(() => void 0), harnessConsole, ...namespaceObjects);
1761
- } catch (cause) {
1762
- throw new EvaluationThrew(cause);
1763
- }
1764
- if (typeof candidate !== "function") throw new EvaluationThrew(new NotAFunction(typeof candidate));
1765
- let outcome;
1766
- try {
1767
- outcome = candidate();
1768
- } catch (cause) {
1769
- throw new EvaluationThrew(cause);
1770
- }
1771
- return await Promise.resolve(outcome);
1772
- },
1773
- catch: (thrown) => classifyProgramFailure(thrown, request.limits, capture)
1774
- });
1775
- const startedAt = yield* Clock.currentTimeMillis;
1776
- const returned = yield* Effect.raceFirst(program, Fiber.join(server)).pipe(Effect.timeoutOrElse({
1777
- duration: request.limits.maxWallTime,
1778
- orElse: () => CodeExecutionTimeoutError.make({
1779
- implementation: inProcessCodeExecutorImplementation,
1780
- kind: "wall-clock",
1781
- maxWallTime: request.limits.maxWallTime,
1782
- logs: [...capture.lines]
1783
- })
1784
- }), Effect.ensuring(Fiber.interrupt(server)));
1785
- const finishedAt = yield* Clock.currentTimeMillis;
1786
- if (issuedHostCalls > request.limits.maxHostCalls) return yield* CodeHostCallLimitError.make({
1787
- implementation: inProcessCodeExecutorImplementation,
1788
- limit: request.limits.maxHostCalls,
1789
- logs: [...capture.lines]
1790
- });
1791
- const value = yield* Schema.decodeUnknownEffect(Schema.Json)(returned).pipe(Effect.mapError(() => CodeProgramFailedError.make({
1792
- implementation: inProcessCodeExecutorImplementation,
1793
- reason: "non-json-result",
1794
- thrown: null,
1795
- message: "The program must return a JSON value",
1796
- logs: [...capture.lines]
1797
- })));
1798
- const resultBytes = encodedJsonByteLength(value);
1799
- if (resultBytes === void 0 || resultBytes > request.limits.maxResultBytes) return yield* CodeOutputLimitError.make({
1800
- implementation: inProcessCodeExecutorImplementation,
1801
- surface: "result",
1802
- limit: request.limits.maxResultBytes,
1803
- observed: resultBytes ?? 0,
1804
- logs: [...capture.lines]
1805
- });
1806
- return CodeExecutionResult.make({
1807
- implementation: inProcessCodeExecutorImplementation,
1808
- value,
1809
- logs: [...capture.lines],
1810
- resourceUse: CodeExecutionResourceUse.make({
1811
- wallTime: Duration.millis(Math.max(0, finishedAt - startedAt)),
1812
- hostCalls: counter.calls,
1813
- logBytes: capture.bytes,
1814
- resultBytes
1815
- })
1816
- });
1817
- });
1818
- /**
1819
- * Layer providing the unisolated in-process `CodeExecutor` substitute. The
1820
- * per-pass `CodeExecutionHost` stays in the caller's requirement channel, the
1821
- * same as every real adapter.
1822
- */
1823
- const inProcessCodeExecutorLayer = Layer.succeed(CodeExecutor)(CodeExecutor.of({ execute: executeInProcess }));
1824
- //#endregion
1825
- //#region src/fixtures/docs-researcher/definition.ts
1826
- const ResearchDocumentId = Schema.NonEmptyString.check(Schema.isMaxLength(64)).pipe(Schema.brand("@effect-agent/testing/docs-researcher/ResearchDocumentId"));
1827
- const BoundedTitle = Schema.NonEmptyString.check(Schema.isMaxLength(120));
1828
- const BoundedBody = Schema.NonEmptyString.check(Schema.isMaxLength(16 * 1024));
1829
- /** Bounded summary text: the ONLY child-derived text that may cross to the parent. */
1830
- const BoundedSummary = Schema.NonEmptyString.check(Schema.isMaxLength(240));
1831
- var DocumentQuery = class extends Schema.Class("DocumentQuery")({ documentId: ResearchDocumentId }) {};
1832
- /** One bounded research document as the MCP content server exposes it. */
1833
- var ResearchDocument = class extends Schema.Class("ResearchDocument")({
1834
- documentId: ResearchDocumentId,
1835
- title: BoundedTitle,
1836
- body: BoundedBody
1837
- }) {};
1838
- var DocumentUnavailable = class extends Schema.TaggedError()("DocumentUnavailable", {
1839
- documentId: ResearchDocumentId,
1840
- message: Schema.String
1841
- }) {};
1842
- /** The content store behind the scripted MCP server. */
1843
- var DocumentLibrary = class extends Context.Service()("@effect-agent/testing/docs-researcher/DocumentLibrary") {};
1844
- /**
1845
- * The one content tool the doc-summarizer child uses. Its authored JSON
1846
- * schema is what MCP discovery must serve byte-for-byte: the scripted MCP
1847
- * fixture derives its discovery entry from `Tool.getJsonSchema(FetchDocument)`
1848
- * and `validateMcpDiscovery` re-derives and digests both sides (CAP-009).
1849
- */
1850
- const FetchDocument = Tool.make("fetch_document", {
1851
- description: "Fetch one bounded research document by its identifier.",
1852
- parameters: DocumentQuery,
1853
- success: ResearchDocument,
1854
- failure: DocumentUnavailable,
1855
- failureMode: "error",
1856
- dependencies: [DocumentLibrary]
1857
- });
1858
- const DocContentToolkit = Toolkit.make(FetchDocument);
1859
- const docContentToolkitLayer = DocContentToolkit.toLayer({ fetch_document: (query) => Effect.flatMap(DocumentLibrary, (library) => library.fetch(query)) });
1860
- /** Never allowed outside a child Conversation or an unredacted fixture value. */
1861
- const docsDocumentBodySecret = "docs-vault-secret-771";
1862
- const decodeDocumentId = Schema.decodeSync(ResearchDocumentId);
1863
- const corpusEntries = new Map([{
1864
- documentId: "durability-notes",
1865
- title: "Durability protocol notes",
1866
- bodyPhrase: "amber-ledger-passage",
1867
- summary: "Settlement results are recorded exactly once while external side effects stay at-least-once."
1868
- }, {
1869
- documentId: "subagent-notes",
1870
- title: "Subagent join notes",
1871
- bodyPhrase: "cobalt-join-corridor",
1872
- summary: "A parent joins only the verified settlement of its own established child."
1873
- }].map((entry) => [entry.documentId, {
1874
- document: ResearchDocument.make({
1875
- documentId: decodeDocumentId(entry.documentId),
1876
- title: entry.title,
1877
- body: `${entry.bodyPhrase}: internal working notes. ${docsDocumentBodySecret}. ${entry.summary} Raw notes stay inside the child Conversation.`
1878
- }),
1879
- bodyPhrase: entry.bodyPhrase,
1880
- summary: entry.summary
1881
- }]));
1882
- /** The corpus document ids in canonical fixture order. */
1883
- const researchCorpusDocumentIds = [decodeDocumentId("durability-notes"), decodeDocumentId("subagent-notes")];
1884
- const requireCorpusEntry = (documentId) => {
1885
- const entry = corpusEntries.get(documentId);
1886
- if (entry === void 0) throw new Error(`No deterministic corpus entry exists for document ${documentId}`);
1887
- return entry;
1888
- };
1889
- /** Deterministic library lookup shared by the scripted MCP content handlers. */
1890
- const researchDocumentLookup = (query) => {
1891
- const entry = corpusEntries.get(query.documentId);
1892
- return entry === void 0 ? Effect.fail(DocumentUnavailable.make({
1893
- documentId: query.documentId,
1894
- message: "No deterministic corpus entry exists for this document."
1895
- })) : Effect.succeed(entry.document);
1896
- };
1897
- /** The full fixture document (body includes the secret marker — child-side only). */
1898
- const researchDocumentFor = (documentId) => requireCorpusEntry(documentId).document;
1899
- /** The distinctive body phrase used by context-isolation assertions. */
1900
- const documentBodyPhrase = (documentId) => requireCorpusEntry(documentId).bodyPhrase;
1901
- var SummaryBrief = class extends Schema.Class("SummaryBrief")({
1902
- documentId: ResearchDocumentId,
1903
- focus: Schema.NonEmptyString
1904
- }) {};
1905
- var DocumentSummary = class extends Schema.Class("DocumentSummary")({
1906
- documentId: ResearchDocumentId,
1907
- summary: BoundedSummary
1908
- }) {};
1909
- /** The summary the scripted child writes after fetching the document. */
1910
- const documentSummaryFor = (documentId) => DocumentSummary.make({
1911
- documentId: requireCorpusEntry(documentId).document.documentId,
1912
- summary: requireCorpusEntry(documentId).summary
1913
- });
1914
- const encodedDocumentSummary = (documentId) => JSON.stringify(Schema.encodeSync(DocumentSummary)(documentSummaryFor(documentId)));
1915
- const DocSummarizer = Agent.define("doc-summarizer", {
1916
- input: SummaryBrief,
1917
- output: DocumentSummary,
1918
- instructions: "Fetch the briefed document with fetch_document exactly once, then return only a JSON document summary. Never copy raw notes or secrets into the summary.",
1919
- toolkit: DocContentToolkit,
1920
- policy: AgentPolicy.make({
1921
- maxTurns: 2,
1922
- maxToolCalls: 1,
1923
- maxDuration: "30 seconds",
1924
- toolConcurrency: 1
1925
- }),
1926
- description: "Summarize one bounded research document fetched through MCP content tools.",
1927
- metadata: {
1928
- deploymentClass: "DN",
1929
- phase: "P7"
1930
- }
1931
- });
1932
- var SummaryRequest = class extends Schema.Class("SummaryRequest")({ documentId: ResearchDocumentId }) {};
1933
- var SummaryFinding = class extends Schema.Class("SummaryFinding")({
1934
- documentId: ResearchDocumentId,
1935
- summary: BoundedSummary
1936
- }) {};
1937
- var DocumentSummaryFailed = class extends Schema.TaggedError()("DocumentSummaryFailed", { childErrorTag: Schema.NonEmptyString }) {};
1938
- /** Finite per-invocation bounds (SUB-009): one fetch per child, two children per Run. */
1939
- const documentSummaryPolicy = SubagentPolicy.make({
1940
- maxChildren: 2,
1941
- maxConcurrency: 2,
1942
- maxTurns: 2,
1943
- maxToolCalls: 1,
1944
- maxDuration: "10 seconds"
1945
- });
1946
- const delegateDocumentSummary = Subagent.define("delegate_document_summary", {
1947
- description: "Summarize one research document through the doc-summarizer child and return a bounded finding.",
1948
- target: DocSummarizer,
1949
- parameters: SummaryRequest,
1950
- success: SummaryFinding,
1951
- failure: DocumentSummaryFailed,
1952
- prepareInput: (request) => Effect.succeed(SummaryBrief.make({
1953
- documentId: request.documentId,
1954
- focus: "summarize:durability-claims"
1955
- })),
1956
- projectResult: (summary) => Effect.succeed(SummaryFinding.make({
1957
- documentId: summary.documentId,
1958
- summary: summary.summary
1959
- })),
1960
- policy: documentSummaryPolicy
1961
- });
1962
- /** Total mapping from every expected child Run failure to the declared Tool failure (SUB-028). */
1963
- const mapSummaryChildFailure = (failure) => DocumentSummaryFailed.make({ childErrorTag: failure._tag });
1964
- /** The exact digest strings the durable declaration AND host registration must share (SUB-023). */
1965
- const docsSummarizerDigestStrings = {
1966
- agent: "50".repeat(32),
1967
- model: "51".repeat(32),
1968
- tools: "52".repeat(32)
1969
- };
1970
- /** Runtime wiring: the immutable delegation plus one explicit child Binding (S2 declaration). */
1971
- const docsSummaryHandlersLayer = (childBinding) => SubagentRuntime.layer(delegateDocumentSummary, childBinding, {
1972
- mapChildFailure: mapSummaryChildFailure,
1973
- durable: { targetDigests: docsSummarizerDigestStrings }
1974
- });
1975
- var ResearchRequest = class extends Schema.Class("ResearchRequest")({
1976
- question: Schema.NonEmptyString,
1977
- documentIds: Schema.Array(ResearchDocumentId).check(Schema.isMinLength(1))
1978
- }) {};
1979
- var ResearchDigest = class extends Schema.Class("ResearchDigest")({
1980
- findings: Schema.Array(SummaryFinding),
1981
- nextAction: Schema.Literal("review")
1982
- }) {};
1983
- /** Parent-only transcript markers proving child context isolation (SUB-006/SUB-015). */
1984
- const docsCoordinatorConfidentialMarker = "docs-coordinator-vault-19x";
1985
- const docsMissionConfidentialMarker = "docs-mission-dossier-42f";
1986
- const DocsResearcherToolkit = Toolkit.make(delegateDocumentSummary.tool);
1987
- const DocsResearcher = Agent.define("docs-researcher", {
1988
- input: ResearchRequest,
1989
- output: ResearchDigest,
1990
- instructions: [
1991
- "You are the Effect Agent P7 docs-researcher coordinator.",
1992
- `Coordinator-only context: ${docsCoordinatorConfidentialMarker}.`,
1993
- "Call delegate_document_summary once per requested document in one Tool batch.",
1994
- "Return only a JSON digest built from the delegated findings. This is read-only research."
1995
- ].join("\n"),
1996
- toolkit: DocsResearcherToolkit,
1997
- policy: AgentPolicy.make({
1998
- maxTurns: 2,
1999
- maxToolCalls: 2,
2000
- maxDuration: "30 seconds",
2001
- toolConcurrency: 2
2002
- }),
2003
- description: "Coordinate per-document summarization through one declared delegation Tool.",
2004
- metadata: {
2005
- deploymentClass: "DN",
2006
- phase: "P7"
2007
- }
2008
- });
2009
- /** The default two-document research mission. */
2010
- const researchMissionRequest = ResearchRequest.make({
2011
- question: `Summarize the durability and subagent notes; keep ${docsMissionConfidentialMarker} inside the coordinator conversation.`,
2012
- documentIds: researchCorpusDocumentIds
2013
- });
2014
- /** The coordinator's expected final digest for the given documents. */
2015
- const expectedResearchDigest = (documentIds = researchCorpusDocumentIds) => ResearchDigest.make({
2016
- findings: documentIds.map((documentId) => {
2017
- const summary = documentSummaryFor(documentId);
2018
- return SummaryFinding.make({
2019
- documentId: summary.documentId,
2020
- summary: summary.summary
2021
- });
2022
- }),
2023
- nextAction: "review"
2024
- });
2025
- //#endregion
2026
- //#region src/fixtures/travel-planner/definition.ts
2027
- const AirportCode = Schema.NonEmptyString.pipe(Schema.brand("@effect-agent/testing/travel-planner/AirportCode"));
2028
- const QuoteId = Schema.NonEmptyString.pipe(Schema.brand("@effect-agent/testing/travel-planner/QuoteId"));
2029
- var TripRequest = class extends Schema.Class("TripRequest")({
2030
- request: Schema.NonEmptyString,
2031
- origin: AirportCode,
2032
- destination: AirportCode,
2033
- departOn: Schema.String,
2034
- nights: Schema.Int.check(Schema.isGreaterThan(0)),
2035
- travelers: Schema.Int.check(Schema.isGreaterThan(0)),
2036
- budgetCents: Schema.Int.check(Schema.isGreaterThan(0)),
2037
- currency: Schema.Literal("USD")
2038
- }) {};
2039
- var FlightQuery = class extends Schema.Class("FlightQuery")({
2040
- origin: AirportCode,
2041
- destination: AirportCode,
2042
- departOn: Schema.String,
2043
- travelers: Schema.Int.check(Schema.isGreaterThan(0))
2044
- }) {};
2045
- var LodgingQuery = class extends Schema.Class("LodgingQuery")({
2046
- destination: AirportCode,
2047
- departOn: Schema.String,
2048
- nights: Schema.Int.check(Schema.isGreaterThan(0)),
2049
- travelers: Schema.Int.check(Schema.isGreaterThan(0))
2050
- }) {};
2051
- var ActivityQuery = class extends Schema.Class("ActivityQuery")({
2052
- destination: AirportCode,
2053
- departOn: Schema.String,
2054
- nights: Schema.Int.check(Schema.isGreaterThan(0)),
2055
- travelers: Schema.Int.check(Schema.isGreaterThan(0))
2056
- }) {};
2057
- var FlightOption = class extends Schema.Class("FlightOption")({
2058
- quoteId: QuoteId,
2059
- flight: Schema.String,
2060
- estimatedCents: Schema.Int.check(Schema.isGreaterThan(0)),
2061
- currency: Schema.Literal("USD")
2062
- }) {};
2063
- var LodgingOption = class extends Schema.Class("LodgingOption")({
2064
- lodging: Schema.String,
2065
- estimatedCents: Schema.Int.check(Schema.isGreaterThan(0)),
2066
- currency: Schema.Literal("USD")
2067
- }) {};
2068
- /** A successful empty activity search is distinct from supplier unavailability. */
2069
- var ActivitySearchResult = class extends Schema.Class("ActivitySearchResult")({ activities: Schema.Array(Schema.String) }) {};
2070
- var Itinerary = class extends Schema.Class("Itinerary")({
2071
- title: Schema.String,
2072
- route: Schema.String,
2073
- dates: Schema.String,
2074
- flight: Schema.String,
2075
- lodging: Schema.String,
2076
- activities: Schema.Array(Schema.String),
2077
- estimatedTotalCents: Schema.Int.check(Schema.isGreaterThan(0)),
2078
- currency: Schema.Literal("USD"),
2079
- quoteId: QuoteId,
2080
- assumptions: Schema.Array(Schema.String),
2081
- unresolvedConstraints: Schema.Array(Schema.String),
2082
- nextAction: Schema.Literal("review")
2083
- }) {};
2084
- var TravelPlan = class extends Schema.Class("TravelPlan")({ itineraries: Schema.Array(Itinerary) }) {};
2085
- const unavailableFields = {
2086
- query: Schema.String,
2087
- message: Schema.String
2088
- };
2089
- var FlightUnavailable = class extends Schema.TaggedError()("FlightUnavailable", unavailableFields) {};
2090
- var LodgingUnavailable = class extends Schema.TaggedError()("LodgingUnavailable", unavailableFields) {};
2091
- var ActivityUnavailable = class extends Schema.TaggedError()("ActivityUnavailable", unavailableFields) {};
2092
- var GuidanceFailure = class extends Schema.TaggedError()("GuidanceFailure", { message: Schema.String }) {};
2093
- var FlightCatalog = class extends Context.Service()("@effect-agent/testing/travel-planner/FlightCatalog") {};
2094
- var LodgingCatalog = class extends Context.Service()("@effect-agent/testing/travel-planner/LodgingCatalog") {};
2095
- var ActivityCatalog = class extends Context.Service()("@effect-agent/testing/travel-planner/ActivityCatalog") {};
2096
- var TravelGuidance = class extends Context.Service()("@effect-agent/testing/travel-planner/TravelGuidance") {};
2097
- const SearchFlights = Tool.make("search_flights", {
2098
- parameters: FlightQuery,
2099
- success: FlightOption,
2100
- failure: FlightUnavailable,
2101
- failureMode: "error",
2102
- dependencies: [FlightCatalog]
2103
- });
2104
- const SearchLodging = Tool.make("search_lodging", {
2105
- parameters: LodgingQuery,
2106
- success: LodgingOption,
2107
- failure: LodgingUnavailable,
2108
- failureMode: "error",
2109
- dependencies: [LodgingCatalog]
2110
- });
2111
- const SearchActivities = Tool.make("search_activities", {
2112
- parameters: ActivityQuery,
2113
- success: ActivitySearchResult,
2114
- failure: ActivityUnavailable,
2115
- failureMode: "error",
2116
- dependencies: [ActivityCatalog]
2117
- });
2118
- const TravelPlannerToolkit = Toolkit.make(SearchFlights, SearchLodging, SearchActivities);
2119
- const TravelPlannerToolkitLayer = TravelPlannerToolkit.toLayer({
2120
- search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(query)),
2121
- search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(query)),
2122
- search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(query))
2123
- });
2124
- const TravelPlanner = Agent.define("travel-planner", {
2125
- input: TripRequest,
2126
- output: TravelPlan,
2127
- instructions: (input) => Effect.flatMap(TravelGuidance, (guidance) => guidance.instructions(input)),
2128
- toolkit: TravelPlannerToolkit,
2129
- policy: AgentPolicy.make({
2130
- maxTurns: 2,
2131
- maxToolCalls: 3,
2132
- maxDuration: "30 seconds",
2133
- toolConcurrency: 3
2134
- }),
2135
- description: "Build one review-only itinerary from bounded parallel deterministic searches.",
2136
- metadata: {
2137
- deploymentClass: "E",
2138
- phase: "P1"
2139
- }
2140
- });
2141
- //#endregion
2142
- //#region src/fixtures/travel-planner/deterministic-layers.ts
2143
- var CatalogLifecycleCounts = class extends Schema.Class("CatalogLifecycleCounts")({
2144
- acquired: Schema.Natural,
2145
- finalized: Schema.Natural
2146
- }) {};
2147
- var CatalogLifecycle = class CatalogLifecycle extends Context.Service()("@effect-agent/testing/travel-planner/CatalogLifecycle") {
2148
- static layerNoDeps = Layer.effect(this, Effect.gen(function* () {
2149
- const acquired = yield* Ref.make(0);
2150
- const finalized = yield* Ref.make(0);
2151
- return CatalogLifecycle.of({
2152
- markAcquired: Ref.update(acquired, (n) => n + 1),
2153
- markFinalized: Ref.update(finalized, (n) => n + 1),
2154
- counts: Effect.all({
2155
- acquired: Ref.get(acquired),
2156
- finalized: Ref.get(finalized)
2157
- }).pipe(Effect.map((counts) => CatalogLifecycleCounts.make(counts)))
2158
- });
2159
- }));
2160
- };
2161
- const flight = FlightOption.make({
2162
- quoteId: Schema.decodeSync(QuoteId)("quote-sfo-lhr-001"),
2163
- flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
2164
- estimatedCents: 18e4,
2165
- currency: "USD"
2166
- });
2167
- const lodging = LodgingOption.make({
2168
- lodging: "Bloomsbury House · refundable studio · 4 nights",
2169
- estimatedCents: 104e3,
2170
- currency: "USD"
2171
- });
2172
- const activities = ActivitySearchResult.make({ activities: ["British Museum timed entry", "Thames evening walk"] });
2173
- const ReverseCompletionToolkitLayer = Effect.gen(function* () {
2174
- const flightStarted = yield* Deferred.make();
2175
- const lodgingStarted = yield* Deferred.make();
2176
- const activityStarted = yield* Deferred.make();
2177
- const releaseFlight = yield* Deferred.make();
2178
- const releaseLodging = yield* Deferred.make();
2179
- const releaseActivity = yield* Deferred.make();
2180
- const awaitRelease = (started, release, value) => Deferred.succeed(started, void 0).pipe(Effect.andThen(Deferred.await(release)), Effect.as(value));
2181
- return {
2182
- controls: {
2183
- flightStarted: Deferred.await(flightStarted),
2184
- lodgingStarted: Deferred.await(lodgingStarted),
2185
- activityStarted: Deferred.await(activityStarted),
2186
- releaseFlight: Deferred.succeed(releaseFlight, void 0).pipe(Effect.asVoid),
2187
- releaseLodging: Deferred.succeed(releaseLodging, void 0).pipe(Effect.asVoid),
2188
- releaseActivity: Deferred.succeed(releaseActivity, void 0).pipe(Effect.asVoid)
2189
- },
2190
- layer: TravelPlannerToolkit.toLayer({
2191
- search_flights: () => awaitRelease(flightStarted, releaseFlight, flight),
2192
- search_lodging: () => awaitRelease(lodgingStarted, releaseLodging, lodging),
2193
- search_activities: () => awaitRelease(activityStarted, releaseActivity, activities)
2194
- })
2195
- };
2196
- });
2197
- const FlightCatalogLayer = Layer.effect(FlightCatalog, Effect.gen(function* () {
2198
- const lifecycle = yield* CatalogLifecycle;
2199
- yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
2200
- return FlightCatalog.of({ search: (query) => query.origin === query.destination ? Effect.fail(FlightUnavailable.make({
2201
- query: `${query.origin}-${query.destination}`,
2202
- message: "Origin and destination must differ."
2203
- })) : Effect.succeed(flight) });
2204
- }));
2205
- const LodgingCatalogLayer = Layer.effect(LodgingCatalog, Effect.gen(function* () {
2206
- const lifecycle = yield* CatalogLifecycle;
2207
- yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
2208
- return LodgingCatalog.of({ search: (query) => query.nights < 1 ? Effect.fail(LodgingUnavailable.make({
2209
- query: query.destination,
2210
- message: "At least one night is required."
2211
- })) : Effect.succeed(lodging) });
2212
- }));
2213
- const ActivityCatalogLayer = Layer.effect(ActivityCatalog, Effect.gen(function* () {
2214
- const lifecycle = yield* CatalogLifecycle;
2215
- yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
2216
- return ActivityCatalog.of({ search: (query) => query.destination === "" ? Effect.fail(ActivityUnavailable.make({
2217
- query: query.destination,
2218
- message: "Destination is required."
2219
- })) : Effect.succeed(activities) });
2220
- }));
2221
- /** Stable supplier-side booking identity, minted deterministically from the idempotency key. */
2222
- const BookingRef = Schema.NonEmptyString.pipe(Schema.brand("@effect-agent/testing/travel-planner/BookingRef"));
2223
- /** The supplier desk operations the P5 booking Tools and Steps invoke. */
2224
- const SupplierOperation = Schema.Literals([
2225
- "book-flight",
2226
- "cancel-booking",
2227
- "reserve-flight",
2228
- "reserve-lodging",
2229
- "issue-confirmation"
2230
- ]);
2231
- /**
2232
- * One row of external supplier truth. The desk deduplicates by `idempotencyKey` — replaying a
2233
- * call with the same key returns this exact record without creating a second booking — which is
2234
- * precisely the honesty model of DUR-010: the framework never makes an external call
2235
- * exactly-once; the supplier's idempotency key does.
2236
- */
2237
- var SupplierBookingRecord = class extends Schema.Class("@effect-agent/testing/travel-planner/SupplierBookingRecord")({
2238
- bookingRef: BookingRef,
2239
- idempotencyKey: Schema.NonEmptyString,
2240
- operation: SupplierOperation,
2241
- detail: Schema.NonEmptyString,
2242
- status: Schema.Literals(["confirmed", "cancelled"])
2243
- }) {};
2244
- var SupplierUnavailable = class extends Schema.TaggedError()("SupplierUnavailable", { message: Schema.String }) {};
2245
- /** The desk-internal idempotency key of one cancellation: cancel is idempotent by bookingRef. */
2246
- const cancelBookingIdempotencyKey = (bookingRef) => `cancel-booking:${bookingRef}`;
2247
- /** The deterministic bookingRef the desk mints for one idempotency key. */
2248
- const supplierBookingRefFor = (idempotencyKey) => Schema.decodeSync(BookingRef)(`ref:${idempotencyKey}`);
2249
- /**
2250
- * The deterministic in-memory supplier: an idempotency-keyed booking store with per-key call
2251
- * counters and injectable crash windows.
2252
- *
2253
- * - `book`/`cancel` always count the call (at-least-once execution stays observable), then
2254
- * dedupe the external effect by idempotency key — the supplier-side contract the P5 Tools and
2255
- * Steps rely on.
2256
- * - `holdAfterWrite` arms a one-shot crash window: the next call with that key performs its
2257
- * supplier write, signals `held`, and never returns. Interrupting the Attempt at that point
2258
- * models "the external effect happened but no outcome was recorded" without any wall clock.
2259
- * - `bookings`/`lookup` expose external truth for the reconciler and for never-fabricate
2260
- * assertions.
2261
- */
2262
- var SupplierBookingDesk = class SupplierBookingDesk extends Context.Service()("@effect-agent/testing/travel-planner/SupplierBookingDesk") {
2263
- static layer = Layer.effect(this, Effect.gen(function* () {
2264
- const state = yield* Ref.make({
2265
- bookings: /* @__PURE__ */ new Map(),
2266
- counts: /* @__PURE__ */ new Map(),
2267
- holds: /* @__PURE__ */ new Map()
2268
- });
2269
- const enterHold = (hold) => Option.isSome(hold) ? Deferred.succeed(hold.value.held, void 0).pipe(Effect.andThen(Deferred.await(hold.value.release))) : Effect.void;
2270
- const book = (request) => Ref.modify(state, (current) => {
2271
- const counts = new Map(current.counts).set(request.idempotencyKey, (current.counts.get(request.idempotencyKey) ?? 0) + 1);
2272
- const existing = current.bookings.get(request.idempotencyKey);
2273
- const record = existing ?? SupplierBookingRecord.make({
2274
- bookingRef: supplierBookingRefFor(request.idempotencyKey),
2275
- idempotencyKey: request.idempotencyKey,
2276
- operation: request.operation,
2277
- detail: request.detail,
2278
- status: "confirmed"
2279
- });
2280
- const bookings = existing === void 0 ? new Map(current.bookings).set(request.idempotencyKey, record) : current.bookings;
2281
- const hold = Option.fromNullishOr(current.holds.get(request.idempotencyKey));
2282
- const holds = Option.isSome(hold) ? (() => {
2283
- const next = new Map(current.holds);
2284
- next.delete(request.idempotencyKey);
2285
- return next;
2286
- })() : current.holds;
2287
- return [{
2288
- record,
2289
- hold
2290
- }, {
2291
- bookings,
2292
- counts,
2293
- holds
2294
- }];
2295
- }).pipe(Effect.flatMap(({ hold, record }) => enterHold(hold).pipe(Effect.as(record))));
2296
- const cancel = (bookingRef) => Ref.modify(state, (current) => {
2297
- const key = cancelBookingIdempotencyKey(bookingRef);
2298
- const counts = new Map(current.counts).set(key, (current.counts.get(key) ?? 0) + 1);
2299
- const existingEntry = [...current.bookings.entries()].find(([, record]) => record.bookingRef === bookingRef);
2300
- if (existingEntry === void 0) return [{
2301
- record: Option.none(),
2302
- hold: Option.none()
2303
- }, {
2304
- ...current,
2305
- counts
2306
- }];
2307
- const [storeKey, existing] = existingEntry;
2308
- const cancelled = existing.status === "cancelled" ? existing : SupplierBookingRecord.make({
2309
- ...existing,
2310
- status: "cancelled"
2311
- });
2312
- const bookings = new Map(current.bookings).set(storeKey, cancelled);
2313
- const hold = Option.fromNullishOr(current.holds.get(key));
2314
- const holds = Option.isSome(hold) ? (() => {
2315
- const next = new Map(current.holds);
2316
- next.delete(key);
2317
- return next;
2318
- })() : current.holds;
2319
- return [{
2320
- record: Option.some(cancelled),
2321
- hold
2322
- }, {
2323
- bookings,
2324
- counts,
2325
- holds
2326
- }];
2327
- }).pipe(Effect.flatMap(({ hold, record }) => Option.isNone(record) ? Effect.fail(SupplierUnavailable.make({ message: `The supplier desk has no booking under ${bookingRef}.` })) : enterHold(hold).pipe(Effect.as(record.value))));
2328
- return SupplierBookingDesk.of({
2329
- book,
2330
- cancel,
2331
- lookup: (idempotencyKey) => Ref.get(state).pipe(Effect.map((current) => Option.fromNullishOr(current.bookings.get(idempotencyKey)))),
2332
- bookings: Ref.get(state).pipe(Effect.map((current) => [...current.bookings.values()])),
2333
- callCount: (idempotencyKey) => Ref.get(state).pipe(Effect.map((current) => current.counts.get(idempotencyKey) ?? 0)),
2334
- holdAfterWrite: (idempotencyKey) => Effect.gen(function* () {
2335
- const held = yield* Deferred.make();
2336
- const release = yield* Deferred.make();
2337
- yield* Ref.update(state, (current) => ({
2338
- ...current,
2339
- holds: new Map(current.holds).set(idempotencyKey, {
2340
- held,
2341
- release
2342
- })
2343
- }));
2344
- return {
2345
- held: Deferred.await(held),
2346
- release: Deferred.succeed(release, void 0).pipe(Effect.asVoid)
2347
- };
2348
- })
2349
- });
2350
- }));
2351
- };
2352
- const TravelGuidanceLayer = Layer.succeed(TravelGuidance, TravelGuidance.of({ instructions: (input) => Effect.succeed([
2353
- "You are the Effect Agent Travel Planner P1 interpreter fixture.",
2354
- `The user asked: ${input.request}`,
2355
- "Call search_flights, search_lodging, and search_activities exactly once in one Tool batch.",
2356
- "Then return only a JSON object of exactly this shape, no prose:",
2357
- "{\"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\"}]}",
2358
- "Use the Tool results verbatim; activity results may legitimately be an empty array.",
2359
- "This is read-only planning. Require review before any mutation."
2360
- ].join("\n")) }));
2361
- const DeterministicIdGeneratorLayer = Layer.effect(IdGenerator, Effect.gen(function* () {
2362
- const conversation = yield* Ref.make(0);
2363
- const run = yield* Ref.make(0);
2364
- const turn = yield* Ref.make(0);
2365
- return IdGenerator.of({
2366
- nextConversationId: Ref.updateAndGet(conversation, (n) => n + 1).pipe(Effect.map((n) => Schema.decodeSync(ConversationId)(`conversation-${n}`))),
2367
- nextRunId: Ref.updateAndGet(run, (n) => n + 1).pipe(Effect.map((n) => Schema.decodeSync(RunId)(`run-${n}`))),
2368
- nextTurnId: Ref.updateAndGet(turn, (n) => n + 1).pipe(Effect.map((n) => Schema.decodeSync(TurnId)(`turn-${n}`)))
2369
- });
2370
- }));
2371
- const TravelPlannerRuntimeLayer = Layer.mergeAll(TravelPlannerToolkitLayer, FlightCatalogLayer, LodgingCatalogLayer, ActivityCatalogLayer, TravelGuidanceLayer, DeterministicIdGeneratorLayer).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
2372
- //#endregion
2373
- //#region src/fixtures/docs-researcher/mcp.ts
2374
- /** Framework-side hard bounds one docs-researcher assembly requests. */
2375
- const docsMcpRequest = McpConnectionRequest.make({
2376
- serverId: "docs-content-mcp",
2377
- maxToolCount: 4,
2378
- maxToolDescriptionBytes: 256,
2379
- maxDiscoveryBytes: 16384,
2380
- connectTimeoutMillis: 1e3
2381
- });
2382
- const docsMcpIdentity = McpServerIdentity.make({
2383
- serverId: docsMcpRequest.serverId,
2384
- implementation: McpSchema.Implementation.make({
2385
- name: "docs-researcher-content-fixture",
2386
- version: "1.0.0"
2387
- })
2388
- });
2389
- const discoveredFetchDocument = McpSchema.Tool.make({
2390
- name: FetchDocument.name,
2391
- description: "Fetch one bounded research document by its identifier.",
2392
- inputSchema: Tool.getJsonSchema(FetchDocument)
2393
- });
2394
- const scriptedConnector = (tools) => Layer.succeed(McpConnector)({ connect: () => Effect.acquireRelease(Effect.succeed({
2395
- identity: docsMcpIdentity,
2396
- capabilities: McpSchema.ServerCapabilities.make({}),
2397
- tools,
2398
- toolkit: DocContentToolkit
2399
- }), () => Effect.void) });
2400
- /** The well-behaved scripted content server. */
2401
- const docsMcpConnectorLayer = scriptedConnector([discoveredFetchDocument]);
2402
- /** Serves a tool description exceeding `maxToolDescriptionBytes` (SEC-013 bound). */
2403
- const docsMcpOversizedConnectorLayer = scriptedConnector([McpSchema.Tool.make({
2404
- name: discoveredFetchDocument.name,
2405
- description: "x".repeat(1024),
2406
- inputSchema: discoveredFetchDocument.inputSchema
2407
- })]);
2408
- /** Serves a discovery schema that disagrees with the authored toolkit (drift fails closed). */
2409
- const docsMcpMismatchedConnectorLayer = scriptedConnector([McpSchema.Tool.make({
2410
- name: discoveredFetchDocument.name,
2411
- description: discoveredFetchDocument.description,
2412
- inputSchema: {
2413
- type: "object",
2414
- properties: { url: { type: "string" } }
2415
- }
2416
- })]);
2417
- const isJsonEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
2418
- /**
2419
- * Bind DISCOVERY to AUTHORING: `validateMcpDiscovery` (inside `connectMcp`)
2420
- * already proved the served discovery matches the connection's own Toolkit;
2421
- * this check additionally proves that Toolkit is the exact toolkit the
2422
- * doc-summarizer was AUTHORED against — same tool names, same derived JSON
2423
- * schemas — so a connector cannot substitute a look-alike toolkit. The
2424
- * docs-researcher harness runs it before any worker Binding registration and
2425
- * fails closed on any drift.
2426
- */
2427
- const assertDiscoveryMatchesAuthoredToolkit = Effect.fn("DocsResearcher.assertDiscoveryMatchesAuthoredToolkit")(function* (connection) {
2428
- const authored = Object.values(DocContentToolkit.tools).map((tool) => ({
2429
- name: tool.name,
2430
- inputSchema: Tool.getJsonSchema(tool)
2431
- })).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
2432
- const discovered = Object.values(connection.toolkit.tools).map((tool) => ({
2433
- name: tool.name,
2434
- inputSchema: Tool.getJsonSchema(tool)
2435
- })).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
2436
- if (!(authored.length === discovered.length && authored.every((tool, index) => tool.name === discovered[index]?.name && isJsonEqual(tool.inputSchema, discovered[index]?.inputSchema)))) return yield* McpToolkitMismatch.make({
2437
- serverId: connection.discovery.identity.serverId,
2438
- message: "The MCP-discovered toolkit does not match the doc-summarizer's authored content toolkit"
2439
- });
2440
- });
2441
- /** Round-trip guard for encoded discovery values persisted as fixture evidence. */
2442
- const DocsMcpDiscoveryEvidence = Schema.Struct({
2443
- serverId: Schema.NonEmptyString,
2444
- toolCount: Schema.Natural,
2445
- encodedBytes: Schema.Natural,
2446
- toolkitSchemaDigest: Schema.String
2447
- });
2448
- //#endregion
2449
- //#region src/fixtures/docs-researcher/harness.ts
2450
- const docsResearcherDeploymentId = Schema.decodeSync(DeploymentId)("docs-researcher-p7-deployment");
2451
- const docsResearcherProducerId = Schema.decodeSync(ProducerId)("docs-researcher-p7-producer");
2452
- const docsResearcherPrincipal = Schema.decodeSync(Principal)("docs-researcher-p7-principal");
2453
- const digestOf$2 = (pair) => Schema.decodeSync(Digest)(pair.repeat(32));
2454
- /** Redacted, deterministic coordinator definition digests for this fixture version. */
2455
- const docsCoordinatorDigests = DefinitionDigests.make({
2456
- agent: digestOf$2("40"),
2457
- model: digestOf$2("41"),
2458
- tools: digestOf$2("42")
2459
- });
2460
- /** The child registration digests — byte-equal to `docsSummarizerDigestStrings` (SUB-023). */
2461
- const docsSummarizerDigests = DefinitionDigests.make({
2462
- agent: digestOf$2("50"),
2463
- model: digestOf$2("51"),
2464
- tools: digestOf$2("52")
2465
- });
2466
- /** Durable admission options for one docs-researcher Submission on one mission lane. */
2467
- const docsResearcherSubmitOptions = (conversationId, idempotencyKey) => ({
2468
- conversationId,
2469
- principal: docsResearcherPrincipal,
2470
- idempotencyKey,
2471
- definitions: docsCoordinatorDigests
2472
- });
2473
- /** The structural submit slice of the coordinator Binding (`DurableAgentRuntime.submit`). */
2474
- const docsResearcherSubmitAgent = { definition: {
2475
- id: DocsResearcher.id,
2476
- input: DocsResearcher.input
2477
- } };
2478
- /** The deterministic delegation Tool Call identity for one document. */
2479
- const summarizeCallId = (documentId) => `summarize-${documentId}`;
2480
- /** The child's own scripted fetch Tool Call identity for one document. */
2481
- const fetchCallId = (documentId) => `fetch-${documentId}`;
2482
- const scriptedUsage$3 = {
2483
- inputTokens: { total: 96 },
2484
- outputTokens: { total: 64 }
2485
- };
2486
- const summaryDelegationParts = (documentIds) => [...documentIds.map((documentId) => ({
2487
- type: "tool-call",
2488
- id: summarizeCallId(documentId),
2489
- name: "delegate_document_summary",
2490
- params: { documentId },
2491
- providerExecuted: false
2492
- })), {
2493
- type: "finish",
2494
- reason: "tool-calls",
2495
- usage: scriptedUsage$3
2496
- }];
2497
- const digestParts = (documentIds) => [
2498
- {
2499
- type: "text-start",
2500
- id: "digest"
2501
- },
2502
- {
2503
- type: "text-delta",
2504
- id: "digest",
2505
- delta: JSON.stringify(Schema.encodeSync(ResearchDigest)(expectedResearchDigest(documentIds)))
2506
- },
2507
- {
2508
- type: "text-end",
2509
- id: "digest"
2510
- },
2511
- {
2512
- type: "finish",
2513
- reason: "stop",
2514
- usage: scriptedUsage$3
2515
- }
2516
- ];
2517
- const fetchParts = (documentId) => [{
2518
- type: "tool-call",
2519
- id: fetchCallId(documentId),
2520
- name: "fetch_document",
2521
- params: { documentId },
2522
- providerExecuted: false
2523
- }, {
2524
- type: "finish",
2525
- reason: "tool-calls",
2526
- usage: scriptedUsage$3
2527
- }];
2528
- const summaryParts = (documentId) => [
2529
- {
2530
- type: "text-start",
2531
- id: "document-summary"
2532
- },
2533
- {
2534
- type: "text-delta",
2535
- id: "document-summary",
2536
- delta: encodedDocumentSummary(documentId)
2537
- },
2538
- {
2539
- type: "text-end",
2540
- id: "document-summary"
2541
- },
2542
- {
2543
- type: "finish",
2544
- reason: "stop",
2545
- usage: scriptedUsage$3
2546
- }
2547
- ];
2548
- /**
2549
- * One prompt-aware scripted model with externally observable counters. A DN
2550
- * Attempt may resume on a fresh Layer build, so responses derive from the
2551
- * committed history in the prompt — never from an in-Layer turn counter.
2552
- */
2553
- const makeCountingModel = (name, decide) => Effect.gen(function* () {
2554
- const calls = yield* Ref.make(0);
2555
- const prompts = yield* Ref.make([]);
2556
- return {
2557
- model: Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
2558
- generateText: () => Effect.succeed([]),
2559
- streamText: (request) => Stream.unwrap(Effect.gen(function* () {
2560
- yield* Ref.update(calls, (value) => value + 1);
2561
- const promptJson = JSON.stringify(request.prompt);
2562
- yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
2563
- return Stream.fromIterable(yield* decide(promptJson));
2564
- }))
2565
- }))),
2566
- calls: Ref.get(calls),
2567
- prompts: Ref.get(prompts)
2568
- };
2569
- });
2570
- /**
2571
- * Build the docs-researcher harness. Order matters and is the point: the
2572
- * child's content toolkit is only registered as a worker Binding AFTER the
2573
- * MCP connector's bounded discovery validated the authored toolkit
2574
- * byte-for-byte (`connectMcp` + `assertDiscoveryMatchesAuthoredToolkit`), so
2575
- * "the tools the summarizer runs are the tools discovery served" is enforced
2576
- * at assembly, not assumed. Content-tool execution then flows through the
2577
- * counting `DocumentLibrary` — the scripted MCP server's content store.
2578
- */
2579
- const makeDocsResearcherHarness = (options) => Effect.gen(function* () {
2580
- const documentIds = options?.documentIds ?? researchCorpusDocumentIds;
2581
- const discovery = yield* Effect.scoped(Effect.gen(function* () {
2582
- const connection = yield* connectMcp(docsMcpRequest);
2583
- yield* assertDiscoveryMatchesAuthoredToolkit(connection);
2584
- return connection.discovery;
2585
- })).pipe(Effect.provide(docsMcpConnectorLayer));
2586
- const fetchCounts = yield* Ref.make(/* @__PURE__ */ new Map());
2587
- const libraryLayer = Layer.succeed(DocumentLibrary, DocumentLibrary.of({ fetch: (query) => Ref.update(fetchCounts, (current) => new Map(current).set(query.documentId, (current.get(query.documentId) ?? 0) + 1)).pipe(Effect.andThen(researchDocumentLookup(query))) }));
2588
- const childToolkitLayer = docContentToolkitLayer.pipe(Layer.provideMerge(libraryLayer));
2589
- const childModel = yield* makeCountingModel("doc-summarizer-p7", (promptJson) => Effect.suspend(() => {
2590
- const documentId = documentIds.find((candidate) => promptJson.includes(candidate));
2591
- if (documentId === void 0) return Effect.die(/* @__PURE__ */ new Error("The summarizer prompt names no corpus document"));
2592
- return Effect.succeed(promptJson.includes(fetchCallId(documentId)) ? summaryParts(documentId) : fetchParts(documentId));
2593
- }));
2594
- const childBinding = Agent.withModel(DocSummarizer, childModel.model);
2595
- const firstCallId = summarizeCallId(documentIds[0] ?? "durability-notes");
2596
- const parentModel = yield* makeCountingModel("docs-researcher-p7", (promptJson) => Effect.succeed(promptJson.includes(firstCallId) ? digestParts(documentIds) : summaryDelegationParts(documentIds)));
2597
- const parentBinding = Agent.withModel(DocsResearcher, parentModel.model);
2598
- const delegationLayer = docsSummaryHandlersLayer(childBinding).pipe(Layer.provide(Layer.mergeAll(childToolkitLayer, SubagentReservationsMemoryLive, DeterministicIdGeneratorLayer)));
2599
- return {
2600
- bindings: [yield* DurableWorkerBinding.make(parentBinding, docsCoordinatorDigests).pipe(Effect.provide(delegationLayer)), yield* DurableWorkerBinding.make(childBinding, docsSummarizerDigests).pipe(Effect.provide(childToolkitLayer))],
2601
- discovery,
2602
- parentModelCalls: parentModel.calls,
2603
- parentPrompts: parentModel.prompts,
2604
- childModelCalls: childModel.calls,
2605
- childPrompts: childModel.prompts,
2606
- fetchInvocations: (documentId) => Ref.get(fetchCounts).pipe(Effect.map((current) => current.get(documentId) ?? 0))
2607
- };
2608
- });
2609
- const encodeResearchDocument = Schema.encodeEffect(ResearchDocument);
2610
- /**
2611
- * The audit-surface preview of one fetched document: the raw document —
2612
- * secret marker and all — passes through the configured structural `Redactor`
2613
- * before anything may quote it outside the child Conversation (SEC-008,
2614
- * CAP-013). Tests assert the preview keeps shape but no scalar content.
2615
- */
2616
- const redactedDocumentPreview = Effect.fn("DocsResearcher.redactedDocumentPreview")(function* (documentId) {
2617
- const redactor = yield* Redactor;
2618
- const encoded = yield* encodeResearchDocument(researchDocumentFor(documentId)).pipe(Effect.orDie);
2619
- return yield* redactor.redact(encoded);
2620
- });
2621
- //#endregion
2622
- //#region src/fixtures/travel-planner/phase2.ts
2623
- var ItineraryHoldRequest = class extends Schema.Class("@effect-agent/testing/travel-planner/ItineraryHoldRequest")({
2624
- quoteId: QuoteId,
2625
- expiresInMinutes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(60))
2626
- }) {};
2627
- var ItineraryHold = class extends Schema.Class("@effect-agent/testing/travel-planner/ItineraryHold")({
2628
- holdId: Schema.NonEmptyString,
2629
- quoteId: QuoteId,
2630
- status: Schema.Literal("held")
2631
- }) {};
2632
- var ItineraryHoldUnavailable = class extends Schema.TaggedError()("ItineraryHoldUnavailable", {
2633
- quoteId: QuoteId,
2634
- message: Schema.String
2635
- }) {};
2636
- var ItineraryHoldGateway = class extends Context.Service()("@effect-agent/testing/travel-planner/ItineraryHoldGateway") {};
2637
- /**
2638
- * The first mutating Travel Planner Tool. Effect AI marks it as approval-gated
2639
- * so the engine must settle approval before acquiring a handler permit.
2640
- */
2641
- const HoldItinerary = Tool.make("hold_itinerary", {
2642
- parameters: ItineraryHoldRequest,
2643
- success: ItineraryHold,
2644
- failure: ItineraryHoldUnavailable,
2645
- failureMode: "error",
2646
- dependencies: [ItineraryHoldGateway],
2647
- needsApproval: true
2648
- });
2649
- const TravelPlannerPhase2Toolkit = Toolkit.make(SearchFlights, SearchLodging, SearchActivities, HoldItinerary);
2650
- const TravelPlannerPhase2ToolkitLayer = TravelPlannerPhase2Toolkit.toLayer({
2651
- search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(query)),
2652
- search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(query)),
2653
- search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(query)),
2654
- hold_itinerary: (request) => Effect.flatMap(ItineraryHoldGateway, (gateway) => gateway.hold(request))
2655
- });
2656
- const TravelPlannerPhase2 = Agent.define("travel-planner-phase-2", {
2657
- input: TripRequest,
2658
- output: TravelPlan,
2659
- instructions: (input) => Effect.flatMap(TravelGuidance, (guidance) => guidance.instructions(input)),
2660
- toolkit: TravelPlannerPhase2Toolkit,
2661
- policy: AgentPolicy.make({
2662
- maxTurns: 3,
2663
- maxToolCalls: 4,
2664
- maxDuration: "30 seconds",
2665
- toolConcurrency: 3,
2666
- tokenBudget: 2048
2667
- }),
2668
- description: "Build a review-only itinerary and require approval before creating a temporary hold.",
2669
- metadata: {
2670
- deploymentClass: "E",
2671
- phase: "P2"
2672
- }
2673
- });
2674
- //#endregion
2675
- //#region src/fixtures/travel-planner/scenarios.ts
2676
- const usage = {
2677
- inputTokens: { total: 128 },
2678
- outputTokens: { total: 96 }
2679
- };
2680
- const phase1Trip = Schema.decodeSync(TripRequest)({
2681
- request: "Plan a review-only London trip using the deterministic flight, lodging, and activity searches.",
2682
- origin: "SFO",
2683
- destination: "LHR",
2684
- departOn: "2026-09-14",
2685
- nights: 4,
2686
- travelers: 2,
2687
- budgetCents: 35e4,
2688
- currency: "USD"
2689
- });
2690
- /** Backward-compatible fixture alias while consumers transition to the P1 name. */
2691
- const phase0Trip = phase1Trip;
2692
- const expectedTravelPlan = Schema.decodeSync(TravelPlan)({ itineraries: [{
2693
- title: "Westward light, eastbound overnight",
2694
- route: "San Francisco → London",
2695
- dates: "14–19 September 2026",
2696
- flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
2697
- lodging: "Bloomsbury House · refundable studio · 4 nights",
2698
- activities: ["British Museum timed entry", "Thames evening walk"],
2699
- estimatedTotalCents: 284e3,
2700
- currency: "USD",
2701
- quoteId: "quote-sfo-lhr-001",
2702
- assumptions: ["Two travelers sharing one studio", "Quote is read-only availability, not a reservation"],
2703
- unresolvedConstraints: ["Traveler names and accessibility requests are intentionally omitted"],
2704
- nextAction: "review"
2705
- }] });
2706
- const phase1HappyPathTurns = [{
2707
- _tag: "Stream",
2708
- parts: [
2709
- {
2710
- type: "tool-call",
2711
- id: "flight-call-1",
2712
- name: "search_flights",
2713
- params: {
2714
- origin: "SFO",
2715
- destination: "LHR",
2716
- departOn: "2026-09-14",
2717
- travelers: 2
2718
- }
2719
- },
2720
- {
2721
- type: "tool-call",
2722
- id: "lodging-call-1",
2723
- name: "search_lodging",
2724
- params: {
2725
- destination: "LHR",
2726
- departOn: "2026-09-14",
2727
- nights: 4,
2728
- travelers: 2
2729
- }
2730
- },
2731
- {
2732
- type: "tool-call",
2733
- id: "activity-call-1",
2734
- name: "search_activities",
2735
- params: {
2736
- destination: "LHR",
2737
- departOn: "2026-09-14",
2738
- nights: 4,
2739
- travelers: 2
2740
- }
2741
- },
2742
- {
2743
- type: "finish",
2744
- reason: "tool-calls",
2745
- usage
2746
- }
2747
- ],
2748
- termination: { _tag: "Complete" }
2749
- }, {
2750
- _tag: "Stream",
2751
- parts: [
2752
- {
2753
- type: "text-start",
2754
- id: "itinerary-json"
2755
- },
2756
- {
2757
- type: "text-delta",
2758
- id: "itinerary-json",
2759
- delta: JSON.stringify(Schema.encodeSync(TravelPlan)(expectedTravelPlan))
2760
- },
2761
- {
2762
- type: "text-end",
2763
- id: "itinerary-json"
2764
- },
2765
- {
2766
- type: "finish",
2767
- reason: "stop",
2768
- usage
2769
- }
2770
- ],
2771
- termination: { _tag: "Complete" }
2772
- }];
2773
- const phase0HappyPathTurns = phase1HappyPathTurns;
2774
- //#endregion
2775
- //#region src/fixtures/travel-planner/phase3.ts
2776
- /**
2777
- * The Phase 3 profile persists Conversation history but deliberately does not
2778
- * claim durable admission or recovery of accepted work.
2779
- */
2780
- var TravelPlannerPersistenceProfile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerPersistenceProfile")({
2781
- deploymentClass: Schema.Literal("P"),
2782
- durableAcceptedWork: Schema.Literal(false),
2783
- canonicalSchemaVersion: Schema.Literal(1)
2784
- }) {};
2785
- const phase3TravelPlannerProfile = TravelPlannerPersistenceProfile.make({
2786
- deploymentClass: "P",
2787
- durableAcceptedWork: false,
2788
- canonicalSchemaVersion: 1
2789
- });
2790
- const phase3TravelPlannerConversationId = Schema.decodeSync(ConversationId)("travel-planner-p3-conversation");
2791
- const phase3TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p3-producer");
2792
- const phase3TravelPlannerRunId = Schema.decodeSync(RunId)("travel-planner-p3-run");
2793
- const deploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p3-scripted");
2794
- const agentId = Schema.decodeSync(AgentId)("travel-planner");
2795
- const submissionId = Schema.decodeSync(SubmissionId)("travel-planner-p3-submission");
2796
- const digest$2 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
2797
- /** Redacted, deterministic definition identities for the current fixture version. */
2798
- const phase3TravelPlannerDefinitionDigests = DefinitionDigests.make({
2799
- agent: digest$2("a"),
2800
- model: digest$2("b"),
2801
- tools: digest$2("c")
2802
- });
2803
- const tripInput = Schema.encodeSync(TripRequest)(phase1Trip);
2804
- const travelPlanOutput = Schema.encodeSync(TravelPlan)(expectedTravelPlan);
2805
- const record = (recordId, createdAt, payload) => Schema.decodeUnknownSync(RecordEnvelope)({
2806
- recordId: Schema.decodeSync(RecordId)(recordId),
2807
- family: "conversation",
2808
- schemaVersion: 1,
2809
- createdAt,
2810
- deploymentId,
2811
- payload
2812
- });
2813
- /**
2814
- * The first atomic append establishes the Conversation and records its input.
2815
- * Its encoded value is the redacted current-version persistence fixture.
2816
- */
2817
- const phase3TravelPlannerInitialBatch = CanonicalBatch.make({
2818
- batchId: Schema.decodeSync(BatchId)("travel-planner-p3-initial"),
2819
- producerId: phase3TravelPlannerProducerId,
2820
- records: [record("travel-planner-p3-created", "2026-09-01T00:00:00.000Z", {
2821
- _tag: "ConversationCreated",
2822
- agentId,
2823
- definitions: phase3TravelPlannerDefinitionDigests
2824
- }), record("travel-planner-p3-input", "2026-09-01T00:00:01.000Z", {
2825
- _tag: "UserInputRecorded",
2826
- submissionId,
2827
- kind: "user",
2828
- runId: phase3TravelPlannerRunId,
2829
- input: tripInput
2830
- })]
2831
- });
2832
- /** The second append records the Schema-decoded itinerary and terminal Run result. */
2833
- const phase3TravelPlannerCompletionBatch = CanonicalBatch.make({
2834
- batchId: Schema.decodeSync(BatchId)("travel-planner-p3-completion"),
2835
- producerId: phase3TravelPlannerProducerId,
2836
- records: [record("travel-planner-p3-model", "2026-09-01T00:00:02.000Z", {
2837
- _tag: "ModelCompleted",
2838
- runId: phase3TravelPlannerRunId,
2839
- output: travelPlanOutput
2840
- }), record("travel-planner-p3-completed", "2026-09-01T00:00:03.000Z", {
2841
- _tag: "RunCompleted",
2842
- runId: phase3TravelPlannerRunId,
2843
- output: travelPlanOutput
2844
- })]
2845
- });
2846
- const phase3TravelPlannerBatches = [phase3TravelPlannerInitialBatch, phase3TravelPlannerCompletionBatch];
2847
- /** Portable current-version fixture; it contains no passenger identity or credentials. */
2848
- const phase3TravelPlannerEncodedFixture = Schema.encodeSync(Schema.Array(CanonicalBatch))(phase3TravelPlannerBatches);
2849
- var TravelPlannerProjectionError = class extends Schema.TaggedError()("TravelPlannerProjectionError", { message: Schema.String }) {};
2850
- /** Decode the itinerary projection rebuilt from canonical model-completion records. */
2851
- const travelPlanFromProjection = (projection) => {
2852
- const output = projection.modelOutputs.at(-1);
2853
- if (output === void 0) return Effect.fail(TravelPlannerProjectionError.make({ message: "The canonical projection has no completed Travel Planner model output." }));
2854
- return Schema.decodeUnknownEffect(TravelPlan)(output).pipe(Effect.mapError((error) => TravelPlannerProjectionError.make({ message: error.message })));
2855
- };
2856
- /** Build a disposable checkpoint bound to a validated canonical prefix. */
2857
- const makePhase3TravelPlannerCheckpoint = (projection) => Schema.decodeSync(ConversationCheckpoint)({
2858
- schemaVersion: 1,
2859
- conversationId: projection.conversationId,
2860
- throughSequence: projection.throughSequence,
2861
- tailDigest: projection.tailDigest,
2862
- engineVersion: "phase-3-test-runtime",
2863
- agentDefinitionDigest: phase3TravelPlannerDefinitionDigests.agent,
2864
- modelDigest: phase3TravelPlannerDefinitionDigests.model,
2865
- toolDigest: phase3TravelPlannerDefinitionDigests.tools,
2866
- state: Schema.encodeSync(ConversationProjection)(projection),
2867
- createdAt: "2026-09-01T00:00:04.000Z"
2868
- });
2869
- //#endregion
2870
- //#region src/scripted-model.ts
2871
- const ScriptedPartMetadata = Schema.Record(Schema.String, Schema.NullOr(Schema.Json));
2872
- const ScriptedPartBase = { metadata: Schema.optionalKey(ScriptedPartMetadata) };
2873
- const ScriptedToolCallPart = Schema.Struct({
2874
- ...ScriptedPartBase,
2875
- type: Schema.Literal("tool-call"),
2876
- id: Schema.String,
2877
- name: Schema.String,
2878
- params: Schema.Unknown,
2879
- providerExecuted: Schema.optionalKey(Schema.Boolean)
2880
- });
2881
- const ScriptedToolResultPart = Schema.Struct({
2882
- ...ScriptedPartBase,
2883
- type: Schema.Literal("tool-result"),
2884
- id: Schema.String,
2885
- name: Schema.String,
2886
- result: Schema.Unknown,
2887
- isFailure: Schema.Boolean,
2888
- providerExecuted: Schema.optionalKey(Schema.Boolean),
2889
- preliminary: Schema.optionalKey(Schema.Boolean)
2890
- });
2891
- /**
2892
- * Schema for encoded, non-streaming Effect AI response parts.
2893
- *
2894
- * Generic Tool payloads remain explicitly unknown here. `LanguageModel.make`
2895
- * performs the toolkit-specific decode when the scripted response is consumed.
2896
- */
2897
- const ScriptedGeneratePart = Schema.Union([
2898
- Schema.toEncoded(Response.TextPart),
2899
- Schema.toEncoded(Response.ReasoningPart),
2900
- Schema.toEncoded(Response.ReasoningDeltaPart),
2901
- Schema.toEncoded(Response.ReasoningEndPart),
2902
- ScriptedToolCallPart,
2903
- ScriptedToolResultPart,
2904
- Schema.toEncoded(Response.ToolApprovalRequestPart),
2905
- Schema.toEncoded(Response.FilePart),
2906
- Schema.toEncoded(Response.DocumentSourcePart),
2907
- Schema.toEncoded(Response.UrlSourcePart),
2908
- Schema.toEncoded(Response.ResponseMetadataPart),
2909
- Schema.toEncoded(Response.FinishPart)
2910
- ]).annotate({ identifier: "ScriptedGeneratePart" });
2911
- /**
2912
- * Schema for encoded Effect AI streaming response parts.
2913
- */
2914
- const ScriptedStreamPart = Schema.Union([
2915
- Schema.toEncoded(Response.TextStartPart),
2916
- Schema.toEncoded(Response.TextDeltaPart),
2917
- Schema.toEncoded(Response.TextEndPart),
2918
- Schema.toEncoded(Response.ReasoningStartPart),
2919
- Schema.toEncoded(Response.ReasoningDeltaPart),
2920
- Schema.toEncoded(Response.ReasoningEndPart),
2921
- Schema.toEncoded(Response.ToolParamsStartPart),
2922
- Schema.toEncoded(Response.ToolParamsDeltaPart),
2923
- Schema.toEncoded(Response.ToolParamsEndPart),
2924
- ScriptedToolCallPart,
2925
- ScriptedToolResultPart,
2926
- Schema.toEncoded(Response.ToolApprovalRequestPart),
2927
- Schema.toEncoded(Response.FilePart),
2928
- Schema.toEncoded(Response.DocumentSourcePart),
2929
- Schema.toEncoded(Response.UrlSourcePart),
2930
- Schema.toEncoded(Response.ResponseMetadataPart),
2931
- Schema.toEncoded(Response.FinishPart),
2932
- Schema.toEncoded(Response.ErrorPart)
2933
- ]).annotate({ identifier: "ScriptedStreamPart" });
2934
- /** Controls whether a scripted stream completes, fails, or waits for interruption. */
2935
- const ScriptedStreamTermination = Schema.Union([
2936
- Schema.TaggedStruct("Complete", {}),
2937
- Schema.TaggedStruct("Fail", { description: Schema.String }),
2938
- Schema.TaggedStruct("Hang", {})
2939
- ]);
2940
- /** One non-streaming invocation and the encoded response parts it returns. */
2941
- const ScriptedGenerateTurn = Schema.TaggedStruct("Generate", { parts: Schema.Array(ScriptedGeneratePart) });
2942
- /** One streaming invocation with its encoded parts and terminal behavior. */
2943
- const ScriptedStreamTurn = Schema.TaggedStruct("Stream", {
2944
- parts: Schema.Array(ScriptedStreamPart),
2945
- termination: ScriptedStreamTermination
2946
- });
2947
- /**
2948
- * Serializable grammar for one finite scripted provider invocation.
2949
- */
2950
- const ScriptedTurn = Schema.Union([ScriptedGenerateTurn, ScriptedStreamTurn]);
2951
- const scriptedError = (method, description) => AiError.AiError.make({
2952
- module: "@effect-agent/testing/ScriptedModel",
2953
- method,
2954
- reason: AiError.UnknownError.make({ description })
2955
- });
2956
- const runAssertion = Effect.fn("ScriptedModel.runAssertion")((assertion, request) => {
2957
- if (assertion === void 0) return Effect.void;
2958
- return Effect.suspend(() => {
2959
- const result = assertion(request);
2960
- return Effect.isEffect(result) ? result : Effect.void;
2961
- });
2962
- });
2963
- const takeTurn = Effect.fn("ScriptedModel.takeTurn")((state, kind, options) => Ref.modify(state, (current) => {
2964
- const turn = current.remaining[0];
2965
- if (turn === void 0) return [void 0, {
2966
- ...current,
2967
- requests: [...current.requests, {
2968
- kind,
2969
- options
2970
- }]
2971
- }];
2972
- return [turn, {
2973
- remaining: current.remaining.slice(1),
2974
- requests: [...current.requests, {
2975
- kind,
2976
- options
2977
- }]
2978
- }];
2979
- }).pipe(Effect.flatMap((turn) => turn === void 0 ? Effect.fail(scriptedError(kind, `Script exhausted before the ${kind} request`)) : Effect.succeed(turn))));
2980
- const requireGenerateTurn = (turn) => turn._tag === "Generate" ? Effect.succeed(turn) : Effect.fail(scriptedError("generate", `Expected a Generate turn but found ${turn._tag}`));
2981
- const requireStreamTurn = (turn) => turn._tag === "Stream" ? Effect.succeed(turn) : Effect.fail(scriptedError("stream", `Expected a Stream turn but found ${turn._tag}`));
2982
- const streamForTurn = (turn) => {
2983
- let stream = Stream.fromIterable(turn.parts);
2984
- switch (turn.termination._tag) {
2985
- case "Complete": break;
2986
- case "Fail":
2987
- stream = stream.pipe(Stream.concat(Stream.fail(scriptedError("stream", turn.termination.description))));
2988
- break;
2989
- case "Hang":
2990
- stream = stream.pipe(Stream.concat(Stream.never));
2991
- break;
2992
- }
2993
- if (turn.onStreamStart !== void 0) stream = Stream.fromEffectDrain(turn.onStreamStart).pipe(Stream.concat(stream));
2994
- if (turn.onStreamFinalize !== void 0) stream = stream.pipe(Stream.ensuring(turn.onStreamFinalize));
2995
- return stream;
2996
- };
2997
- /** Inspection service for a deterministic LanguageModel backed by finite scripted turns. */
2998
- var ScriptedModel = class ScriptedModel extends Context.Service()("@effect-agent/testing/ScriptedModel") {
2999
- /**
3000
- * Provides the native Effect AI `LanguageModel` and this inspection service.
3001
- * Supplying the extra inspection service does not add it to model-call
3002
- * requirements. Each model invocation consumes one turn before assertion and
3003
- * turn-kind validation.
3004
- */
3005
- static layer(turns) {
3006
- return Layer.effectContext(Effect.gen(function* () {
3007
- const state = yield* Ref.make({
3008
- remaining: [...turns],
3009
- requests: []
3010
- });
3011
- const languageModel = yield* LanguageModel.make({
3012
- generateText: (options) => Effect.gen(function* () {
3013
- const turn = yield* takeTurn(state, "generate", options);
3014
- yield* runAssertion(turn.assertRequest, options);
3015
- return [...(yield* requireGenerateTurn(turn)).parts];
3016
- }),
3017
- streamText: (options) => Stream.unwrap(Effect.gen(function* () {
3018
- const turn = yield* takeTurn(state, "stream", options);
3019
- yield* runAssertion(turn.assertRequest, options);
3020
- const streamTurn = yield* requireStreamTurn(turn);
3021
- return streamForTurn(streamTurn);
3022
- }))
3023
- });
3024
- const inspection = ScriptedModel.of({
3025
- requests: Ref.get(state).pipe(Effect.map((current) => current.requests)),
3026
- remaining: Ref.get(state).pipe(Effect.map((current) => current.remaining.length)),
3027
- assertExhausted: Ref.get(state).pipe(Effect.flatMap((current) => current.remaining.length === 0 ? Effect.void : Effect.fail(scriptedError("assertExhausted", `${current.remaining.length} scripted turn(s) remain`))))
3028
- });
3029
- return Context.make(LanguageModel.LanguageModel, languageModel).pipe(Context.add(ScriptedModel, inspection));
3030
- }));
3031
- }
3032
- };
3033
- //#endregion
3034
- //#region src/fixtures/travel-planner/phase4.ts
3035
- /**
3036
- * The Phase 4 profile claims durable accepted work on the Node/SQLite runtime (deployment class
3037
- * DN): once `submit` returns a Receipt, the Submission settles exactly once even across process
3038
- * loss. The claim is limited to safe-to-repeat toolkits (D6): supplier booking is explicitly NOT
3039
- * claimed safely replayable — replay-safe external mutation is P5 (Durable Tools) scope.
3040
- */
3041
- var TravelPlannerDurabilityProfile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerDurabilityProfile")({
3042
- deploymentClass: Schema.Literal("DN"),
3043
- durableAcceptedWork: Schema.Literal(true),
3044
- canonicalSchemaVersion: Schema.Literal(1),
3045
- /** Supplier booking replay safety is P5 (Durable Tools) scope; DN does not claim it. */
3046
- supplierBookingReplaySafe: Schema.Literal(false)
3047
- }) {};
3048
- const phase4TravelPlannerProfile = TravelPlannerDurabilityProfile.make({
3049
- deploymentClass: "DN",
3050
- durableAcceptedWork: true,
3051
- canonicalSchemaVersion: 1,
3052
- supplierBookingReplaySafe: false
3053
- });
3054
- const phase4TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p4-deployment");
3055
- const phase4TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p4-producer");
3056
- const phase4TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-p4-principal");
3057
- const digest$1 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
3058
- /** Redacted, deterministic definition identities for the current fixture version. */
3059
- const phase4TravelPlannerDefinitionDigests = DefinitionDigests.make({
3060
- agent: digest$1("d"),
3061
- model: digest$1("e"),
3062
- tools: digest$1("f")
3063
- });
3064
- /** Durable admission options for one Travel Planner Submission on one trip lane. */
3065
- const phase4TravelPlannerSubmitOptions = (conversationId, idempotencyKey) => ({
3066
- conversationId,
3067
- principal: phase4TravelPlannerPrincipal,
3068
- idempotencyKey,
3069
- definitions: phase4TravelPlannerDefinitionDigests
3070
- });
3071
- /**
3072
- * The P4 read-only search Tools, calling the same deterministic catalogs as the P1 toolkit.
3073
- *
3074
- * The plain-Struct parameter shape is a historical remnant of the P4 carry-in workaround: since
3075
- * the P5 engine fix, official history carries Schema-ENCODED Tool-call parameters, so class-typed
3076
- * parameter codecs persist canonically too — the Structs simply need no change here. The
3077
- * `ToolExecutionClass` `readonly` annotation is the deliberate P5 migration (plan §4.3): these
3078
- * Tools perform no external mutation, so a crash between start and settlement is a free re-run
3079
- * and they never enter the prepared/settled uncertainty protocol — keeping the P4 canonical
3080
- * history byte-stable (an unannotated Tool fails closed to `uncertain`).
3081
- */
3082
- const DurableSearchFlights = Tool.make("search_flights", {
3083
- parameters: Schema.Struct(FlightQuery.fields),
3084
- success: FlightOption,
3085
- failure: FlightUnavailable,
3086
- failureMode: "error",
3087
- dependencies: [FlightCatalog]
3088
- }).annotate(ToolExecutionClass, "readonly");
3089
- const DurableSearchLodging = Tool.make("search_lodging", {
3090
- parameters: Schema.Struct(LodgingQuery.fields),
3091
- success: LodgingOption,
3092
- failure: LodgingUnavailable,
3093
- failureMode: "error",
3094
- dependencies: [LodgingCatalog]
3095
- }).annotate(ToolExecutionClass, "readonly");
3096
- const DurableSearchActivities = Tool.make("search_activities", {
3097
- parameters: Schema.Struct(ActivityQuery.fields),
3098
- success: ActivitySearchResult,
3099
- failure: ActivityUnavailable,
3100
- failureMode: "error",
3101
- dependencies: [ActivityCatalog]
3102
- }).annotate(ToolExecutionClass, "readonly");
3103
- const TravelPlannerPhase4Toolkit = Toolkit.make(DurableSearchFlights, DurableSearchLodging, DurableSearchActivities);
3104
- const TravelPlannerPhase4ToolkitLayer = TravelPlannerPhase4Toolkit.toLayer({
3105
- search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(FlightQuery.make(query))),
3106
- search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(LodgingQuery.make(query))),
3107
- search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(ActivityQuery.make(query)))
3108
- });
3109
- /**
3110
- * The cumulative Travel Planner, Phase 4: the P1 planning behavior on the durable Node/SQLite
3111
- * runtime. The searches are read-only and safe to repeat across Attempts (D6); supplier booking
3112
- * is deliberately absent because DN does NOT claim replay-safe external mutation (P5 scope).
3113
- */
3114
- const TravelPlannerPhase4 = Agent.define("travel-planner-phase-4", {
3115
- input: TripRequest,
3116
- output: TravelPlan,
3117
- instructions: (input) => Effect.flatMap(TravelGuidance, (guidance) => guidance.instructions(input)),
3118
- toolkit: TravelPlannerPhase4Toolkit,
3119
- policy: AgentPolicy.make({
3120
- maxTurns: 2,
3121
- maxToolCalls: 3,
3122
- maxDuration: "30 seconds",
3123
- toolConcurrency: 3
3124
- }),
3125
- description: "Durably plan one review-only itinerary from safe-to-repeat deterministic searches; supplier booking is not claimed safely replayable.",
3126
- metadata: {
3127
- deploymentClass: "DN",
3128
- phase: "P4"
3129
- }
3130
- });
3131
- /**
3132
- * The P4 Agent Binding: the durable Travel Planner definition bound to a finite scripted model.
3133
- * The scripted Layer is rebuilt per Run, so every Run of one Binding replays the same
3134
- * deterministic script.
3135
- */
3136
- const makePhase4TravelPlannerAgent = (turns = phase1HappyPathTurns) => Agent.withModel(TravelPlannerPhase4, Model.make("scripted", "travel-planner-phase-4", ScriptedModel.layer(turns)));
3137
- /**
3138
- * Everything a durable worker needs beyond the runtime stack, reusing the deterministic P1
3139
- * travel-service Layers. The durable coordinator supplies its own deterministic `IdGenerator`,
3140
- * so this Layer deliberately provides none.
3141
- */
3142
- const phase4TravelPlannerWorkerLayer = Layer.mergeAll(TravelPlannerPhase4ToolkitLayer, FlightCatalogLayer, LodgingCatalogLayer, ActivityCatalogLayer, TravelGuidanceLayer).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
3143
- var TravelPlannerDurableEvidenceError = class extends Schema.TaggedError()("TravelPlannerDurableEvidenceError", { message: Schema.String }) {};
3144
- /**
3145
- * Decode the completed itinerary from the canonical `SubmissionSettled` record. Canonical history
3146
- * is the outcome authority (DUR-015): the settled result — not any ledger cache — must decode
3147
- * through the trip output schema.
3148
- */
3149
- const travelPlanFromDurableSettlement = Effect.fn("TravelPlannerPhase4.travelPlanFromDurableSettlement")(function* (records) {
3150
- const settled = records.flatMap((envelope) => envelope.record.payload._tag === "SubmissionSettled" ? [envelope.record.payload] : []).at(0);
3151
- if (settled === void 0) return yield* TravelPlannerDurableEvidenceError.make({ message: "The canonical Conversation Log has no SubmissionSettled record." });
3152
- if (settled.outcome !== "completed" || settled.result === void 0) return yield* TravelPlannerDurableEvidenceError.make({ message: `The Submission settled ${settled.outcome} without a completed itinerary result.` });
3153
- 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}` })));
3154
- });
3155
- const encodeEvidence = Schema.encodeEffect(Schema.Array(CanonicalRecordEnvelope));
3156
- const decodeComparableJson$1 = Schema.decodeUnknownEffect(Schema.Json);
3157
- /**
3158
- * Project canonical evidence into a Submission-identity-independent comparable form: batch
3159
- * identity, canonical sequence, and the full encoded record, with the ledger-minted
3160
- * `submissionId`/`receiptId` (and every identity derived from them: run, turn, batch, record,
3161
- * and settlement ids) replaced by stable placeholders. Two Conversations whose normalized
3162
- * evidence is equal took byte-equivalent canonical histories, so restart-equivalence can compare
3163
- * a recovered run against an uninterrupted control run on a separate database.
3164
- */
3165
- const normalizeDurableTravelPlannerEvidence = Effect.fn("TravelPlannerPhase4.normalizeDurableTravelPlannerEvidence")(function* (records, receipt) {
3166
- const comparable = (yield* encodeEvidence(records).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Canonical evidence failed to encode: ${error.message}` })))).map((envelope) => ({
3167
- batchId: envelope.batchId,
3168
- sequence: envelope.sequence,
3169
- record: envelope.record
3170
- }));
3171
- const substituted = JSON.parse(JSON.stringify(comparable).replaceAll(receipt.submissionId, "{submissionId}").replaceAll(receipt.receiptId, "{receiptId}"));
3172
- return yield* decodeComparableJson$1(substituted).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Normalized evidence is not comparable JSON: ${error.message}` })));
3173
- });
3174
- //#endregion
3175
- //#region src/fixtures/travel-planner/phase5.ts
3176
- /**
3177
- * The Phase 5 profile extends the P4 `DN` claim to consequential supplier mutation: booking
3178
- * Tools enter the prepared/settled uncertainty protocol, unresolved external effects stop at
3179
- * Unknown Outcomes instead of replaying, Durable Steps replay recorded results, and queued
3180
- * traveler input joins the active Run. Exactly-once EXTERNAL execution is still — deliberately —
3181
- * not claimed (DUR-003): the supplier's own idempotency keys are what dedupe repeats.
3182
- */
3183
- var TravelPlannerBookingProfile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerBookingProfile")({
3184
- deploymentClass: Schema.Literal("DN"),
3185
- durableAcceptedWork: Schema.Literal(true),
3186
- canonicalSchemaVersion: Schema.Literal(1),
3187
- /** P5: supplier mutations get prepared/settled records, Unknown Outcomes, and reconciliation. */
3188
- supplierBookingUncertaintyProtocol: Schema.Literal(true),
3189
- /** P5: Durable Steps are exactly-once-RECORDED; their side effects stay at-least-once. */
3190
- durableStepsRecorded: Schema.Literal(true),
3191
- /** Never claimed at any phase (DUR-003). */
3192
- exactlyOnceExternalEffects: Schema.Literal(false)
3193
- }) {};
3194
- const phase5TravelPlannerProfile = TravelPlannerBookingProfile.make({
3195
- deploymentClass: "DN",
3196
- durableAcceptedWork: true,
3197
- canonicalSchemaVersion: 1,
3198
- supplierBookingUncertaintyProtocol: true,
3199
- durableStepsRecorded: true,
3200
- exactlyOnceExternalEffects: false
3201
- });
3202
- const phase5TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p5-deployment");
3203
- const phase5TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-p5-producer");
3204
- const phase5TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-p5-principal");
3205
- const digest = (character) => Schema.decodeSync(Digest)(character.repeat(64));
3206
- /** Redacted, deterministic definition identities for the current fixture version. */
3207
- const phase5TravelPlannerDefinitionDigests = DefinitionDigests.make({
3208
- agent: digest("1"),
3209
- model: digest("2"),
3210
- tools: digest("3")
3211
- });
3212
- /** Durable admission options for one Travel Planner Submission on one trip lane. */
3213
- const phase5TravelPlannerSubmitOptions = (conversationId, idempotencyKey) => ({
3214
- conversationId,
3215
- principal: phase5TravelPlannerPrincipal,
3216
- idempotencyKey,
3217
- definitions: phase5TravelPlannerDefinitionDigests
3218
- });
3219
- const TravelerRef = Schema.NonEmptyString.pipe(Schema.brand("@effect-agent/testing/travel-planner/TravelerRef"));
3220
- /**
3221
- * Class-shaped booking parameters with branded fields: since the P5 engine fix, official history
3222
- * carries Schema-ENCODED Tool-call parameters, so class-typed parameter codecs persist
3223
- * canonically end-to-end (the P4 Struct workaround is gone for new Tools).
3224
- */
3225
- var FlightBookingRequest = class extends Schema.Class("FlightBookingRequest")({
3226
- quoteId: QuoteId,
3227
- travelerRef: TravelerRef,
3228
- departOn: Schema.String
3229
- }) {};
3230
- var SupplierBookingConfirmation = class extends Schema.Class("SupplierBookingConfirmation")({
3231
- bookingRef: BookingRef,
3232
- status: Schema.Literal("confirmed"),
3233
- detail: Schema.String
3234
- }) {};
3235
- var CancelBookingRequest = class extends Schema.Class("CancelBookingRequest")({
3236
- bookingRef: BookingRef,
3237
- travelerRef: TravelerRef
3238
- }) {};
3239
- var CancellationConfirmation = class extends Schema.Class("CancellationConfirmation")({
3240
- bookingRef: BookingRef,
3241
- status: Schema.Literal("cancelled")
3242
- }) {};
3243
- var ItineraryBookingRequest = class extends Schema.Class("ItineraryBookingRequest")({
3244
- quoteId: QuoteId,
3245
- destination: AirportCode,
3246
- nights: Schema.Int.check(Schema.isGreaterThan(0)),
3247
- travelerRef: TravelerRef
3248
- }) {};
3249
- var ItineraryConfirmation = class extends Schema.Class("ItineraryConfirmation")({
3250
- flightBookingRef: BookingRef,
3251
- lodgingBookingRef: BookingRef,
3252
- confirmationCode: Schema.String
3253
- }) {};
3254
- /** The P5 Run output: a booked (or explicitly not-booked) trip report. */
3255
- var TravelBookingReport = class extends Schema.Class("TravelBookingReport")({
3256
- summary: Schema.String,
3257
- bookingRefs: Schema.Array(BookingRef)
3258
- }) {};
3259
- /**
3260
- * Supplier idempotency-key derivations. The handler owns key derivation (the `idempotent` and
3261
- * `uncertain` execution classes carry no key), and the reconciler MUST use the same derivations
3262
- * to query external truth — both sides are exported so they cannot drift.
3263
- */
3264
- const bookFlightIdempotencyKey = (toolCallId) => `book-flight:${toolCallId}`;
3265
- const itineraryStepIdempotencyKey = (toolCallId, stepName) => `${toolCallId}:${stepName}`;
3266
- /**
3267
- * Approval-gated supplier booking, explicitly `uncertain`: a crash after the call may have
3268
- * mutated supplier state without a recorded outcome, so recovery must reconcile or stop at an
3269
- * Unknown Outcome — never replay automatically (DUR-009, ADR-0004).
3270
- */
3271
- const BookFlight = Tool.make("book_flight", {
3272
- parameters: FlightBookingRequest,
3273
- success: SupplierBookingConfirmation,
3274
- failure: SupplierUnavailable,
3275
- failureMode: "error",
3276
- needsApproval: true,
3277
- dependencies: [SupplierBookingDesk]
3278
- }).annotate(ToolExecutionClass, "uncertain");
3279
- /**
3280
- * Approval-gated cancellation, annotated `idempotent`: the DECLARED external contract is that
3281
- * cancellation is idempotent by `bookingRef` (the supplier desk enforces it), so recovery may
3282
- * re-execute a prepared-but-unsettled cancel without reconciliation proof. Repeats stay
3283
- * observable in the supplier call counters — the annotation never claims exactly-once execution.
3284
- */
3285
- const CancelBooking = Tool.make("cancel_booking", {
3286
- parameters: CancelBookingRequest,
3287
- success: CancellationConfirmation,
3288
- failure: SupplierUnavailable,
3289
- failureMode: "error",
3290
- needsApproval: true,
3291
- dependencies: [SupplierBookingDesk]
3292
- }).annotate(ToolExecutionClass, "idempotent");
3293
- /**
3294
- * The Durable Tool: declaring `DurableStep` in `dependencies` is what makes it durable
3295
- * (CONTEXT.md). Its handler divides supplier mutation into three named Steps — `reserve-flight`,
3296
- * `reserve-lodging`, `issue-confirmation` — each deriving its supplier idempotency key from
3297
- * `(toolCallId, stepName)`, so re-entry after interruption replays recorded Step results and the
3298
- * supplier dedupes any honestly-repeated call. The Tool itself carries no execution-class
3299
- * annotation: it stays fail-closed `uncertain`, and `TravelSupplierReconcilerLayer` proves
3300
- * re-entry safe from the keyed-Step construction instead.
3301
- */
3302
- const BookItinerary = Tool.make("book_itinerary", {
3303
- parameters: ItineraryBookingRequest,
3304
- success: ItineraryConfirmation,
3305
- failure: Schema.Union([SupplierUnavailable, DurableStepError]),
3306
- failureMode: "error",
3307
- dependencies: [DurableStep, SupplierBookingDesk]
3308
- });
3309
- const TravelPlannerPhase5Toolkit = Toolkit.make(DurableSearchFlights, DurableSearchLodging, DurableSearchActivities, BookFlight, CancelBooking, BookItinerary);
3310
- 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);
3311
- const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLayer({
3312
- search_flights: (query) => Effect.flatMap(FlightCatalog, (catalog) => catalog.search(FlightQuery.make(query))),
3313
- search_lodging: (query) => Effect.flatMap(LodgingCatalog, (catalog) => catalog.search(LodgingQuery.make(query))),
3314
- search_activities: (query) => Effect.flatMap(ActivityCatalog, (catalog) => catalog.search(ActivityQuery.make(query))),
3315
- book_flight: (request, context) => Effect.gen(function* () {
3316
- const desk = yield* SupplierBookingDesk;
3317
- const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
3318
- const record = yield* desk.book({
3319
- operation: "book-flight",
3320
- idempotencyKey: bookFlightIdempotencyKey(toolCallId),
3321
- detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`
3322
- });
3323
- return SupplierBookingConfirmation.make({
3324
- bookingRef: record.bookingRef,
3325
- status: "confirmed",
3326
- detail: record.detail
3327
- });
3328
- }),
3329
- cancel_booking: (request) => Effect.gen(function* () {
3330
- const record = yield* (yield* SupplierBookingDesk).cancel(request.bookingRef);
3331
- return CancellationConfirmation.make({
3332
- bookingRef: record.bookingRef,
3333
- status: "cancelled"
3334
- });
3335
- }),
3336
- book_itinerary: (request, context) => Effect.gen(function* () {
3337
- const desk = yield* SupplierBookingDesk;
3338
- const step = yield* DurableStep;
3339
- const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
3340
- const bookStep = (stepName, detail) => step.do(stepName, SupplierBookingRecord, desk.book({
3341
- operation: stepName,
3342
- idempotencyKey: itineraryStepIdempotencyKey(toolCallId, stepName),
3343
- detail
3344
- }));
3345
- const flight = yield* bookStep("reserve-flight", `flight ${request.quoteId} for ${request.travelerRef}`);
3346
- const lodging = yield* bookStep("reserve-lodging", `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`);
3347
- const confirmation = yield* bookStep("issue-confirmation", `itinerary confirmation for ${request.travelerRef}`);
3348
- return ItineraryConfirmation.make({
3349
- flightBookingRef: flight.bookingRef,
3350
- lodgingBookingRef: lodging.bookingRef,
3351
- confirmationCode: confirmation.bookingRef
3352
- });
3353
- })
3354
- });
3355
- const decodePersistedJson = Schema.decodeUnknownEffect(PersistedJson);
3356
- const encodeConfirmation = Schema.encodeEffect(SupplierBookingConfirmation);
3357
- /**
3358
- * The application reconciliation policy (durability §10): a claim about EXTERNAL truth, queried
3359
- * from the supplier desk by the same idempotency-key derivations the handlers use.
3360
- *
3361
- * - `book_flight`: a confirmed booking under `book-flight:{toolCallId}` is recovered supplier
3362
- * truth — `CompletedWithResult` settles it canonically without executing anything. Absence is
3363
- * NOT proof the call never started (a real supplier write could be in flight), so the desk
3364
- * answer stays fail-closed `Uncertain`.
3365
- * - `book_itinerary`: every external mutation inside is a named Step whose supplier idempotency
3366
- * key derives from `(toolCallId, stepName)`, so re-entry is provably safe by construction —
3367
- * `SafeToRetry`. Committed Steps replay from their records; repeated calls dedupe at the desk.
3368
- * - `cancel_booking`: declared `idempotent`, so the coordinator re-executes without consulting
3369
- * this policy; if ever asked, the bookingRef contract makes `SafeToRetry` honest.
3370
- * - anything else: fail-closed `Uncertain` (AGENTS rule 11).
3371
- */
3372
- const TravelSupplierReconcilerLayer = Layer.effect(ToolReconciler, Effect.gen(function* () {
3373
- const desk = yield* SupplierBookingDesk;
3374
- return ToolReconciler.of({ reconcile: (evidence) => Effect.gen(function* () {
3375
- switch (evidence.toolName) {
3376
- case "book_flight": {
3377
- const key = bookFlightIdempotencyKey(evidence.toolCallId);
3378
- const booking = yield* desk.lookup(key);
3379
- if (Option.isSome(booking) && booking.value.status === "confirmed") {
3380
- const confirmation = yield* encodeConfirmation(SupplierBookingConfirmation.make({
3381
- bookingRef: booking.value.bookingRef,
3382
- status: "confirmed",
3383
- detail: booking.value.detail
3384
- })).pipe(Effect.flatMap(decodePersistedJson));
3385
- return ReconciliationCompleted.make({
3386
- result: confirmation,
3387
- isFailure: false
3388
- });
3389
- }
3390
- return ReconciliationUncertain.make({ reason: `The supplier desk shows no confirmed booking under ${key}; a write may still be in flight.` });
3391
- }
3392
- case "book_itinerary": return ReconciliationSafeToRetry.make();
3393
- case "cancel_booking": return ReconciliationSafeToRetry.make();
3394
- default: return ReconciliationUncertain.make({ reason: `No supplier reconciliation exists for ${evidence.toolName}.` });
3395
- }
3396
- }).pipe(Effect.mapError((error) => ToolReconcilerError.make({
3397
- toolCallId: evidence.toolCallId,
3398
- message: `Supplier reconciliation failed: ${error.message}`
3399
- }))) });
3400
- }));
3401
- /**
3402
- * The cumulative Travel Planner, Phase 5: the durable planner now performs consequential
3403
- * supplier mutation under the full uncertainty protocol — approval-gated uncertain booking,
3404
- * idempotent-by-contract cancellation, and one Durable Tool whose Steps carry supplier
3405
- * idempotency keys.
3406
- */
3407
- const TravelPlannerPhase5 = Agent.define("travel-planner-phase-5", {
3408
- input: TripRequest,
3409
- output: TravelBookingReport,
3410
- instructions: [
3411
- "You are the Effect Agent Travel Planner P5 booking fixture.",
3412
- "Search with the read-only tools, then book with book_flight, book_itinerary, or",
3413
- "cancel_booking exactly as scripted. Every consequential mutation is approval-gated",
3414
- "or Step-structured. Return only a JSON object with summary and bookingRefs."
3415
- ].join(" "),
3416
- toolkit: TravelPlannerPhase5Toolkit,
3417
- policy: AgentPolicy.make({
3418
- maxTurns: 4,
3419
- maxToolCalls: 6,
3420
- maxDuration: "30 seconds",
3421
- toolConcurrency: 2
3422
- }),
3423
- description: "Durably book one itinerary with prepared/settled supplier records, Unknown Outcomes, named Steps, and joined traveler input.",
3424
- metadata: {
3425
- deploymentClass: "DN",
3426
- phase: "P5"
3427
- }
3428
- });
3429
- var TravelPlannerBookingEvidenceError = class extends Schema.TaggedError()("TravelPlannerBookingEvidenceError", { message: Schema.String }) {};
3430
- const bookingResultRefs = (result) => {
3431
- if (typeof result !== "object" || result === null) return [];
3432
- const refs = [];
3433
- for (const [field, value] of Object.entries(result)) if (typeof value === "string" && (field === "bookingRef" || field === "flightBookingRef" || field === "lodgingBookingRef" || field === "confirmationCode")) refs.push(value);
3434
- return refs;
3435
- };
3436
- const bookingToolNames = /* @__PURE__ */ new Set([
3437
- "book_flight",
3438
- "cancel_booking",
3439
- "book_itinerary"
3440
- ]);
3441
- /**
3442
- * Never-fabricate assertion (ROADMAP P5 exit gate): every successfully settled booking result in
3443
- * canonical history must reference a booking that actually exists in the supplier store. A
3444
- * `ToolCallSettled` whose bookingRef the supplier cannot produce would be a fabricated result —
3445
- * the exact lie the uncertainty protocol exists to prevent.
3446
- */
3447
- const assertSettledBookingsExistAtSupplier = Effect.fn("TravelPlannerPhase5.assertSettledBookingsExistAtSupplier")(function* (records) {
3448
- const bookings = yield* (yield* SupplierBookingDesk).bookings;
3449
- const knownRefs = new Set(bookings.map((booking) => booking.bookingRef));
3450
- for (const envelope of records) {
3451
- const payload = envelope.record.payload;
3452
- if (payload._tag !== "ToolCallSettled" || payload.isFailure || !bookingToolNames.has(payload.toolName)) continue;
3453
- 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.` });
3454
- }
3455
- });
3456
- /**
3457
- * Everything a durable P5 worker needs beyond the runtime stack and the supplier desk: the
3458
- * booking toolkit plus the deterministic P1 travel-service Layers. `SupplierBookingDesk` is
3459
- * deliberately NOT provided here — tests own the desk's lifetime so its counters, bookings, and
3460
- * crash windows survive Tool-Layer rebuilds across Attempts.
3461
- */
3462
- const phase5TravelPlannerWorkerLayer = Layer.mergeAll(TravelPlannerPhase5ToolkitLayer, FlightCatalogLayer, LodgingCatalogLayer, ActivityCatalogLayer).pipe(Layer.provide(CatalogLifecycle.layerNoDeps));
3463
- //#endregion
3464
- //#region src/fixtures/travel-planner/subagents.ts
3465
- var DestinationQuery = class extends Schema.Class("DestinationQuery")({ destination: AirportCode }) {};
3466
- var DestinationFacts = class extends Schema.Class("DestinationFacts")({
3467
- destination: AirportCode,
3468
- highlights: Schema.Array(Schema.String),
3469
- advisory: Schema.NonEmptyString
3470
- }) {};
3471
- var DestinationGuideUnavailable = class extends Schema.TaggedError()("DestinationGuideUnavailable", {
3472
- destination: AirportCode,
3473
- message: Schema.String
3474
- }) {};
3475
- var DestinationGuide = class extends Context.Service()("@effect-agent/testing/travel-planner/DestinationGuide") {};
3476
- const LookupDestination = Tool.make("lookup_destination", {
3477
- parameters: DestinationQuery,
3478
- success: DestinationFacts,
3479
- failure: DestinationGuideUnavailable,
3480
- failureMode: "error",
3481
- dependencies: [DestinationGuide]
3482
- });
3483
- const DestinationResearcherToolkit = Toolkit.make(LookupDestination);
3484
- const DestinationResearcherToolkitLayer = DestinationResearcherToolkit.toLayer({ lookup_destination: (query) => Effect.flatMap(DestinationGuide, (guide) => guide.lookup(query)) });
3485
- var DestinationBrief = class extends Schema.Class("DestinationBrief")({
3486
- destination: AirportCode,
3487
- focus: Schema.NonEmptyString
3488
- }) {};
3489
- var DestinationReport = class extends Schema.Class("DestinationReport")({
3490
- destination: AirportCode,
3491
- highlights: Schema.Array(Schema.String),
3492
- advisory: Schema.NonEmptyString
3493
- }) {};
3494
- const DestinationResearcher = Agent.define("destination-researcher", {
3495
- input: DestinationBrief,
3496
- output: DestinationReport,
3497
- instructions: "Consult lookup_destination exactly once for the briefed airport, then return only a JSON destination report.",
3498
- toolkit: DestinationResearcherToolkit,
3499
- policy: AgentPolicy.make({
3500
- maxTurns: 2,
3501
- maxToolCalls: 1,
3502
- maxDuration: "30 seconds",
3503
- toolConcurrency: 1
3504
- }),
3505
- description: "Research one candidate destination with the deterministic travel guide.",
3506
- metadata: {
3507
- deploymentClass: "E",
3508
- phase: "S1"
3509
- }
3510
- });
3511
- const decodeAirportCode = Schema.decodeSync(AirportCode);
3512
- const guideFacts = /* @__PURE__ */ new Map([["LHR", DestinationFacts.make({
3513
- destination: decodeAirportCode("LHR"),
3514
- highlights: ["Barbican brutalism walk", "Kew glasshouse survey"],
3515
- advisory: "London favors museum mornings and riverside evenings."
3516
- })], ["CDG", DestinationFacts.make({
3517
- destination: decodeAirportCode("CDG"),
3518
- highlights: ["Marais passage crawl", "Seine bookstall loop"],
3519
- advisory: "Paris rewards early galleries and late cafes."
3520
- })]]);
3521
- /** Deterministic guide lookup shared by the default and test-local guide Layers. */
3522
- const destinationLookup = (query) => {
3523
- const facts = guideFacts.get(query.destination);
3524
- return facts === void 0 ? Effect.fail(DestinationGuideUnavailable.make({
3525
- destination: query.destination,
3526
- message: "No deterministic guide entry exists for this destination."
3527
- })) : Effect.succeed(facts);
3528
- };
3529
- const requireDestinationFacts = (destination) => {
3530
- const facts = guideFacts.get(destination);
3531
- if (facts === void 0) throw new Error(`No deterministic guide entry exists for destination ${destination}`);
3532
- return facts;
3533
- };
3534
- /** The report the scripted researcher writes after consulting the guide. */
3535
- const destinationReportFor = (destination) => {
3536
- const facts = requireDestinationFacts(destination);
3537
- return DestinationReport.make({
3538
- destination: facts.destination,
3539
- highlights: facts.highlights,
3540
- advisory: facts.advisory
3541
- });
3542
- };
3543
- const encodedDestinationReport = (destination) => JSON.stringify(Schema.encodeSync(DestinationReport)(destinationReportFor(destination)));
3544
- const DestinationGuideLayer = Layer.effect(DestinationGuide, Effect.gen(function* () {
3545
- const lifecycle = yield* CatalogLifecycle;
3546
- yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
3547
- return DestinationGuide.of({ lookup: destinationLookup });
3548
- }));
3549
- /** Child-side construction requirements of the delegation handler Layer. */
3550
- const DestinationResearchSupportLayer = Layer.mergeAll(DestinationResearcherToolkitLayer, DestinationGuideLayer);
3551
- var DestinationResearchRequest = class extends Schema.Class("DestinationResearchRequest")({
3552
- destination: AirportCode,
3553
- focus: Schema.NonEmptyString
3554
- }) {};
3555
- var DestinationResearchFindings = class extends Schema.Class("DestinationResearchFindings")({
3556
- destination: AirportCode,
3557
- summary: Schema.NonEmptyString
3558
- }) {};
3559
- var DestinationResearchFailed = class extends Schema.TaggedError()("DestinationResearchFailed", { childErrorTag: Schema.NonEmptyString }) {};
3560
- /**
3561
- * Deterministic delegation-admission choreography seam. `prepareInput` awaits
3562
- * this gate before the handler reserves budget or spawns, so tests can order
3563
- * concurrent delegation preflights without sleeps. It also keeps the
3564
- * projection's construction requirements honestly visible in the handler
3565
- * Layer's `R` (spec/subagents.md §4.1). The open Layer never waits.
3566
- */
3567
- var ResearchDispatchGate = class ResearchDispatchGate extends Context.Service()("@effect-agent/testing/travel-planner/ResearchDispatchGate") {
3568
- static layerOpen = Layer.succeed(this, ResearchDispatchGate.of({ awaitDispatch: () => Effect.void }));
3569
- };
3570
- /**
3571
- * Finite per-invocation bounds (SUB-009): each child may use two Turns and
3572
- * one Tool Call; the parent Run may establish at most two children with at
3573
- * most two running concurrently.
3574
- */
3575
- const destinationResearchPolicy = SubagentPolicy.make({
3576
- maxChildren: 2,
3577
- maxConcurrency: 2,
3578
- maxTurns: 2,
3579
- maxToolCalls: 1,
3580
- maxDuration: "10 seconds"
3581
- });
3582
- const destinationResearchDelegation = Subagent.define("delegate_destination_research", {
3583
- description: "Research one candidate destination with the deterministic travel guide and return a bounded finding.",
3584
- target: DestinationResearcher,
3585
- parameters: DestinationResearchRequest,
3586
- success: DestinationResearchFindings,
3587
- failure: DestinationResearchFailed,
3588
- prepareInput: (request) => Effect.gen(function* () {
3589
- yield* (yield* ResearchDispatchGate).awaitDispatch(request.destination);
3590
- return DestinationBrief.make({
3591
- destination: request.destination,
3592
- focus: `research:${request.focus}`
3593
- });
3594
- }),
3595
- projectResult: (report) => Effect.succeed(DestinationResearchFindings.make({
3596
- destination: report.destination,
3597
- summary: report.advisory
3598
- })),
3599
- policy: destinationResearchPolicy
3600
- });
3601
- /** Total mapping from every expected child Run failure to the declared Tool failure (SUB-028). */
3602
- const mapResearchChildFailure = (failure) => DestinationResearchFailed.make({ childErrorTag: failure._tag });
3603
- /** Runtime wiring: pair the immutable delegation with one explicit child Binding. */
3604
- const destinationResearchHandlersLayer = (childBinding) => SubagentRuntime.layer(destinationResearchDelegation, childBinding, { mapChildFailure: mapResearchChildFailure });
3605
- var ResearchMission = class extends Schema.Class("ResearchMission")({
3606
- request: Schema.NonEmptyString,
3607
- candidates: Schema.Array(AirportCode).check(Schema.isMinLength(1))
3608
- }) {};
3609
- var DestinationRecommendation = class extends Schema.Class("DestinationRecommendation")({
3610
- destination: AirportCode,
3611
- summary: Schema.NonEmptyString
3612
- }) {};
3613
- var DestinationShortlist = class extends Schema.Class("DestinationShortlist")({
3614
- recommendations: Schema.Array(DestinationRecommendation),
3615
- nextAction: Schema.Literal("review")
3616
- }) {};
3617
- /** Parent-only transcript markers used to prove child context isolation (SUB-006/015). */
3618
- const coordinatorConfidentialMarker = "coordinator-vault-7q42";
3619
- const missionConfidentialMarker = "traveler-dossier-19f";
3620
- const TravelCoordinatorToolkit = Toolkit.make(destinationResearchDelegation.tool);
3621
- const TravelCoordinator = Agent.define("travel-coordinator", {
3622
- input: ResearchMission,
3623
- output: DestinationShortlist,
3624
- instructions: [
3625
- "You are the Effect Agent Travel Planner S1 delegation coordinator.",
3626
- `Coordinator-only context: ${coordinatorConfidentialMarker}.`,
3627
- "Call delegate_destination_research once per candidate in one Tool batch.",
3628
- "Return only a JSON shortlist built from the delegated findings. This is read-only planning."
3629
- ].join("\n"),
3630
- toolkit: TravelCoordinatorToolkit,
3631
- policy: AgentPolicy.make({
3632
- maxTurns: 2,
3633
- maxToolCalls: 3,
3634
- maxDuration: "30 seconds",
3635
- toolConcurrency: 3
3636
- }),
3637
- description: "Coordinate bounded destination research through one declared attached delegation Tool.",
3638
- metadata: {
3639
- deploymentClass: "E",
3640
- phase: "S1"
3641
- }
3642
- });
3643
- const researchMission = Schema.decodeSync(ResearchMission)({
3644
- request: `Shortlist one September culture city; keep ${missionConfidentialMarker} inside the coordinator conversation.`,
3645
- candidates: ["LHR", "CDG"]
3646
- });
3647
- const expectedDestinationShortlist = DestinationShortlist.make({
3648
- recommendations: researchMission.candidates.map((destination) => DestinationRecommendation.make({
3649
- destination,
3650
- summary: requireDestinationFacts(destination).advisory
3651
- })),
3652
- nextAction: "review"
3653
- });
3654
- const scriptedUsage$2 = {
3655
- inputTokens: { total: 96 },
3656
- outputTokens: { total: 64 }
3657
- };
3658
- /** One coordinator Turn that declares the given delegation Tool Calls in order. */
3659
- const coordinatorResearchTurn = (calls) => ({
3660
- _tag: "Stream",
3661
- parts: [...calls.map((call) => ({
3662
- type: "tool-call",
3663
- id: call.id,
3664
- name: "delegate_destination_research",
3665
- params: {
3666
- destination: call.destination,
3667
- focus: call.focus
3668
- }
3669
- })), {
3670
- type: "finish",
3671
- reason: "tool-calls",
3672
- usage: scriptedUsage$2
3673
- }],
3674
- termination: { _tag: "Complete" }
3675
- });
3676
- /** The coordinator's final structured-output Turn. */
3677
- const coordinatorShortlistTurn = (shortlist) => ({
3678
- _tag: "Stream",
3679
- parts: [
3680
- {
3681
- type: "text-start",
3682
- id: "shortlist"
3683
- },
3684
- {
3685
- type: "text-delta",
3686
- id: "shortlist",
3687
- delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(shortlist))
3688
- },
3689
- {
3690
- type: "text-end",
3691
- id: "shortlist"
3692
- },
3693
- {
3694
- type: "finish",
3695
- reason: "stop",
3696
- usage: scriptedUsage$2
3697
- }
3698
- ],
3699
- termination: { _tag: "Complete" }
3700
- });
3701
- /** Static researcher script for single-child tests: one guide lookup, then the report. */
3702
- const researcherHappyPathTurns = (destination) => [{
3703
- _tag: "Stream",
3704
- parts: [{
3705
- type: "tool-call",
3706
- id: `lookup-${destination}`,
3707
- name: "lookup_destination",
3708
- params: { destination }
3709
- }, {
3710
- type: "finish",
3711
- reason: "tool-calls",
3712
- usage: scriptedUsage$2
3713
- }],
3714
- termination: { _tag: "Complete" }
3715
- }, {
3716
- _tag: "Stream",
3717
- parts: [
3718
- {
3719
- type: "text-start",
3720
- id: "destination-report"
3721
- },
3722
- {
3723
- type: "text-delta",
3724
- id: "destination-report",
3725
- delta: encodedDestinationReport(destination)
3726
- },
3727
- {
3728
- type: "text-end",
3729
- id: "destination-report"
3730
- },
3731
- {
3732
- type: "finish",
3733
- reason: "stop",
3734
- usage: scriptedUsage$2
3735
- }
3736
- ],
3737
- termination: { _tag: "Complete" }
3738
- }];
3739
- const researcherLookupParts$2 = (destination) => [{
3740
- type: "tool-call",
3741
- id: `lookup-${destination}`,
3742
- name: "lookup_destination",
3743
- params: { destination },
3744
- providerExecuted: false
3745
- }, {
3746
- type: "finish",
3747
- reason: "tool-calls",
3748
- usage: scriptedUsage$2
3749
- }];
3750
- const researcherReportParts$2 = (destination) => [
3751
- {
3752
- type: "text-start",
3753
- id: "destination-report"
3754
- },
3755
- {
3756
- type: "text-delta",
3757
- id: "destination-report",
3758
- delta: encodedDestinationReport(destination)
3759
- },
3760
- {
3761
- type: "text-end",
3762
- id: "destination-report"
3763
- },
3764
- {
3765
- type: "finish",
3766
- reason: "stop",
3767
- usage: scriptedUsage$2
3768
- }
3769
- ];
3770
- /**
3771
- * Build a deterministic researcher Model whose per-child behavior is keyed by
3772
- * the destination named in the child's own prompt: Turn one records the
3773
- * prompt, signals `started`, and calls the guide Tool; Turn two waits for the
3774
- * caller's `release` before writing the report. Each child Run builds the
3775
- * Model Layer inside its own scope, so one `CatalogLifecycle` acquisition and
3776
- * finalization is observed per child — the same acquire/release counting the
3777
- * catalog Layers use to prove interruption reaches every finalizer.
3778
- */
3779
- const makeDestinationResearcherModel = (destinations) => Effect.gen(function* () {
3780
- const lifecycle = yield* CatalogLifecycle;
3781
- const prompts = yield* Ref.make([]);
3782
- const gates = /* @__PURE__ */ new Map();
3783
- for (const destination of destinations) gates.set(destination, {
3784
- started: yield* Deferred.make(),
3785
- release: yield* Deferred.make()
3786
- });
3787
- const gatesFor = (destination) => Effect.suspend(() => {
3788
- const entry = gates.get(destination);
3789
- return entry === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No researcher gates exist for destination ${destination}`)) : Effect.succeed(entry);
3790
- });
3791
- return {
3792
- controls: {
3793
- awaitStarted: (destination) => gatesFor(destination).pipe(Effect.flatMap((entry) => Deferred.await(entry.started))),
3794
- release: (destination) => gatesFor(destination).pipe(Effect.flatMap((entry) => Deferred.succeed(entry.release, void 0)), Effect.asVoid),
3795
- prompts: Ref.get(prompts)
3796
- },
3797
- model: Model.make("scripted", "destination-researcher-scripted", Layer.effect(LanguageModel.LanguageModel, Effect.gen(function* () {
3798
- yield* Effect.acquireRelease(lifecycle.markAcquired, () => lifecycle.markFinalized);
3799
- const turn = yield* Ref.make(0);
3800
- return yield* LanguageModel.make({
3801
- generateText: () => Effect.succeed([]),
3802
- streamText: (options) => Stream.unwrap(Effect.gen(function* () {
3803
- const promptJson = JSON.stringify(options.prompt.content);
3804
- const destination = destinations.find((candidate) => promptJson.includes(candidate));
3805
- if (destination === void 0) return yield* Effect.die(/* @__PURE__ */ new Error("The researcher prompt names no scripted destination"));
3806
- const entry = yield* gatesFor(destination);
3807
- if ((yield* Ref.getAndUpdate(turn, (value) => value + 1)) === 0) {
3808
- yield* Ref.update(prompts, (previous) => [...previous, promptJson]);
3809
- yield* Deferred.succeed(entry.started, void 0);
3810
- return Stream.fromIterable(researcherLookupParts$2(destination));
3811
- }
3812
- yield* Deferred.await(entry.release);
3813
- return Stream.fromIterable(researcherReportParts$2(destination));
3814
- }))
3815
- });
3816
- })))
3817
- };
3818
- });
3819
- //#endregion
3820
- //#region src/fixtures/travel-planner/subagents-durable.ts
3821
- var TravelPlannerSubagentDurabilityProfile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerSubagentDurabilityProfile")({
3822
- deploymentClass: Schema.Literal("DN"),
3823
- durableAttachedSubagents: Schema.Literal(true),
3824
- canonicalSchemaVersion: Schema.Literal(1),
3825
- /** Establishment/join replay converges on one child Receipt, Conversation, and join batch. */
3826
- subagentReplaySafe: Schema.Literal(true),
3827
- /** Never claimed (rule 8): child ordinary Tools stop at Unknown Outcomes, they do not replay. */
3828
- childExternalEffectsExactlyOnce: Schema.Literal(false),
3829
- /** The same conformance suite under DO eviction/alarms is P6 scope (spec §17 `DC`). */
3830
- cloudflareEquivalence: Schema.Literal(false)
3831
- }) {};
3832
- const s2TravelPlannerProfile = TravelPlannerSubagentDurabilityProfile.make({
3833
- deploymentClass: "DN",
3834
- durableAttachedSubagents: true,
3835
- canonicalSchemaVersion: 1,
3836
- subagentReplaySafe: true,
3837
- childExternalEffectsExactlyOnce: false,
3838
- cloudflareEquivalence: false
3839
- });
3840
- const s2TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-s2-deployment");
3841
- const s2TravelPlannerProducerId = Schema.decodeSync(ProducerId)("travel-planner-s2-producer");
3842
- const s2TravelPlannerPrincipal = Schema.decodeSync(Principal)("travel-planner-s2-principal");
3843
- const digestOf$1 = (character) => Schema.decodeSync(Digest)(character.repeat(64));
3844
- /** Redacted, deterministic parent (coordinator) definition digests for this fixture version. */
3845
- const s2CoordinatorDigests = DefinitionDigests.make({
3846
- agent: digestOf$1("a"),
3847
- model: digestOf$1("b"),
3848
- tools: digestOf$1("c")
3849
- });
3850
- /**
3851
- * The exact child Binding digest strings the application declares on
3852
- * `SubagentRuntimeOptions.durable.targetDigests` AND the host registers with
3853
- * the `AgentBindingResolver` for the researcher Binding. The coordinator
3854
- * stores and verifies them byte-for-byte (SUB-023); a host registration under
3855
- * different strings is a `ChildCompatibilityFailure`, never a substitution.
3856
- */
3857
- const s2ResearcherDigestStrings = {
3858
- agent: "d".repeat(64),
3859
- model: "e".repeat(64),
3860
- tools: "f".repeat(64)
3861
- };
3862
- const s2ResearcherDigests = DefinitionDigests.make({
3863
- agent: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.agent),
3864
- model: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.model),
3865
- tools: Schema.decodeSync(Digest)(s2ResearcherDigestStrings.tools)
3866
- });
3867
- /** Durable admission options for one coordinator Submission on one mission lane. */
3868
- const s2TravelPlannerSubmitOptions = (conversationId, idempotencyKey) => ({
3869
- conversationId,
3870
- principal: s2TravelPlannerPrincipal,
3871
- idempotencyKey,
3872
- definitions: s2CoordinatorDigests
3873
- });
3874
- /** The structural submit slice of the coordinator Binding (`DurableAgentRuntime.submit`). */
3875
- const s2CoordinatorSubmitAgent = { definition: {
3876
- id: TravelCoordinator.id,
3877
- input: TravelCoordinator.input
3878
- } };
3879
- /**
3880
- * The per-invocation reservation the durable handler computes from the S1
3881
- * delegation policy (`delegationAllocationFromPolicy`): the conservation
3882
- * evidence in the S2 tests checks the ledger reservation rows and the
3883
- * canonical `SubagentJoined.finalAccounting` against exactly this value.
3884
- */
3885
- const durableResearchAllocation = delegationAllocationFromPolicy(destinationResearchPolicy);
3886
- /** The one scripted delegation Tool Call id of the durable coordinator Run. */
3887
- const durableResearchCallId = "research-lhr-1";
3888
- /** The child's own scripted guide-lookup Tool Call id. */
3889
- const durableChildLookupCallId = (destination) => `lookup-${destination}`;
3890
- /** The projected finding the parent joins (only the advisory crosses, SUB-015). */
3891
- const durableResearchFinding = (destination) => ({
3892
- destination: destinationReportFor(destination).destination,
3893
- summary: destinationReportFor(destination).advisory
3894
- });
3895
- /** The coordinator's expected final shortlist for one researched destination. */
3896
- const durableResearchShortlist = (destination) => DestinationShortlist.make({
3897
- recommendations: [DestinationRecommendation.make({
3898
- destination: destinationReportFor(destination).destination,
3899
- summary: destinationReportFor(destination).advisory
3900
- })],
3901
- nextAction: "review"
3902
- });
3903
- /**
3904
- * The deterministic guide facts in encoded (wire) form: the "supplier truth"
3905
- * an authorized operator records through `resolveUnknown` when a child guide
3906
- * lookup stopped at an Unknown Outcome (DUR-017 — the framework never guesses
3907
- * or replays it).
3908
- */
3909
- const encodedDestinationFacts = (destination) => {
3910
- const report = destinationReportFor(destination);
3911
- return Schema.encodeSync(DestinationFacts)(DestinationFacts.make({
3912
- destination: report.destination,
3913
- highlights: report.highlights,
3914
- advisory: report.advisory
3915
- }));
3916
- };
3917
- const scriptedUsage$1 = {
3918
- inputTokens: { total: 96 },
3919
- outputTokens: { total: 64 }
3920
- };
3921
- /** One scripted model whose behavior is keyed by the global invocation index. */
3922
- const makeInvocationCountingModel = (name, script) => Effect.gen(function* () {
3923
- const calls = yield* Ref.make(0);
3924
- const prompts = yield* Ref.make([]);
3925
- return {
3926
- model: Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
3927
- generateText: () => Effect.succeed([]),
3928
- streamText: (request) => Stream.unwrap(Effect.gen(function* () {
3929
- const call = yield* Ref.getAndUpdate(calls, (value) => value + 1);
3930
- yield* Ref.update(prompts, (previous) => [...previous, JSON.stringify(request.prompt.content)]);
3931
- return Stream.fromIterable(script(call));
3932
- }))
3933
- }))),
3934
- calls: Ref.get(calls),
3935
- prompts: Ref.get(prompts)
3936
- };
3937
- });
3938
- const delegationTurnParts = (toolCallId, destination, focus) => [{
3939
- type: "tool-call",
3940
- id: toolCallId,
3941
- name: "delegate_destination_research",
3942
- params: {
3943
- destination,
3944
- focus
3945
- },
3946
- providerExecuted: false
3947
- }, {
3948
- type: "finish",
3949
- reason: "tool-calls",
3950
- usage: scriptedUsage$1
3951
- }];
3952
- const shortlistParts = (shortlist) => [
3953
- {
3954
- type: "text-start",
3955
- id: "shortlist"
3956
- },
3957
- {
3958
- type: "text-delta",
3959
- id: "shortlist",
3960
- delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(shortlist))
3961
- },
3962
- {
3963
- type: "text-end",
3964
- id: "shortlist"
3965
- },
3966
- {
3967
- type: "finish",
3968
- reason: "stop",
3969
- usage: scriptedUsage$1
3970
- }
3971
- ];
3972
- const researcherLookupParts$1 = (destination) => [{
3973
- type: "tool-call",
3974
- id: durableChildLookupCallId(destination),
3975
- name: "lookup_destination",
3976
- params: { destination },
3977
- providerExecuted: false
3978
- }, {
3979
- type: "finish",
3980
- reason: "tool-calls",
3981
- usage: scriptedUsage$1
3982
- }];
3983
- const researcherReportParts$1 = (destination) => [
3984
- {
3985
- type: "text-start",
3986
- id: "destination-report"
3987
- },
3988
- {
3989
- type: "text-delta",
3990
- id: "destination-report",
3991
- delta: encodedDestinationReport(destination)
3992
- },
3993
- {
3994
- type: "text-end",
3995
- id: "destination-report"
3996
- },
3997
- {
3998
- type: "finish",
3999
- reason: "stop",
4000
- usage: scriptedUsage$1
4001
- }
4002
- ];
4003
- /** Runtime wiring for the durable slice: the S1 delegation plus the S2 digest declaration. */
4004
- const durableDestinationResearchHandlersLayer = (childBinding) => SubagentRuntime.layer(destinationResearchDelegation, childBinding, {
4005
- mapChildFailure: mapResearchChildFailure,
4006
- durable: { targetDigests: s2ResearcherDigestStrings }
4007
- });
4008
- /**
4009
- * Build the S2 Travel Planner harness: an invocation-counting scripted
4010
- * coordinator (Turn 1 declares the one delegation call, Turn 2 writes the
4011
- * shortlist), an invocation-counting scripted researcher (Turn 1 consults the
4012
- * guide, Turn 2 writes the report), and both worker Bindings captured with
4013
- * their requirement Contexts via `DurableWorkerBinding.make` under the exact
4014
- * fixture digests. The returned `bindings` are plain values: they can be
4015
- * registered with several `NodeDurableRuntime` stacks over the same SQLite
4016
- * file while the counters keep counting across all of them.
4017
- */
4018
- const makeDurableResearchHarness = (options) => Effect.gen(function* () {
4019
- const destination = options?.destination ?? "LHR";
4020
- const focus = options?.focus ?? "museums";
4021
- const guideInvocations = yield* Ref.make(0);
4022
- const guideLayer = Layer.succeed(DestinationGuide, DestinationGuide.of({ lookup: (query) => Ref.update(guideInvocations, (count) => count + 1).pipe(Effect.andThen(destinationLookup(query))) }));
4023
- const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(guideLayer));
4024
- const childModel = yield* makeInvocationCountingModel("destination-researcher-s2", (call) => call === 0 ? researcherLookupParts$1(destination) : researcherReportParts$1(destination));
4025
- const childBinding = Agent.withModel(DestinationResearcher, childModel.model);
4026
- const parentModel = yield* makeInvocationCountingModel("travel-coordinator-s2", (call) => call === 0 ? delegationTurnParts(durableResearchCallId, destination, focus) : shortlistParts(durableResearchShortlist(destination)));
4027
- const parentBinding = Agent.withModel(TravelCoordinator, parentModel.model);
4028
- const delegationLayer = durableDestinationResearchHandlersLayer(childBinding).pipe(Layer.provide(Layer.mergeAll(childToolkitLayer, SubagentReservationsMemoryLive, DeterministicIdGeneratorLayer, ResearchDispatchGate.layerOpen)));
4029
- return {
4030
- bindings: [yield* DurableWorkerBinding.make(parentBinding, s2CoordinatorDigests).pipe(Effect.provide(delegationLayer)), yield* DurableWorkerBinding.make(childBinding, options?.childRegistrationDigests ?? s2ResearcherDigests).pipe(Effect.provide(childToolkitLayer))],
4031
- parentModelCalls: parentModel.calls,
4032
- parentPrompts: parentModel.prompts,
4033
- childModelCalls: childModel.calls,
4034
- childPrompts: childModel.prompts,
4035
- guideInvocations: Ref.get(guideInvocations)
4036
- };
4037
- });
4038
- //#endregion
4039
- //#region src/fixtures/travel-planner/phase6.ts
4040
- /**
4041
- * The Phase 6 profile: the P4/P5/S2 Travel Planner claims re-earned on the Cloudflare Durable
4042
- * Object runtime (deployment class `DC`), where eviction and alarm redelivery replace process
4043
- * kill and restart as the exercised recovery path. `cloudflareEquivalence` is the claim the S2
4044
- * fixture explicitly deferred to P6 (`TravelPlannerSubagentDurabilityProfile` pins it `false`
4045
- * for `DN`): it flips to `true` here ONLY because the phase-6 suites assert byte-equal
4046
- * cross-platform normalized canonical evidence against one committed golden. Exactly-once
4047
- * EXTERNAL effects remain — deliberately — unclaimed on every platform (DUR-003).
4048
- */
4049
- var TravelPlannerCloudflareProfile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerCloudflareProfile")({
4050
- deploymentClass: Schema.Literal("DC"),
4051
- durableAcceptedWork: Schema.Literal(true),
4052
- canonicalSchemaVersion: Schema.Literal(1),
4053
- /** P5 semantics under DC recovery: prepared/settled records, Unknown Outcomes, approvals. */
4054
- supplierBookingUncertaintyProtocol: Schema.Literal(true),
4055
- /** S2 semantics under DC recovery: cross-Object establishment/join, completed child never re-runs. */
4056
- durableAttachedSubagents: Schema.Literal(true),
4057
- /** DN and DC produce byte-equal cross-platform normalized canonical evidence (one golden). */
4058
- cloudflareEquivalence: Schema.Literal(true),
4059
- /** Never claimed at any phase on any platform (DUR-003). */
4060
- exactlyOnceExternalEffects: Schema.Literal(false)
4061
- }) {};
4062
- const phase6TravelPlannerProfile = TravelPlannerCloudflareProfile.make({
4063
- deploymentClass: "DC",
4064
- durableAcceptedWork: true,
4065
- canonicalSchemaVersion: 1,
4066
- supplierBookingUncertaintyProtocol: true,
4067
- durableAttachedSubagents: true,
4068
- cloudflareEquivalence: true,
4069
- exactlyOnceExternalEffects: false
4070
- });
4071
- const phase6TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p6-deployment");
4072
- /** Producer prefix of the DC host; each Object mints `{prefix}:{conversationId}`. */
4073
- const phase6TravelPlannerProducerPrefix = "travel-planner-p6-producer";
4074
- /** The full producer identity one DC Conversation Object mints for itself. */
4075
- const phase6TravelPlannerProducerId = (conversationId) => Schema.decodeSync(ProducerId)(`${phase6TravelPlannerProducerPrefix}:${conversationId}`);
4076
- const digestOf = (character) => Schema.decodeSync(Digest)(character.repeat(64));
4077
- /**
4078
- * Registration digests of the GATED planner Binding: the same `TravelPlannerPhase4` definition
4079
- * bound to a model whose first response waits on a test gate, addressable separately so the
4080
- * admission-limits rows can hold a lane busy deterministically without touching the ordinary
4081
- * planner registration.
4082
- */
4083
- const phase6GatedPlannerDefinitionDigests = DefinitionDigests.make({
4084
- agent: digestOf("9"),
4085
- model: digestOf("8"),
4086
- tools: digestOf("7")
4087
- });
4088
- const decodeComparableJson = Schema.decodeUnknownEffect(Schema.Json);
4089
- /** The base normal form's element shape, re-decoded so sequences can be renumbered. */
4090
- const ComparableEnvelope = Schema.Struct({
4091
- batchId: Schema.String,
4092
- sequence: Schema.Number,
4093
- record: Schema.Json
4094
- });
4095
- const decodeComparableEnvelopes = Schema.decodeUnknownEffect(Schema.Array(ComparableEnvelope));
4096
- /**
4097
- * The CROSS-PLATFORM extension of `normalizeDurableTravelPlannerEvidence` (D-P6-6): after the
4098
- * base normalization replaces the two ledger-minted identities (which also normalizes the
4099
- * DC-format routable `{uuidv7}:{conversationId}` Submission identities and everything derived
4100
- * from them), this form additionally scrubs everything that legitimately differs between a DN
4101
- * process and a DC Durable Object over the same scenario:
4102
- *
4103
- * - `RepairAnnotated` audit records are dropped BEFORE normalization and the canonical
4104
- * sequence is renumbered to the surviving order: repairs are DUR-013 evidence of recovery
4105
- * itself, legally present in a recovered run and legally absent from an uninterrupted
4106
- * control (on DC even a CLEAN run carries one, because every pass reconciles before it
4107
- * claims, so the ready lane's input is applied through the recovery path). Canonical ORDER
4108
- * is the durability §5 claim; sequence contiguity is a platform artifact of who appended;
4109
- * - the Conversation identity (DC lanes mint unique names per test run);
4110
- * - the deployment and producer identities (host configuration, not canonical semantics);
4111
- * - `createdAt` commit timestamps (wall clock);
4112
- * - 64-hex digests (they hash RAW content that legally embeds run-specific identity, so they
4113
- * can never be byte-equal across runs; chain integrity is asserted separately by the
4114
- * adapters and the convergence helpers).
4115
- *
4116
- * Two runs whose cross-platform normalized evidence is byte-equal took canonically equivalent
4117
- * histories — the exact sense in which durability §5 permits storage differences while
4118
- * requiring the same observable ordering. Both the DN and DC suites assert equality against
4119
- * the one committed `phase6TravelPlannerGoldenEvidence`, so DN ≡ DC transitively.
4120
- */
4121
- const normalizeCrossPlatformTravelPlannerEvidence = Effect.fn("TravelPlannerPhase6.normalizeCrossPlatformTravelPlannerEvidence")(function* (records, receipt, identity) {
4122
- const base = yield* normalizeDurableTravelPlannerEvidence(records.filter((envelope) => envelope.record.payload._tag !== "RepairAnnotated"), receipt);
4123
- const scrubbed = JSON.parse(JSON.stringify(base).replaceAll(identity.producerId, "{producerId}").replaceAll(identity.deploymentId, "{deploymentId}").replaceAll(identity.conversationId, "{conversationId}").replaceAll(/\d{4}-\d{2}-\d{2}T[0-9:.]+Z/g, "{timestamp}").replaceAll(/"[0-9a-f]{64}"/g, "\"{digest}\""));
4124
- 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) => ({
4125
- batchId: entry.batchId,
4126
- sequence: index + 1,
4127
- record: entry.record
4128
- }));
4129
- return yield* decodeComparableJson(renumbered).pipe(Effect.mapError((error) => TravelPlannerDurableEvidenceError.make({ message: `Cross-platform normalized evidence is not comparable JSON: ${error.message}` })));
4130
- });
4131
- const scriptedUsage = {
4132
- inputTokens: { total: 128 },
4133
- outputTokens: { total: 96 }
4134
- };
4135
- const promptAwareModel = (name, decide) => Model.make("scripted", name, Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
4136
- generateText: () => Effect.succeed([]),
4137
- streamText: (options) => Stream.unwrap(Effect.sync(() => decide(JSON.stringify(options.prompt))))
4138
- })));
4139
- /** The P1/P4 happy-path Tool Call identities (scenarios.ts, byte-stable since P1). */
4140
- const phase6FlightCallId = "flight-call-1";
4141
- const phase6LodgingCallId = "lodging-call-1";
4142
- const phase6ActivityCallId = "activity-call-1";
4143
- /** Turn 1 of the planner: the SAME three search declarations as `phase1HappyPathTurns`. */
4144
- const plannerSearchTurnParts = [
4145
- {
4146
- type: "tool-call",
4147
- id: phase6FlightCallId,
4148
- name: "search_flights",
4149
- params: {
4150
- origin: "SFO",
4151
- destination: "LHR",
4152
- departOn: "2026-09-14",
4153
- travelers: 2
4154
- }
4155
- },
4156
- {
4157
- type: "tool-call",
4158
- id: phase6LodgingCallId,
4159
- name: "search_lodging",
4160
- params: {
4161
- destination: "LHR",
4162
- departOn: "2026-09-14",
4163
- nights: 4,
4164
- travelers: 2
4165
- }
4166
- },
4167
- {
4168
- type: "tool-call",
4169
- id: phase6ActivityCallId,
4170
- name: "search_activities",
4171
- params: {
4172
- destination: "LHR",
4173
- departOn: "2026-09-14",
4174
- nights: 4,
4175
- travelers: 2
4176
- }
4177
- },
4178
- {
4179
- type: "finish",
4180
- reason: "tool-calls",
4181
- usage: scriptedUsage
4182
- }
4183
- ];
4184
- /** Turn 2 of the planner: the SAME itinerary text as `phase1HappyPathTurns`. */
4185
- const plannerPlanTurnParts = [
4186
- {
4187
- type: "text-start",
4188
- id: "itinerary-json"
4189
- },
4190
- {
4191
- type: "text-delta",
4192
- id: "itinerary-json",
4193
- delta: JSON.stringify(Schema.encodeSync(TravelPlan)(expectedTravelPlan))
4194
- },
4195
- {
4196
- type: "text-end",
4197
- id: "itinerary-json"
4198
- },
4199
- {
4200
- type: "finish",
4201
- reason: "stop",
4202
- usage: scriptedUsage
4203
- }
4204
- ];
4205
- const plannerDecide = (promptJson) => promptJson.includes("flight-call-1") ? Stream.fromIterable(plannerPlanTurnParts) : Stream.fromIterable(plannerSearchTurnParts);
4206
- /**
4207
- * The P4 planner script (`phase1HappyPathTurns`) as a prompt-aware model: once the search
4208
- * batch is committed history, every later request gets the plan — identical parts, so the DC
4209
- * canonical evidence is byte-equivalent to the DN ScriptedModel run after normalization.
4210
- */
4211
- const phase6PlannerModel = promptAwareModel("travel-planner-phase-4", plannerDecide);
4212
- const releasedPlannerGates = /* @__PURE__ */ new Set();
4213
- /** Release the gated planner model for one `[gate:...]` marker. */
4214
- const releasePhase6PlannerGate = (marker) => {
4215
- releasedPlannerGates.add(marker);
4216
- };
4217
- /** Re-close one gate marker (fresh suites reuse markers safely). */
4218
- const resetPhase6PlannerGate = (marker) => {
4219
- releasedPlannerGates.delete(marker);
4220
- };
4221
- const awaitPlannerGate = (marker) => Effect.gen(function* () {
4222
- while (!releasedPlannerGates.has(marker)) yield* Effect.sleep(Duration.millis(10));
4223
- });
4224
- const gateMarkerFromPrompt = (promptJson) => /\[gate:([^\]]+)\]/.exec(promptJson)?.[1] ?? "unknown-gate";
4225
- /** A trip whose request text carries the gate marker the gated model waits on. */
4226
- const phase6GatedTrip = (marker) => Schema.decodeUnknownSync(TripRequest)({
4227
- request: `Plan a review-only London trip, but wait for the concierge. [gate:${marker}]`,
4228
- origin: "SFO",
4229
- destination: "LHR",
4230
- departOn: "2026-09-14",
4231
- nights: 4,
4232
- travelers: 2,
4233
- budgetCents: 35e4,
4234
- currency: "USD"
4235
- });
4236
- /**
4237
- * The SAME planner behavior with a hanging first response: the model waits on the released
4238
- * gate before answering, keeping its lane durably busy so queue-depth admission limits can be
4239
- * exercised deterministically.
4240
- */
4241
- const phase6GatedPlannerModel = Model.make("scripted", "travel-planner-phase-4-gated", Layer.effect(LanguageModel.LanguageModel, LanguageModel.make({
4242
- generateText: () => Effect.succeed([]),
4243
- streamText: (options) => Stream.unwrap(Effect.sync(() => {
4244
- const promptJson = JSON.stringify(options.prompt);
4245
- return promptJson.includes("flight-call-1") ? plannerDecide(promptJson) : Stream.fromEffectDrain(awaitPlannerGate(gateMarkerFromPrompt(promptJson))).pipe(Stream.concat(plannerDecide(promptJson)));
4246
- }))
4247
- })));
4248
- const sharedSupplierDesk = Effect.runSync(Effect.flatMap(SupplierBookingDesk, Effect.succeed).pipe(Effect.provide(SupplierBookingDesk.layer)));
4249
- /** The shared external supplier desk instance (module-level external truth). */
4250
- const phase6SupplierDesk = sharedSupplierDesk;
4251
- /** Layer handing the shared desk to Bindings, reconcilers, and assertions. */
4252
- const phase6SupplierDeskLayer = Layer.succeed(SupplierBookingDesk, sharedSupplierDesk);
4253
- /**
4254
- * The REAL P5 supplier reconciliation policy over the shared desk, closed to no requirements
4255
- * so a Conversation Object can install it directly: `book_flight` recovers only from supplier
4256
- * truth (absence stays fail-closed `Uncertain` → durable Unknown Outcome), keyed Steps are
4257
- * provably re-enterable.
4258
- */
4259
- const phase6SupplierReconcilerLayer = TravelSupplierReconcilerLayer.pipe(Layer.provide(phase6SupplierDeskLayer));
4260
- const bookingMarkerFromPrompt = (promptJson) => /\[case:([^\]]+)\]/.exec(promptJson)?.[1] ?? "unknown-case";
4261
- /** The deterministic booking Tool Call identity for one `[case:...]` marker. */
4262
- const phase6BookingToolCallId = (marker) => `book-${marker}`;
4263
- /** The bookingRef the supplier desk mints for one marker's approved booking. */
4264
- const phase6BookingRef = (marker) => supplierBookingRefFor(bookFlightIdempotencyKey(phase6BookingToolCallId(marker)));
4265
- /** A trip whose request text carries the per-lane booking case marker. */
4266
- const phase6BookingTrip = (marker) => Schema.decodeUnknownSync(TripRequest)({
4267
- request: `Book the approved London flight for the traveler. [case:${marker}]`,
4268
- origin: "SFO",
4269
- destination: "LHR",
4270
- departOn: "2026-09-14",
4271
- nights: 4,
4272
- travelers: 2,
4273
- budgetCents: 35e4,
4274
- currency: "USD"
4275
- });
4276
- const bookingCallParts = (marker) => [{
4277
- type: "tool-call",
4278
- id: phase6BookingToolCallId(marker),
4279
- name: "book_flight",
4280
- params: {
4281
- quoteId: "quote-sfo-lhr-001",
4282
- travelerRef: `traveler-${marker}`,
4283
- departOn: "2026-09-14"
4284
- },
4285
- providerExecuted: false
4286
- }, {
4287
- type: "finish",
4288
- reason: "tool-calls",
4289
- usage: scriptedUsage
4290
- }];
4291
- const bookingReportParts = (marker) => [
4292
- {
4293
- type: "text-start",
4294
- id: "booking-report"
4295
- },
4296
- {
4297
- type: "text-delta",
4298
- id: "booking-report",
4299
- delta: JSON.stringify({
4300
- summary: "trip booked",
4301
- bookingRefs: [phase6BookingRef(marker)]
4302
- })
4303
- },
4304
- {
4305
- type: "text-end",
4306
- id: "booking-report"
4307
- },
4308
- {
4309
- type: "finish",
4310
- reason: "stop",
4311
- usage: scriptedUsage
4312
- }
4313
- ];
4314
- /**
4315
- * The P5 booking script as a prompt-aware model: request 1 declares the approval-gated
4316
- * `book_flight` call (identity derived from the lane's `[case:...]` marker so supplier
4317
- * idempotency keys never collide across lanes); once that call is committed history, the model
4318
- * writes the booking report.
4319
- */
4320
- const phase6BookingModel = promptAwareModel("travel-planner-phase-5", (promptJson) => {
4321
- const marker = bookingMarkerFromPrompt(promptJson);
4322
- return promptJson.includes(phase6BookingToolCallId(marker)) ? Stream.fromIterable(bookingReportParts(marker)) : Stream.fromIterable(bookingCallParts(marker));
4323
- });
4324
- let guideInvocations = 0;
4325
- /** Deterministic guide-lookup handler executions across every incarnation. */
4326
- const phase6GuideInvocationCount = () => guideInvocations;
4327
- const countingGuideLayer = Layer.succeed(DestinationGuide, DestinationGuide.of({ lookup: (query) => Effect.suspend(() => {
4328
- guideInvocations += 1;
4329
- return destinationLookup(query);
4330
- }) }));
4331
- /** The one-candidate research mission of the DC delegation slice. */
4332
- const phase6ResearchMission = Schema.decodeUnknownSync(ResearchMission)({
4333
- request: "Shortlist one September culture city for the DC delegation slice.",
4334
- candidates: ["LHR"]
4335
- });
4336
- const phase6ResearchDestination = "LHR";
4337
- /** The child's scripted guide-lookup Tool Call identity. */
4338
- const phase6ChildLookupCallId = `lookup-LHR`;
4339
- const coordinatorDelegationParts = [{
4340
- type: "tool-call",
4341
- id: durableResearchCallId,
4342
- name: "delegate_destination_research",
4343
- params: {
4344
- destination: "LHR",
4345
- focus: "museums"
4346
- },
4347
- providerExecuted: false
4348
- }, {
4349
- type: "finish",
4350
- reason: "tool-calls",
4351
- usage: scriptedUsage
4352
- }];
4353
- const coordinatorShortlistParts = [
4354
- {
4355
- type: "text-start",
4356
- id: "shortlist"
4357
- },
4358
- {
4359
- type: "text-delta",
4360
- id: "shortlist",
4361
- delta: JSON.stringify(Schema.encodeSync(DestinationShortlist)(durableResearchShortlist("LHR")))
4362
- },
4363
- {
4364
- type: "text-end",
4365
- id: "shortlist"
4366
- },
4367
- {
4368
- type: "finish",
4369
- reason: "stop",
4370
- usage: scriptedUsage
4371
- }
4372
- ];
4373
- const researcherLookupParts = [{
4374
- type: "tool-call",
4375
- id: phase6ChildLookupCallId,
4376
- name: "lookup_destination",
4377
- params: { destination: "LHR" },
4378
- providerExecuted: false
4379
- }, {
4380
- type: "finish",
4381
- reason: "tool-calls",
4382
- usage: scriptedUsage
4383
- }];
4384
- const researcherReportParts = [
4385
- {
4386
- type: "text-start",
4387
- id: "destination-report"
4388
- },
4389
- {
4390
- type: "text-delta",
4391
- id: "destination-report",
4392
- delta: encodedDestinationReport("LHR")
4393
- },
4394
- {
4395
- type: "text-end",
4396
- id: "destination-report"
4397
- },
4398
- {
4399
- type: "finish",
4400
- reason: "stop",
4401
- usage: scriptedUsage
4402
- }
4403
- ];
4404
- /** Prompt-aware S2 coordinator: delegation call first, shortlist once it is history. */
4405
- const phase6CoordinatorModel = promptAwareModel("travel-coordinator-p6", (promptJson) => promptJson.includes("research-lhr-1") ? Stream.fromIterable(coordinatorShortlistParts) : Stream.fromIterable(coordinatorDelegationParts));
4406
- let researcherGateReleased = false;
4407
- /** Allow the researcher's FIRST model response to proceed (sticky across incarnations). */
4408
- const releasePhase6ResearcherGate = () => {
4409
- researcherGateReleased = true;
4410
- };
4411
- /** Re-close the researcher gate (each delegation scenario starts gated). */
4412
- const resetPhase6ResearcherGate = () => {
4413
- researcherGateReleased = false;
4414
- };
4415
- const awaitResearcherGate = Effect.gen(function* () {
4416
- while (!researcherGateReleased) yield* Effect.sleep(Duration.millis(10));
4417
- });
4418
- /**
4419
- * Prompt-aware S2 researcher: guide lookup first, report once it is history. The FIRST
4420
- * response waits on the researcher gate — a stand-in for real model latency. The child's own
4421
- * Object may legally start its Attempt the moment its routed admission commits, while the
4422
- * parent is still appending the lineage record into the child's log; a child whose first
4423
- * batch commits during that window races the parent's append on one tail. Real models answer
4424
- * in seconds, so establishment always wins that race in production; the gate reproduces that
4425
- * timing deterministically instead of relying on scheduler luck.
4426
- */
4427
- const phase6ResearcherModel = promptAwareModel("destination-researcher-p6", (promptJson) => promptJson.includes(phase6ChildLookupCallId) ? Stream.fromIterable(researcherReportParts) : Stream.fromEffectDrain(awaitResearcherGate).pipe(Stream.concat(Stream.fromIterable(researcherLookupParts))));
4428
- /**
4429
- * Every phase-6 Travel Planner worker Binding, captured with its requirement Contexts
4430
- * (spec/subagents.md §11): the P4 planner and its gated twin, the P5 booking agent over the
4431
- * shared supplier desk, and the S2 coordinator/researcher pair wired through the durable
4432
- * delegation Layer. A Conversation Object registers these via its `bindings` option; the
4433
- * capture runs once per incarnation, and everything stateful the assertions rely on (desk,
4434
- * guide counter, gates) lives at module level so it survives incarnation loss.
4435
- */
4436
- const makePhase6TravelPlannerBindings = Effect.gen(function* () {
4437
- const planner = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase4, phase6PlannerModel), phase4TravelPlannerDefinitionDigests).pipe(Effect.provide(phase4TravelPlannerWorkerLayer));
4438
- const gatedPlanner = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase4, phase6GatedPlannerModel), phase6GatedPlannerDefinitionDigests).pipe(Effect.provide(phase4TravelPlannerWorkerLayer));
4439
- const booking = yield* DurableWorkerBinding.make(Agent.withModel(TravelPlannerPhase5, phase6BookingModel), phase5TravelPlannerDefinitionDigests).pipe(Effect.provide(phase5TravelPlannerWorkerLayer.pipe(Layer.provideMerge(phase6SupplierDeskLayer))));
4440
- const researcherBinding = Agent.withModel(DestinationResearcher, phase6ResearcherModel);
4441
- const childToolkitLayer = DestinationResearcherToolkitLayer.pipe(Layer.provideMerge(countingGuideLayer));
4442
- return [
4443
- planner,
4444
- gatedPlanner,
4445
- booking,
4446
- yield* DurableWorkerBinding.make(Agent.withModel(TravelCoordinator, phase6CoordinatorModel), s2CoordinatorDigests).pipe(Effect.provide(durableDestinationResearchHandlersLayer(researcherBinding).pipe(Layer.provide(Layer.mergeAll(childToolkitLayer, SubagentReservationsMemoryLive, DeterministicIdGeneratorLayer, ResearchDispatchGate.layerOpen))))),
4447
- yield* DurableWorkerBinding.make(researcherBinding, s2ResearcherDigests).pipe(Effect.provide(childToolkitLayer))
4448
- ];
4449
- });
4450
- /**
4451
- * The committed cross-platform normalized canonical evidence of ONE uninterrupted Travel
4452
- * Planner planning Submission (the P1/P4 happy path: canonical input, the search Turn, three
4453
- * Tool settlements, the plan Turn, one Settlement). `travel-planner-phase6.test.ts` asserts
4454
- * the DN run equals this value and `travel-planner-dc.test.ts` asserts the DC run equals this
4455
- * value, so the two platforms' canonical outcomes are byte-equivalent transitively — the P6
4456
- * exit gate "Travel Planner produces equivalent canonical outcomes under DN and DC".
4457
- *
4458
- * Regenerate ONLY when the Travel Planner scenario itself changes, by printing either suite's
4459
- * normalized value; both suites must then agree on the new golden.
4460
- */
4461
- const phase6TravelPlannerGoldenEvidence = [
4462
- {
4463
- batchId: "conversation-created:{conversationId}",
4464
- sequence: 1,
4465
- record: {
4466
- recordId: "conversation-created:{conversationId}",
4467
- family: "conversation",
4468
- schemaVersion: 1,
4469
- createdAt: "{timestamp}",
4470
- deploymentId: "{deploymentId}",
4471
- payload: {
4472
- _tag: "ConversationCreated",
4473
- agentId: "travel-planner-phase-4",
4474
- definitions: {
4475
- agent: "{digest}",
4476
- model: "{digest}",
4477
- tools: "{digest}"
4478
- }
4479
- }
4480
- }
4481
- },
4482
- {
4483
- batchId: "submission-input:{submissionId}",
4484
- sequence: 2,
4485
- record: {
4486
- recordId: "input:{submissionId}",
4487
- family: "conversation",
4488
- schemaVersion: 1,
4489
- createdAt: "{timestamp}",
4490
- deploymentId: "{deploymentId}",
4491
- payload: {
4492
- _tag: "UserInputRecorded",
4493
- submissionId: "{submissionId}",
4494
- kind: "user",
4495
- runId: "run:{submissionId}",
4496
- input: {
4497
- request: "Plan a review-only London trip using the deterministic flight, lodging, and activity searches.",
4498
- origin: "SFO",
4499
- destination: "LHR",
4500
- departOn: "2026-09-14",
4501
- nights: 4,
4502
- travelers: 2,
4503
- budgetCents: 35e4,
4504
- currency: "USD"
4505
- }
4506
- }
4507
- }
4508
- },
4509
- {
4510
- batchId: "turn-response:run:{submissionId}:1",
4511
- sequence: 3,
4512
- record: {
4513
- recordId: "model-response:run:{submissionId}:1",
4514
- family: "conversation",
4515
- schemaVersion: 1,
4516
- createdAt: "{timestamp}",
4517
- deploymentId: "{deploymentId}",
4518
- payload: {
4519
- _tag: "ModelResponseRecorded",
4520
- runId: "run:{submissionId}",
4521
- turnId: "turn:run:{submissionId}:1",
4522
- turn: 1,
4523
- inputTokens: 128,
4524
- outputTokens: 96,
4525
- messages: { content: [
4526
- {
4527
- options: {},
4528
- role: "system",
4529
- 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."
4530
- },
4531
- {
4532
- options: {},
4533
- role: "user",
4534
- 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\"}"
4535
- },
4536
- {
4537
- options: {},
4538
- role: "assistant",
4539
- content: [
4540
- {
4541
- options: {},
4542
- type: "tool-call",
4543
- id: "flight-call-1",
4544
- name: "search_flights",
4545
- params: {
4546
- origin: "SFO",
4547
- destination: "LHR",
4548
- departOn: "2026-09-14",
4549
- travelers: 2
4550
- },
4551
- providerExecuted: false
4552
- },
4553
- {
4554
- options: {},
4555
- type: "tool-call",
4556
- id: "lodging-call-1",
4557
- name: "search_lodging",
4558
- params: {
4559
- destination: "LHR",
4560
- departOn: "2026-09-14",
4561
- nights: 4,
4562
- travelers: 2
4563
- },
4564
- providerExecuted: false
4565
- },
4566
- {
4567
- options: {},
4568
- type: "tool-call",
4569
- id: "activity-call-1",
4570
- name: "search_activities",
4571
- params: {
4572
- destination: "LHR",
4573
- departOn: "2026-09-14",
4574
- nights: 4,
4575
- travelers: 2
4576
- },
4577
- providerExecuted: false
4578
- }
4579
- ]
4580
- }
4581
- ] },
4582
- messagesDigest: "{digest}"
4583
- }
4584
- }
4585
- },
4586
- {
4587
- batchId: "turn-results:run:{submissionId}:1",
4588
- sequence: 4,
4589
- record: {
4590
- recordId: "tool-settled:run:{submissionId}:1:flight-call-1",
4591
- family: "conversation",
4592
- schemaVersion: 1,
4593
- createdAt: "{timestamp}",
4594
- deploymentId: "{deploymentId}",
4595
- payload: {
4596
- _tag: "ToolCallSettled",
4597
- runId: "run:{submissionId}",
4598
- toolCallId: "flight-call-1",
4599
- toolName: "search_flights",
4600
- result: {
4601
- quoteId: "quote-sfo-lhr-001",
4602
- flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
4603
- estimatedCents: 18e4,
4604
- currency: "USD"
4605
- },
4606
- isFailure: false
4607
- }
4608
- }
4609
- },
4610
- {
4611
- batchId: "turn-results:run:{submissionId}:1",
4612
- sequence: 5,
4613
- record: {
4614
- recordId: "tool-settled:run:{submissionId}:1:lodging-call-1",
4615
- family: "conversation",
4616
- schemaVersion: 1,
4617
- createdAt: "{timestamp}",
4618
- deploymentId: "{deploymentId}",
4619
- payload: {
4620
- _tag: "ToolCallSettled",
4621
- runId: "run:{submissionId}",
4622
- toolCallId: "lodging-call-1",
4623
- toolName: "search_lodging",
4624
- result: {
4625
- lodging: "Bloomsbury House · refundable studio · 4 nights",
4626
- estimatedCents: 104e3,
4627
- currency: "USD"
4628
- },
4629
- isFailure: false
4630
- }
4631
- }
4632
- },
4633
- {
4634
- batchId: "turn-results:run:{submissionId}:1",
4635
- sequence: 6,
4636
- record: {
4637
- recordId: "tool-settled:run:{submissionId}:1:activity-call-1",
4638
- family: "conversation",
4639
- schemaVersion: 1,
4640
- createdAt: "{timestamp}",
4641
- deploymentId: "{deploymentId}",
4642
- payload: {
4643
- _tag: "ToolCallSettled",
4644
- runId: "run:{submissionId}",
4645
- toolCallId: "activity-call-1",
4646
- toolName: "search_activities",
4647
- result: { activities: ["British Museum timed entry", "Thames evening walk"] },
4648
- isFailure: false
4649
- }
4650
- }
4651
- },
4652
- {
4653
- batchId: "turn:run:{submissionId}:2",
4654
- sequence: 7,
4655
- record: {
4656
- recordId: "model-response:run:{submissionId}:2",
4657
- family: "conversation",
4658
- schemaVersion: 1,
4659
- createdAt: "{timestamp}",
4660
- deploymentId: "{deploymentId}",
4661
- payload: {
4662
- _tag: "ModelResponseRecorded",
4663
- runId: "run:{submissionId}",
4664
- turnId: "turn:run:{submissionId}:2",
4665
- turn: 2,
4666
- inputTokens: 128,
4667
- outputTokens: 96,
4668
- messages: { content: [{
4669
- options: {},
4670
- role: "assistant",
4671
- 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\"}]}"
4672
- }] },
4673
- messagesDigest: "{digest}"
4674
- }
4675
- }
4676
- },
4677
- {
4678
- batchId: "submission-settlement:{submissionId}",
4679
- sequence: 8,
4680
- record: {
4681
- recordId: "settlement:{submissionId}",
4682
- family: "conversation",
4683
- schemaVersion: 1,
4684
- createdAt: "{timestamp}",
4685
- deploymentId: "{deploymentId}",
4686
- payload: {
4687
- _tag: "SubmissionSettled",
4688
- submissionId: "{submissionId}",
4689
- settlementId: "settlement:{submissionId}",
4690
- receiptId: "{receiptId}",
4691
- outcome: "completed",
4692
- runId: "run:{submissionId}",
4693
- result: { itineraries: [{
4694
- title: "Westward light, eastbound overnight",
4695
- route: "San Francisco → London",
4696
- dates: "14–19 September 2026",
4697
- flight: "EA 218 · nonstop · SFO 18:40 → LHR 13:05+1",
4698
- lodging: "Bloomsbury House · refundable studio · 4 nights",
4699
- activities: ["British Museum timed entry", "Thames evening walk"],
4700
- estimatedTotalCents: 284e3,
4701
- currency: "USD",
4702
- quoteId: "quote-sfo-lhr-001",
4703
- assumptions: ["Two travelers sharing one studio", "Quote is read-only availability, not a reservation"],
4704
- unresolvedConstraints: ["Traveler names and accessibility requests are intentionally omitted"],
4705
- nextAction: "review"
4706
- }] }
4707
- }
4708
- }
4709
- }
4710
- ];
4711
- //#endregion
4712
- //#region src/fixtures/travel-planner/phase7.ts
4713
- /**
4714
- * The P7 dual-profile claim, schema-first so the exact scope of "live
4715
- * integration profiles" is a committed, decodable value:
4716
- *
4717
- * - `offlineConformanceDeterministic` / `offlineRequiresCredentials`: the
4718
- * cumulative conformance suites stay deterministic and credential-free.
4719
- * - `liveProfileOptIn`: live suites are excluded from ordinary gates by the
4720
- * environment predicate (`phase7LiveProfileEnabled`), never by test-runner
4721
- * configuration that could silently drift.
4722
- * - `liveModelLayers` / `liveSupplierLayers`: live profiles exercise real
4723
- * model Layers over the SAME deterministic supplier desk — no claim of a
4724
- * live supplier integration is made anywhere (decision 9).
4725
- * - `structurallyRedactedTranscripts`: transcript evidence a live profile
4726
- * emits passes through the structural `Redactor` first (SEC-008,
4727
- * testing.md §12: "live model and supplier profiles are opt-in smoke or
4728
- * release tests, rate-limited and structurally redacted").
4729
- * - `exactlyOnceExternalEffects`: never claimed at any phase (DUR-003).
4730
- */
4731
- var TravelPlannerPhase7Profile = class extends Schema.Class("@effect-agent/testing/travel-planner/TravelPlannerPhase7Profile")({
4732
- phase: Schema.Literal("P7"),
4733
- offlineConformanceDeterministic: Schema.Literal(true),
4734
- offlineRequiresCredentials: Schema.Literal(false),
4735
- liveProfileOptIn: Schema.Literal(true),
4736
- liveModelLayers: Schema.Literal(true),
4737
- liveSupplierLayers: Schema.Literal(false),
4738
- structurallyRedactedTranscripts: Schema.Literal(true),
4739
- exactlyOnceExternalEffects: Schema.Literal(false)
4740
- }) {};
4741
- const phase7TravelPlannerProfile = TravelPlannerPhase7Profile.make({
4742
- phase: "P7",
4743
- offlineConformanceDeterministic: true,
4744
- offlineRequiresCredentials: false,
4745
- liveProfileOptIn: true,
4746
- liveModelLayers: true,
4747
- liveSupplierLayers: false,
4748
- structurallyRedactedTranscripts: true,
4749
- exactlyOnceExternalEffects: false
4750
- });
4751
- /**
4752
- * The one opt-in switch for EVERY live profile in this repository. `"1"` is
4753
- * the only enabling value: an unset, empty, or differently-truthy value keeps
4754
- * the suite skipped, so CI and ordinary developer runs stay offline.
4755
- */
4756
- const PHASE7_LIVE_GATE_ENV = "EFFECT_AGENT_LIVE";
4757
- /** The credential a Travel Planner live-model profile additionally requires. */
4758
- const PHASE7_LIVE_CREDENTIAL_ENV = "OPENAI_API_KEY";
4759
- /**
4760
- * The test-side live gate (P7 plan §6: no test-side live-gating pattern
4761
- * existed before this — the demo gates at serve time via
4762
- * `Config.redacted("OPENAI_API_KEY")`). Suites use it as
4763
- * `describe.skipIf(!phase7LiveProfileEnabled(process.env))`, which keeps the
4764
- * live block out of ordinary gates while the SAME file's ungated tests keep
4765
- * pinning the profile schema on every run.
4766
- */
4767
- const phase7LiveProfileEnabled = (env) => env["EFFECT_AGENT_LIVE"] === "1" && (env["OPENAI_API_KEY"] ?? "") !== "";
4768
- //#endregion
4769
- export { ActivityCatalog, ActivityCatalogLayer, ActivityQuery, ActivitySearchResult, ActivityUnavailable, AirportCode, BookFlight, BookItinerary, BookingRef, BoundedSummary, CERTIFICATION_SCENARIOS, CancelBooking, CancelBookingRequest, CancellationConfirmation, CatalogLifecycle, CatalogLifecycleCounts, ChaosApprovalDecision, ChaosConvergenceFailure, ChaosLaneReport, ChaosPlan, ChaosPlanReport, ChaosResolutionKind, ChaosScenarioKind, ChaosSubmissionSpec, CodeExecutorConformanceViolation, DEFAULT_CHAOS_SEED, DestinationBrief, DestinationFacts, DestinationGuide, DestinationGuideLayer, DestinationGuideUnavailable, DestinationQuery, DestinationRecommendation, DestinationReport, DestinationResearchFailed, DestinationResearchFindings, DestinationResearchRequest, DestinationResearchSupportLayer, DestinationResearcher, DestinationResearcherToolkit, DestinationResearcherToolkitLayer, DestinationShortlist, DeterministicIdGeneratorLayer, DocContentToolkit, DocSummarizer, DocsMcpDiscoveryEvidence, DocsResearcher, DocsResearcherToolkit, DocumentLibrary, DocumentQuery, DocumentSummary, DocumentSummaryFailed, DocumentUnavailable, DurableSearchActivities, DurableSearchFlights, DurableSearchLodging, FetchDocument, 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, ResearchDigest, ResearchDispatchGate, ResearchDocument, ResearchDocumentId, ResearchMission, ResearchRequest, ReverseCompletionToolkitLayer, ScriptedGeneratePart, ScriptedGenerateTurn, ScriptedModel, ScriptedStreamPart, ScriptedStreamTermination, ScriptedStreamTurn, ScriptedTurn, SearchActivities, SearchFlights, SearchLodging, SummaryBrief, SummaryFinding, SummaryRequest, SupplierBookingConfirmation, SupplierBookingDesk, SupplierBookingRecord, SupplierOperation, SupplierUnavailable, TIER2_UNREACHED_LOCATIONS, TravelBookingReport, TravelCoordinator, TravelCoordinatorToolkit, TravelGuidance, TravelGuidanceLayer, TravelPlan, TravelPlanner, TravelPlannerBookingEvidenceError, TravelPlannerBookingProfile, TravelPlannerCloudflareProfile, TravelPlannerDurabilityProfile, TravelPlannerDurableEvidenceError, TravelPlannerPersistenceProfile, TravelPlannerPhase2, TravelPlannerPhase2Toolkit, TravelPlannerPhase2ToolkitLayer, TravelPlannerPhase4, TravelPlannerPhase4Toolkit, TravelPlannerPhase4ToolkitLayer, TravelPlannerPhase5, TravelPlannerPhase5Toolkit, TravelPlannerPhase5ToolkitLayer, TravelPlannerPhase7Profile, TravelPlannerProjectionError, TravelPlannerRuntimeLayer, TravelPlannerSubagentDurabilityProfile, TravelPlannerToolkit, TravelPlannerToolkitLayer, TravelSupplierReconcilerLayer, TravelerRef, TripRequest, assertDiscoveryMatchesAuthoredToolkit, assertSettledBookingsExistAtSupplier, bookFlightIdempotencyKey, cancelBookingIdempotencyKey, certifyDurableAdapters, chaosSeedFromEnv, codeExecutorConformanceCases, coordinatorConfidentialMarker, coordinatorResearchTurn, coordinatorShortlistTurn, delegateDocumentSummary, destinationLookup, destinationReportFor, destinationResearchDelegation, destinationResearchHandlersLayer, destinationResearchPolicy, docContentToolkitLayer, docsCoordinatorConfidentialMarker, docsCoordinatorDigests, docsDocumentBodySecret, docsMcpConnectorLayer, docsMcpIdentity, docsMcpMismatchedConnectorLayer, docsMcpOversizedConnectorLayer, docsMcpRequest, docsMissionConfidentialMarker, docsResearcherDeploymentId, docsResearcherPrincipal, docsResearcherProducerId, docsResearcherSubmitAgent, docsResearcherSubmitOptions, docsSummarizerDigestStrings, docsSummarizerDigests, docsSummaryHandlersLayer, documentBodyPhrase, documentSummaryFor, documentSummaryPolicy, durableChildLookupCallId, durableDestinationResearchHandlersLayer, durableResearchAllocation, durableResearchCallId, durableResearchFinding, durableResearchShortlist, encodedDestinationFacts, encodedDestinationReport, encodedDocumentSummary, expectedDestinationShortlist, expectedResearchDigest, expectedTravelPlan, fetchCallId, generateChaosPlans, inProcessCodeExecutorImplementation, inProcessCodeExecutorLayer, itineraryStepIdempotencyKey, makeDestinationResearcherModel, makeDocsResearcherHarness, makeDurableResearchHarness, makeInvocationCountingModel, makePhase3TravelPlannerCheckpoint, makePhase4TravelPlannerAgent, makePhase6TravelPlannerBindings, mapResearchChildFailure, mapSummaryChildFailure, missionConfidentialMarker, normalizeCrossPlatformTravelPlannerEvidence, normalizeDurableTravelPlannerEvidence, phase0HappyPathTurns, phase0Trip, phase1HappyPathTurns, phase1Trip, phase3TravelPlannerBatches, phase3TravelPlannerCompletionBatch, phase3TravelPlannerConversationId, phase3TravelPlannerDefinitionDigests, phase3TravelPlannerEncodedFixture, phase3TravelPlannerInitialBatch, phase3TravelPlannerProducerId, phase3TravelPlannerProfile, phase3TravelPlannerRunId, phase4TravelPlannerDefinitionDigests, phase4TravelPlannerDeploymentId, phase4TravelPlannerPrincipal, phase4TravelPlannerProducerId, phase4TravelPlannerProfile, phase4TravelPlannerSubmitOptions, phase4TravelPlannerWorkerLayer, phase5TravelPlannerDefinitionDigests, phase5TravelPlannerDeploymentId, phase5TravelPlannerPrincipal, phase5TravelPlannerProducerId, phase5TravelPlannerProfile, 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, phase6TravelPlannerProfile, phase7LiveProfileEnabled, phase7TravelPlannerProfile, redactedDocumentPreview, releasePhase6PlannerGate, releasePhase6ResearcherGate, researchCorpusDocumentIds, researchDocumentFor, researchDocumentLookup, researchMission, researchMissionRequest, researcherHappyPathTurns, resetPhase6PlannerGate, resetPhase6ResearcherGate, resolveTierThree, runChaosPlan, s2CoordinatorDigests, s2CoordinatorSubmitAgent, s2ResearcherDigestStrings, s2ResearcherDigests, s2TravelPlannerDeploymentId, s2TravelPlannerPrincipal, s2TravelPlannerProducerId, s2TravelPlannerProfile, s2TravelPlannerSubmitOptions, summarizeCallId, supplierBookingRefFor, tier2NeverFiredLocations, travelPlanFromDurableSettlement, travelPlanFromProjection };
4770
-
4771
- //# sourceMappingURL=index.mjs.map
1
+ import { t as ScriptedModel_exports } from "./ScriptedModel.mjs";
2
+ export { ScriptedModel_exports as ScriptedModel };