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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/Certification.d.mts +106 -0
  2. package/dist/Certification.mjs +633 -0
  3. package/dist/Certification.mjs.map +1 -0
  4. package/dist/Chaos.d.mts +126 -0
  5. package/dist/Chaos.mjs +755 -0
  6. package/dist/Chaos.mjs.map +1 -0
  7. package/dist/CodeExecutorConformance.d.mts +33 -0
  8. package/dist/CodeExecutorConformance.mjs +231 -0
  9. package/dist/CodeExecutorConformance.mjs.map +1 -0
  10. package/dist/CodeExecutorSubstitute.d.mts +21 -0
  11. package/dist/CodeExecutorSubstitute.mjs +368 -0
  12. package/dist/CodeExecutorSubstitute.mjs.map +1 -0
  13. package/dist/DocsResearcher.d.mts +276 -0
  14. package/dist/DocsResearcher.mjs +490 -0
  15. package/dist/DocsResearcher.mjs.map +1 -0
  16. package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
  17. package/dist/ScriptedModel.d.mts +2 -0
  18. package/dist/ScriptedModel.mjs +155 -0
  19. package/dist/ScriptedModel.mjs.map +1 -0
  20. package/dist/TravelPlanner.d.mts +1680 -0
  21. package/dist/TravelPlanner.mjs +1963 -0
  22. package/dist/TravelPlanner.mjs.map +1 -0
  23. package/dist/deterministic-layers-D5owIoke.mjs +358 -0
  24. package/dist/deterministic-layers-D5owIoke.mjs.map +1 -0
  25. package/dist/index.d.mts +2 -3408
  26. package/dist/index.mjs +2 -4771
  27. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  28. package/package.json +1 -48
  29. package/src/{certification.ts → Certification.ts} +366 -171
  30. package/src/{chaos.ts → Chaos.ts} +241 -127
  31. package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +30 -7
  32. package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +113 -46
  33. package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
  34. package/src/{scripted-model.ts → ScriptedModel.ts} +25 -29
  35. package/src/TravelPlanner.ts +232 -0
  36. package/src/fixtures/docs-researcher/definition.ts +20 -11
  37. package/src/fixtures/docs-researcher/harness.ts +41 -34
  38. package/src/fixtures/docs-researcher/mcp.ts +49 -3
  39. package/src/fixtures/travel-planner/definition.ts +15 -2
  40. package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
  41. package/src/fixtures/travel-planner/phase2.ts +4 -3
  42. package/src/fixtures/travel-planner/phase3.ts +19 -42
  43. package/src/fixtures/travel-planner/phase4.ts +24 -37
  44. package/src/fixtures/travel-planner/phase5.ts +35 -42
  45. package/src/fixtures/travel-planner/phase6.ts +191 -88
  46. package/src/fixtures/travel-planner/phase7.ts +4 -102
  47. package/src/fixtures/travel-planner/scenarios.ts +3 -4
  48. package/src/fixtures/travel-planner/subagents-durable.ts +35 -61
  49. package/src/fixtures/travel-planner/subagents.ts +36 -14
  50. package/src/index.ts +1 -11
  51. package/src/internal/certification-report.ts +25 -0
  52. package/dist/index.mjs.map +0 -1
  53. package/src/code-executor-conformance.d.ts +0 -30
  54. package/src/fixtures/travel-planner/index.ts +0 -11
  55. package/src/fixtures/warehouse/index.ts +0 -412
@@ -1,45 +1,27 @@
1
- import { AgentId, ConversationId, RunId, SubmissionId } from "@effect-agent/core";
1
+ import { Effect, Schema } from "effect";
2
+ import { AgentId, ThreadId, RunId, SubmissionId } from "effect-agent/identifiers";
2
3
  import {
3
4
  BatchId,
4
5
  CanonicalBatch,
5
- ConversationCheckpoint,
6
- ConversationProjection,
7
6
  DefinitionDigests,
8
7
  DeploymentId,
9
8
  Digest,
10
9
  ProducerId,
11
10
  RecordEnvelope,
12
11
  RecordId,
13
- } from "@effect-agent/session";
14
- import { Effect, Schema } from "effect";
12
+ } from "effect-agent/records";
13
+ import { ThreadProjection } from "effect-agent/thread-projection";
14
+ import { ThreadCheckpoint } from "effect-agent/thread-store";
15
15
 
16
16
  import { TravelPlan, TripRequest } from "./definition.ts";
17
17
  import { expectedTravelPlan, phase1Trip } from "./scenarios.ts";
18
18
 
19
- /**
20
- * The Phase 3 profile persists Conversation history but deliberately does not
21
- * claim durable admission or recovery of accepted work.
22
- */
23
- export class TravelPlannerPersistenceProfile extends Schema.Class<TravelPlannerPersistenceProfile>(
24
- "@effect-agent/testing/travel-planner/TravelPlannerPersistenceProfile",
25
- )({
26
- deploymentClass: Schema.Literal("P"),
27
- durableAcceptedWork: Schema.Literal(false),
28
- canonicalSchemaVersion: Schema.Literal(1),
29
- }) {}
30
-
31
- export const phase3TravelPlannerProfile = TravelPlannerPersistenceProfile.make({
32
- deploymentClass: "P",
33
- durableAcceptedWork: false,
34
- canonicalSchemaVersion: 1,
35
- });
19
+ export const phase3TravelPlannerThreadId = Schema.decodeSync(ThreadId)("travel-planner-p3-thread");
36
20
 
37
- export const phase3TravelPlannerConversationId = Schema.decodeSync(ConversationId)(
38
- "travel-planner-p3-conversation",
39
- );
40
21
  export const phase3TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
41
22
  "travel-planner-p3-producer",
42
23
  );
24
+
43
25
  export const phase3TravelPlannerRunId = Schema.decodeSync(RunId)("travel-planner-p3-run");
44
26
 
45
27
  const deploymentId = Schema.decodeSync(DeploymentId)("travel-planner-p3-scripted");
@@ -61,7 +43,7 @@ const travelPlanOutput = Schema.encodeSync(TravelPlan)(expectedTravelPlan);
61
43
  const record = (recordId: string, createdAt: string, payload: unknown) =>
62
44
  Schema.decodeUnknownSync(RecordEnvelope)({
63
45
  recordId: Schema.decodeSync(RecordId)(recordId),
64
- family: "conversation",
46
+ family: "thread",
65
47
  schemaVersion: 1,
66
48
  createdAt,
67
49
  deploymentId,
@@ -69,7 +51,7 @@ const record = (recordId: string, createdAt: string, payload: unknown) =>
69
51
  });
70
52
 
71
53
  /**
72
- * The first atomic append establishes the Conversation and records its input.
54
+ * The first atomic append establishes the Thread and records its input.
73
55
  * Its encoded value is the redacted current-version persistence fixture.
74
56
  */
75
57
  export const phase3TravelPlannerInitialBatch = CanonicalBatch.make({
@@ -77,7 +59,7 @@ export const phase3TravelPlannerInitialBatch = CanonicalBatch.make({
77
59
  producerId: phase3TravelPlannerProducerId,
78
60
  records: [
79
61
  record("travel-planner-p3-created", "2026-09-01T00:00:00.000Z", {
80
- _tag: "ConversationCreated",
62
+ _tag: "ThreadCreated",
81
63
  agentId,
82
64
  definitions: phase3TravelPlannerDefinitionDigests,
83
65
  }),
@@ -115,9 +97,8 @@ export const phase3TravelPlannerBatches = [
115
97
  ] as const;
116
98
 
117
99
  /** Portable current-version fixture; it contains no passenger identity or credentials. */
118
- export const phase3TravelPlannerEncodedFixture = Schema.encodeSync(Schema.Array(CanonicalBatch))(
119
- phase3TravelPlannerBatches,
120
- );
100
+ export const phase3TravelPlannerEncodedFixture: ReadonlyArray<typeof CanonicalBatch.Encoded> =
101
+ Schema.encodeSync(Schema.Array(CanonicalBatch))(phase3TravelPlannerBatches);
121
102
 
122
103
  export class TravelPlannerProjectionError extends Schema.TaggedError<TravelPlannerProjectionError>()(
123
104
  "TravelPlannerProjectionError",
@@ -126,9 +107,10 @@ export class TravelPlannerProjectionError extends Schema.TaggedError<TravelPlann
126
107
 
127
108
  /** Decode the itinerary projection rebuilt from canonical model-completion records. */
128
109
  export const travelPlanFromProjection = (
129
- projection: ConversationProjection,
110
+ projection: ThreadProjection,
130
111
  ): Effect.Effect<TravelPlan, TravelPlannerProjectionError> => {
131
112
  const output = projection.modelOutputs.at(-1);
113
+
132
114
  if (output === undefined) {
133
115
  return Effect.fail(
134
116
  TravelPlannerProjectionError.make({
@@ -136,24 +118,19 @@ export const travelPlanFromProjection = (
136
118
  }),
137
119
  );
138
120
  }
121
+
139
122
  return Schema.decodeUnknownEffect(TravelPlan)(output).pipe(
140
123
  Effect.mapError((error) => TravelPlannerProjectionError.make({ message: error.message })),
141
124
  );
142
125
  };
143
126
 
144
127
  /** Build a disposable checkpoint bound to a validated canonical prefix. */
145
- export const makePhase3TravelPlannerCheckpoint = (
146
- projection: ConversationProjection,
147
- ): ConversationCheckpoint =>
148
- Schema.decodeSync(ConversationCheckpoint)({
128
+ export const makePhase3TravelPlannerCheckpoint = (projection: ThreadProjection): ThreadCheckpoint =>
129
+ Schema.decodeSync(ThreadCheckpoint)({
149
130
  schemaVersion: 1,
150
- conversationId: projection.conversationId,
131
+ threadId: projection.threadId,
151
132
  throughSequence: projection.throughSequence,
152
133
  tailDigest: projection.tailDigest,
153
- engineVersion: "phase-3-test-runtime",
154
- agentDefinitionDigest: phase3TravelPlannerDefinitionDigests.agent,
155
- modelDigest: phase3TravelPlannerDefinitionDigests.model,
156
- toolDigest: phase3TravelPlannerDefinitionDigests.tools,
157
- state: Schema.encodeSync(ConversationProjection)(projection),
134
+ state: Schema.encodeSync(ThreadProjection)(projection),
158
135
  createdAt: "2026-09-01T00:00:04.000Z",
159
136
  });
@@ -1,20 +1,20 @@
1
- import { Agent, AgentPolicy, type ConversationId } from "@effect-agent/core";
2
- import { ToolExecutionClass } from "@effect-agent/engine";
1
+ import { Effect, Layer, Schema } from "effect";
2
+ import * as Agent from "effect-agent/agent";
3
+ import { AgentPolicy } from "effect-agent/agent-policy";
4
+ import { type DurableSubmitOptions, type Receipt } from "effect-agent/durable-agent-runtime";
5
+ import { ToolExecutionClass } from "effect-agent/durable-step";
6
+ import { type ThreadId } from "effect-agent/identifiers";
3
7
  import {
4
8
  CanonicalRecordEnvelope,
5
9
  DefinitionDigests,
6
10
  DeploymentId,
7
11
  Digest,
8
- Principal,
9
12
  ProducerId,
10
- type DurableSubmitOptions,
11
- type IdempotencyKey,
12
- type Receipt,
13
- } from "@effect-agent/session";
14
- import { Effect, Layer, Schema } from "effect";
13
+ } from "effect-agent/records";
14
+ import { Principal, type IdempotencyKey } from "effect-agent/submission-ledger";
15
15
  import { Model, Tool, Toolkit } from "effect/unstable/ai";
16
16
 
17
- import { ScriptedModel, type ScriptedTurnInput } from "../../scripted-model.ts";
17
+ import { ScriptedModel, type ScriptedTurnInput } from "../../ScriptedModel.ts";
18
18
  import {
19
19
  ActivityCatalog,
20
20
  ActivityQuery,
@@ -41,35 +41,14 @@ import {
41
41
  } from "./deterministic-layers.ts";
42
42
  import { phase1HappyPathTurns } from "./scenarios.ts";
43
43
 
44
- /**
45
- * The Phase 4 profile claims durable accepted work on the Node/SQLite runtime (deployment class
46
- * DN): once `submit` returns a Receipt, the Submission settles exactly once even across process
47
- * loss. The claim is limited to safe-to-repeat toolkits (D6): supplier booking is explicitly NOT
48
- * claimed safely replayable — replay-safe external mutation is P5 (Durable Tools) scope.
49
- */
50
- export class TravelPlannerDurabilityProfile extends Schema.Class<TravelPlannerDurabilityProfile>(
51
- "@effect-agent/testing/travel-planner/TravelPlannerDurabilityProfile",
52
- )({
53
- deploymentClass: Schema.Literal("DN"),
54
- durableAcceptedWork: Schema.Literal(true),
55
- canonicalSchemaVersion: Schema.Literal(1),
56
- /** Supplier booking replay safety is P5 (Durable Tools) scope; DN does not claim it. */
57
- supplierBookingReplaySafe: Schema.Literal(false),
58
- }) {}
59
-
60
- export const phase4TravelPlannerProfile = TravelPlannerDurabilityProfile.make({
61
- deploymentClass: "DN",
62
- durableAcceptedWork: true,
63
- canonicalSchemaVersion: 1,
64
- supplierBookingReplaySafe: false,
65
- });
66
-
67
44
  export const phase4TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
68
45
  "travel-planner-p4-deployment",
69
46
  );
47
+
70
48
  export const phase4TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
71
49
  "travel-planner-p4-producer",
72
50
  );
51
+
73
52
  export const phase4TravelPlannerPrincipal = Schema.decodeSync(Principal)(
74
53
  "travel-planner-p4-principal",
75
54
  );
@@ -85,10 +64,10 @@ export const phase4TravelPlannerDefinitionDigests = DefinitionDigests.make({
85
64
 
86
65
  /** Durable admission options for one Travel Planner Submission on one trip lane. */
87
66
  export const phase4TravelPlannerSubmitOptions = (
88
- conversationId: ConversationId,
67
+ threadId: ThreadId,
89
68
  idempotencyKey: IdempotencyKey,
90
69
  ): DurableSubmitOptions => ({
91
- conversationId,
70
+ threadId,
92
71
  principal: phase4TravelPlannerPrincipal,
93
72
  idempotencyKey,
94
73
  definitions: phase4TravelPlannerDefinitionDigests,
@@ -112,6 +91,7 @@ export const DurableSearchFlights = Tool.make("search_flights", {
112
91
  failureMode: "error",
113
92
  dependencies: [FlightCatalog],
114
93
  }).annotate(ToolExecutionClass, "readonly");
94
+
115
95
  export const DurableSearchLodging = Tool.make("search_lodging", {
116
96
  parameters: Schema.Struct(LodgingQuery.fields),
117
97
  success: LodgingOption,
@@ -119,6 +99,7 @@ export const DurableSearchLodging = Tool.make("search_lodging", {
119
99
  failureMode: "error",
120
100
  dependencies: [LodgingCatalog],
121
101
  }).annotate(ToolExecutionClass, "readonly");
102
+
122
103
  export const DurableSearchActivities = Tool.make("search_activities", {
123
104
  parameters: Schema.Struct(ActivityQuery.fields),
124
105
  success: ActivitySearchResult,
@@ -147,7 +128,7 @@ export const TravelPlannerPhase4ToolkitLayer = TravelPlannerPhase4Toolkit.toLaye
147
128
  * runtime. The searches are read-only and safe to repeat across Attempts (D6); supplier booking
148
129
  * is deliberately absent because DN does NOT claim replay-safe external mutation (P5 scope).
149
130
  */
150
- export const TravelPlannerPhase4 = Agent.define("travel-planner-phase-4", {
131
+ export const TravelPlannerPhase4 = Agent.make("travel-planner-phase-4", {
151
132
  input: TripRequest,
152
133
  output: TravelPlan,
153
134
  instructions: (input) =>
@@ -208,10 +189,12 @@ export const travelPlanFromDurableSettlement = Effect.fn(
208
189
  const settlements = records.flatMap((envelope) =>
209
190
  envelope.record.payload._tag === "SubmissionSettled" ? [envelope.record.payload] : [],
210
191
  );
192
+
211
193
  const settled = settlements.at(0);
194
+
212
195
  if (settled === undefined) {
213
196
  return yield* TravelPlannerDurableEvidenceError.make({
214
- message: "The canonical Conversation Log has no SubmissionSettled record.",
197
+ message: "The canonical Thread Log has no SubmissionSettled record.",
215
198
  });
216
199
  }
217
200
  if (settled.outcome !== "completed" || settled.result === undefined) {
@@ -219,6 +202,7 @@ export const travelPlanFromDurableSettlement = Effect.fn(
219
202
  message: `The Submission settled ${settled.outcome} without a completed itinerary result.`,
220
203
  });
221
204
  }
205
+
222
206
  return yield* Schema.decodeUnknownEffect(TravelPlan)(settled.result).pipe(
223
207
  Effect.mapError((error) =>
224
208
  TravelPlannerDurableEvidenceError.make({
@@ -235,7 +219,7 @@ const decodeComparableJson = Schema.decodeUnknownEffect(Schema.Json);
235
219
  * Project canonical evidence into a Submission-identity-independent comparable form: batch
236
220
  * identity, canonical sequence, and the full encoded record, with the ledger-minted
237
221
  * `submissionId`/`receiptId` (and every identity derived from them: run, turn, batch, record,
238
- * and settlement ids) replaced by stable placeholders. Two Conversations whose normalized
222
+ * and settlement ids) replaced by stable placeholders. Two Threads whose normalized
239
223
  * evidence is equal took byte-equivalent canonical histories, so restart-equivalence can compare
240
224
  * a recovered run against an uninterrupted control run on a separate database.
241
225
  */
@@ -252,16 +236,19 @@ export const normalizeDurableTravelPlannerEvidence = Effect.fn(
252
236
  }),
253
237
  ),
254
238
  );
239
+
255
240
  const comparable = encoded.map((envelope) => ({
256
241
  batchId: envelope.batchId,
257
242
  sequence: envelope.sequence,
258
243
  record: envelope.record,
259
244
  }));
245
+
260
246
  const substituted: unknown = JSON.parse(
261
247
  JSON.stringify(comparable)
262
248
  .replaceAll(receipt.submissionId, "{submissionId}")
263
249
  .replaceAll(receipt.receiptId, "{receiptId}"),
264
250
  );
251
+
265
252
  return yield* decodeComparableJson(substituted).pipe(
266
253
  Effect.mapError((error) =>
267
254
  TravelPlannerDurableEvidenceError.make({
@@ -1,22 +1,25 @@
1
- import { Agent, AgentPolicy, type ConversationId } from "@effect-agent/core";
2
- import { DurableStep, DurableStepError, ToolExecutionClass } from "@effect-agent/engine";
1
+ import { Effect, Layer, Option, Schema } from "effect";
2
+ import * as Agent from "effect-agent/agent";
3
+ import { AgentPolicy } from "effect-agent/agent-policy";
4
+ import { type DurableSubmitOptions } from "effect-agent/durable-agent-runtime";
5
+ import { DurableStep, DurableStepError, ToolExecutionClass } from "effect-agent/durable-step";
6
+ import { type ThreadId } from "effect-agent/identifiers";
3
7
  import {
4
8
  DefinitionDigests,
5
9
  DeploymentId,
6
10
  Digest,
7
11
  PersistedJson,
8
- Principal,
9
12
  ProducerId,
13
+ type CanonicalRecordEnvelope,
14
+ } from "effect-agent/records";
15
+ import { Principal, type IdempotencyKey } from "effect-agent/submission-ledger";
16
+ import {
10
17
  ReconciliationCompleted,
11
18
  ReconciliationSafeToRetry,
12
19
  ReconciliationUncertain,
13
20
  ToolReconciler,
14
21
  ToolReconcilerError,
15
- type CanonicalRecordEnvelope,
16
- type DurableSubmitOptions,
17
- type IdempotencyKey,
18
- } from "@effect-agent/session";
19
- import { Effect, Layer, Option, Schema } from "effect";
22
+ } from "effect-agent/tool-reconciler";
20
23
  import { Tool, Toolkit } from "effect/unstable/ai";
21
24
 
22
25
  import {
@@ -42,42 +45,14 @@ import {
42
45
  } from "./deterministic-layers.ts";
43
46
  import { DurableSearchActivities, DurableSearchFlights, DurableSearchLodging } from "./phase4.ts";
44
47
 
45
- /**
46
- * The Phase 5 profile extends the P4 `DN` claim to consequential supplier mutation: booking
47
- * Tools enter the prepared/settled uncertainty protocol, unresolved external effects stop at
48
- * Unknown Outcomes instead of replaying, Durable Steps replay recorded results, and queued
49
- * traveler input joins the active Run. Exactly-once EXTERNAL execution is still — deliberately —
50
- * not claimed (DUR-003): the supplier's own idempotency keys are what dedupe repeats.
51
- */
52
- export class TravelPlannerBookingProfile extends Schema.Class<TravelPlannerBookingProfile>(
53
- "@effect-agent/testing/travel-planner/TravelPlannerBookingProfile",
54
- )({
55
- deploymentClass: Schema.Literal("DN"),
56
- durableAcceptedWork: Schema.Literal(true),
57
- canonicalSchemaVersion: Schema.Literal(1),
58
- /** P5: supplier mutations get prepared/settled records, Unknown Outcomes, and reconciliation. */
59
- supplierBookingUncertaintyProtocol: Schema.Literal(true),
60
- /** P5: Durable Steps are exactly-once-RECORDED; their side effects stay at-least-once. */
61
- durableStepsRecorded: Schema.Literal(true),
62
- /** Never claimed at any phase (DUR-003). */
63
- exactlyOnceExternalEffects: Schema.Literal(false),
64
- }) {}
65
-
66
- export const phase5TravelPlannerProfile = TravelPlannerBookingProfile.make({
67
- deploymentClass: "DN",
68
- durableAcceptedWork: true,
69
- canonicalSchemaVersion: 1,
70
- supplierBookingUncertaintyProtocol: true,
71
- durableStepsRecorded: true,
72
- exactlyOnceExternalEffects: false,
73
- });
74
-
75
48
  export const phase5TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
76
49
  "travel-planner-p5-deployment",
77
50
  );
51
+
78
52
  export const phase5TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
79
53
  "travel-planner-p5-producer",
80
54
  );
55
+
81
56
  export const phase5TravelPlannerPrincipal = Schema.decodeSync(Principal)(
82
57
  "travel-planner-p5-principal",
83
58
  );
@@ -93,10 +68,10 @@ export const phase5TravelPlannerDefinitionDigests = DefinitionDigests.make({
93
68
 
94
69
  /** Durable admission options for one Travel Planner Submission on one trip lane. */
95
70
  export const phase5TravelPlannerSubmitOptions = (
96
- conversationId: ConversationId,
71
+ threadId: ThreadId,
97
72
  idempotencyKey: IdempotencyKey,
98
73
  ): DurableSubmitOptions => ({
99
- conversationId,
74
+ threadId,
100
75
  principal: phase5TravelPlannerPrincipal,
101
76
  idempotencyKey,
102
77
  definitions: phase5TravelPlannerDefinitionDigests,
@@ -105,6 +80,7 @@ export const phase5TravelPlannerSubmitOptions = (
105
80
  export const TravelerRef = Schema.NonEmptyString.pipe(
106
81
  Schema.brand("@effect-agent/testing/travel-planner/TravelerRef"),
107
82
  );
83
+
108
84
  export type TravelerRef = typeof TravelerRef.Type;
109
85
 
110
86
  /**
@@ -171,6 +147,7 @@ export class TravelBookingReport extends Schema.Class<TravelBookingReport>("Trav
171
147
  * to query external truth — both sides are exported so they cannot drift.
172
148
  */
173
149
  export const bookFlightIdempotencyKey = (toolCallId: string): string => `book-flight:${toolCallId}`;
150
+
174
151
  export const itineraryStepIdempotencyKey = (toolCallId: string, stepName: string): string =>
175
152
  `${toolCallId}:${stepName}`;
176
153
 
@@ -205,7 +182,7 @@ export const CancelBooking = Tool.make("cancel_booking", {
205
182
 
206
183
  /**
207
184
  * The Durable Tool: declaring `DurableStep` in `dependencies` is what makes it durable
208
- * (CONTEXT.md). Its handler divides supplier mutation into three named Steps — `reserve-flight`,
185
+ * (GLOSSARY.md). Its handler divides supplier mutation into three named Steps — `reserve-flight`,
209
186
  * `reserve-lodging`, `issue-confirmation` — each deriving its supplier idempotency key from
210
187
  * `(toolCallId, stepName)`, so re-entry after interruption replays recorded Step results and the
211
188
  * supplier dedupes any honestly-repeated call. The Tool itself carries no execution-class
@@ -252,11 +229,13 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
252
229
  Effect.gen(function* () {
253
230
  const desk = yield* SupplierBookingDesk;
254
231
  const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
232
+
255
233
  const record = yield* desk.book({
256
234
  operation: "book-flight",
257
235
  idempotencyKey: bookFlightIdempotencyKey(toolCallId),
258
236
  detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`,
259
237
  });
238
+
260
239
  return SupplierBookingConfirmation.make({
261
240
  bookingRef: record.bookingRef,
262
241
  status: "confirmed",
@@ -267,6 +246,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
267
246
  Effect.gen(function* () {
268
247
  const desk = yield* SupplierBookingDesk;
269
248
  const record = yield* desk.cancel(request.bookingRef);
249
+
270
250
  return CancellationConfirmation.make({
271
251
  bookingRef: record.bookingRef,
272
252
  status: "cancelled",
@@ -277,6 +257,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
277
257
  const desk = yield* SupplierBookingDesk;
278
258
  const step = yield* DurableStep;
279
259
  const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
260
+
280
261
  const bookStep = (
281
262
  stepName: "reserve-flight" | "reserve-lodging" | "issue-confirmation",
282
263
  detail: string,
@@ -290,18 +271,22 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
290
271
  detail,
291
272
  }),
292
273
  );
274
+
293
275
  const flight = yield* bookStep(
294
276
  "reserve-flight",
295
277
  `flight ${request.quoteId} for ${request.travelerRef}`,
296
278
  );
279
+
297
280
  const lodging = yield* bookStep(
298
281
  "reserve-lodging",
299
282
  `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`,
300
283
  );
284
+
301
285
  const confirmation = yield* bookStep(
302
286
  "issue-confirmation",
303
287
  `itinerary confirmation for ${request.travelerRef}`,
304
288
  );
289
+
305
290
  return ItineraryConfirmation.make({
306
291
  flightBookingRef: flight.bookingRef,
307
292
  lodgingBookingRef: lodging.bookingRef,
@@ -336,6 +321,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
336
321
  ToolReconciler,
337
322
  Effect.gen(function* () {
338
323
  const desk = yield* SupplierBookingDesk;
324
+
339
325
  return ToolReconciler.of({
340
326
  reconcile: (evidence) =>
341
327
  Effect.gen(function* () {
@@ -343,6 +329,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
343
329
  case "book_flight": {
344
330
  const key = bookFlightIdempotencyKey(evidence.toolCallId);
345
331
  const booking = yield* desk.lookup(key);
332
+
346
333
  if (Option.isSome(booking) && booking.value.status === "confirmed") {
347
334
  const confirmation = yield* encodeConfirmation(
348
335
  SupplierBookingConfirmation.make({
@@ -351,8 +338,10 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
351
338
  detail: booking.value.detail,
352
339
  }),
353
340
  ).pipe(Effect.flatMap(decodePersistedJson));
341
+
354
342
  return ReconciliationCompleted.make({ result: confirmation, isFailure: false });
355
343
  }
344
+
356
345
  return ReconciliationUncertain.make({
357
346
  reason: `The supplier desk shows no confirmed booking under ${key}; a write may still be in flight.`,
358
347
  });
@@ -384,7 +373,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
384
373
  * idempotent-by-contract cancellation, and one Durable Tool whose Steps carry supplier
385
374
  * idempotency keys.
386
375
  */
387
- export const TravelPlannerPhase5 = Agent.define("travel-planner-phase-5", {
376
+ export const TravelPlannerPhase5 = Agent.make("travel-planner-phase-5", {
388
377
  input: TripRequest,
389
378
  output: TravelBookingReport,
390
379
  instructions: [
@@ -413,6 +402,7 @@ export class TravelPlannerBookingEvidenceError extends Schema.TaggedError<Travel
413
402
  const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
414
403
  if (typeof result !== "object" || result === null) return [];
415
404
  const refs: Array<string> = [];
405
+
416
406
  for (const [field, value] of Object.entries(result)) {
417
407
  if (
418
408
  typeof value === "string" &&
@@ -424,6 +414,7 @@ const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
424
414
  refs.push(value);
425
415
  }
426
416
  }
417
+
427
418
  return refs;
428
419
  };
429
420
 
@@ -443,8 +434,10 @@ export const assertSettledBookingsExistAtSupplier = Effect.fn(
443
434
  const desk = yield* SupplierBookingDesk;
444
435
  const bookings = yield* desk.bookings;
445
436
  const knownRefs = new Set<string>(bookings.map((booking) => booking.bookingRef));
437
+
446
438
  for (const envelope of records) {
447
439
  const payload = envelope.record.payload;
440
+
448
441
  if (
449
442
  payload._tag !== "ToolCallSettled" ||
450
443
  payload.isFailure ||