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

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
@@ -1,45 +1,27 @@
1
- import { AgentId, ConversationId, RunId, SubmissionId } from "@effect-agent/core";
1
+ import { AgentId, ThreadId, RunId, SubmissionId } from "@effect-agent/core/Identifiers";
2
2
  import {
3
3
  BatchId,
4
4
  CanonicalBatch,
5
- ConversationCheckpoint,
6
- ConversationProjection,
7
5
  DefinitionDigests,
8
6
  DeploymentId,
9
7
  Digest,
10
8
  ProducerId,
11
9
  RecordEnvelope,
12
10
  RecordId,
13
- } from "@effect-agent/session";
11
+ } from "@effect-agent/thread/Records";
12
+ import { ThreadProjection } from "@effect-agent/thread/ThreadProjection";
13
+ import { ThreadCheckpoint } from "@effect-agent/thread/ThreadStore";
14
14
  import { Effect, Schema } from "effect";
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 * as Agent from "@effect-agent/core/Agent";
2
+ import { AgentPolicy } from "@effect-agent/core/AgentPolicy";
3
+ import { type ThreadId } from "@effect-agent/core/Identifiers";
4
+ import { ToolExecutionClass } from "@effect-agent/engine/DurableStep";
5
+ import { type DurableSubmitOptions, type Receipt } from "@effect-agent/thread/DurableAgentRuntime";
3
6
  import {
4
7
  CanonicalRecordEnvelope,
5
8
  DefinitionDigests,
6
9
  DeploymentId,
7
10
  Digest,
8
- Principal,
9
11
  ProducerId,
10
- type DurableSubmitOptions,
11
- type IdempotencyKey,
12
- type Receipt,
13
- } from "@effect-agent/session";
12
+ } from "@effect-agent/thread/Records";
13
+ import { Principal, type IdempotencyKey } from "@effect-agent/thread/SubmissionLedger";
14
14
  import { Effect, Layer, Schema } from "effect";
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,21 +1,28 @@
1
- import { Agent, AgentPolicy, type ConversationId } from "@effect-agent/core";
2
- import { DurableStep, DurableStepError, ToolExecutionClass } from "@effect-agent/engine";
1
+ import * as Agent from "@effect-agent/core/Agent";
2
+ import { AgentPolicy } from "@effect-agent/core/AgentPolicy";
3
+ import { type ThreadId } from "@effect-agent/core/Identifiers";
4
+ import {
5
+ DurableStep,
6
+ DurableStepError,
7
+ ToolExecutionClass,
8
+ } from "@effect-agent/engine/DurableStep";
9
+ import { type DurableSubmitOptions } from "@effect-agent/thread/DurableAgentRuntime";
3
10
  import {
4
11
  DefinitionDigests,
5
12
  DeploymentId,
6
13
  Digest,
7
14
  PersistedJson,
8
- Principal,
9
15
  ProducerId,
16
+ type CanonicalRecordEnvelope,
17
+ } from "@effect-agent/thread/Records";
18
+ import { Principal, type IdempotencyKey } from "@effect-agent/thread/SubmissionLedger";
19
+ import {
10
20
  ReconciliationCompleted,
11
21
  ReconciliationSafeToRetry,
12
22
  ReconciliationUncertain,
13
23
  ToolReconciler,
14
24
  ToolReconcilerError,
15
- type CanonicalRecordEnvelope,
16
- type DurableSubmitOptions,
17
- type IdempotencyKey,
18
- } from "@effect-agent/session";
25
+ } from "@effect-agent/thread/ToolReconciler";
19
26
  import { Effect, Layer, Option, Schema } from "effect";
20
27
  import { Tool, Toolkit } from "effect/unstable/ai";
21
28
 
@@ -42,42 +49,14 @@ import {
42
49
  } from "./deterministic-layers.ts";
43
50
  import { DurableSearchActivities, DurableSearchFlights, DurableSearchLodging } from "./phase4.ts";
44
51
 
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
52
  export const phase5TravelPlannerDeploymentId = Schema.decodeSync(DeploymentId)(
76
53
  "travel-planner-p5-deployment",
77
54
  );
55
+
78
56
  export const phase5TravelPlannerProducerId = Schema.decodeSync(ProducerId)(
79
57
  "travel-planner-p5-producer",
80
58
  );
59
+
81
60
  export const phase5TravelPlannerPrincipal = Schema.decodeSync(Principal)(
82
61
  "travel-planner-p5-principal",
83
62
  );
@@ -93,10 +72,10 @@ export const phase5TravelPlannerDefinitionDigests = DefinitionDigests.make({
93
72
 
94
73
  /** Durable admission options for one Travel Planner Submission on one trip lane. */
95
74
  export const phase5TravelPlannerSubmitOptions = (
96
- conversationId: ConversationId,
75
+ threadId: ThreadId,
97
76
  idempotencyKey: IdempotencyKey,
98
77
  ): DurableSubmitOptions => ({
99
- conversationId,
78
+ threadId,
100
79
  principal: phase5TravelPlannerPrincipal,
101
80
  idempotencyKey,
102
81
  definitions: phase5TravelPlannerDefinitionDigests,
@@ -105,6 +84,7 @@ export const phase5TravelPlannerSubmitOptions = (
105
84
  export const TravelerRef = Schema.NonEmptyString.pipe(
106
85
  Schema.brand("@effect-agent/testing/travel-planner/TravelerRef"),
107
86
  );
87
+
108
88
  export type TravelerRef = typeof TravelerRef.Type;
109
89
 
110
90
  /**
@@ -171,6 +151,7 @@ export class TravelBookingReport extends Schema.Class<TravelBookingReport>("Trav
171
151
  * to query external truth — both sides are exported so they cannot drift.
172
152
  */
173
153
  export const bookFlightIdempotencyKey = (toolCallId: string): string => `book-flight:${toolCallId}`;
154
+
174
155
  export const itineraryStepIdempotencyKey = (toolCallId: string, stepName: string): string =>
175
156
  `${toolCallId}:${stepName}`;
176
157
 
@@ -205,7 +186,7 @@ export const CancelBooking = Tool.make("cancel_booking", {
205
186
 
206
187
  /**
207
188
  * 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`,
189
+ * (GLOSSARY.md). Its handler divides supplier mutation into three named Steps — `reserve-flight`,
209
190
  * `reserve-lodging`, `issue-confirmation` — each deriving its supplier idempotency key from
210
191
  * `(toolCallId, stepName)`, so re-entry after interruption replays recorded Step results and the
211
192
  * supplier dedupes any honestly-repeated call. The Tool itself carries no execution-class
@@ -252,11 +233,13 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
252
233
  Effect.gen(function* () {
253
234
  const desk = yield* SupplierBookingDesk;
254
235
  const toolCallId = yield* requireToolCallId("book_flight", context.toolCallId);
236
+
255
237
  const record = yield* desk.book({
256
238
  operation: "book-flight",
257
239
  idempotencyKey: bookFlightIdempotencyKey(toolCallId),
258
240
  detail: `flight ${request.quoteId} for ${request.travelerRef} on ${request.departOn}`,
259
241
  });
242
+
260
243
  return SupplierBookingConfirmation.make({
261
244
  bookingRef: record.bookingRef,
262
245
  status: "confirmed",
@@ -267,6 +250,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
267
250
  Effect.gen(function* () {
268
251
  const desk = yield* SupplierBookingDesk;
269
252
  const record = yield* desk.cancel(request.bookingRef);
253
+
270
254
  return CancellationConfirmation.make({
271
255
  bookingRef: record.bookingRef,
272
256
  status: "cancelled",
@@ -277,6 +261,7 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
277
261
  const desk = yield* SupplierBookingDesk;
278
262
  const step = yield* DurableStep;
279
263
  const toolCallId = yield* requireToolCallId("book_itinerary", context.toolCallId);
264
+
280
265
  const bookStep = (
281
266
  stepName: "reserve-flight" | "reserve-lodging" | "issue-confirmation",
282
267
  detail: string,
@@ -290,18 +275,22 @@ export const TravelPlannerPhase5ToolkitLayer = TravelPlannerPhase5Toolkit.toLaye
290
275
  detail,
291
276
  }),
292
277
  );
278
+
293
279
  const flight = yield* bookStep(
294
280
  "reserve-flight",
295
281
  `flight ${request.quoteId} for ${request.travelerRef}`,
296
282
  );
283
+
297
284
  const lodging = yield* bookStep(
298
285
  "reserve-lodging",
299
286
  `lodging ${request.destination} for ${request.nights} nights (${request.travelerRef})`,
300
287
  );
288
+
301
289
  const confirmation = yield* bookStep(
302
290
  "issue-confirmation",
303
291
  `itinerary confirmation for ${request.travelerRef}`,
304
292
  );
293
+
305
294
  return ItineraryConfirmation.make({
306
295
  flightBookingRef: flight.bookingRef,
307
296
  lodgingBookingRef: lodging.bookingRef,
@@ -336,6 +325,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
336
325
  ToolReconciler,
337
326
  Effect.gen(function* () {
338
327
  const desk = yield* SupplierBookingDesk;
328
+
339
329
  return ToolReconciler.of({
340
330
  reconcile: (evidence) =>
341
331
  Effect.gen(function* () {
@@ -343,6 +333,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
343
333
  case "book_flight": {
344
334
  const key = bookFlightIdempotencyKey(evidence.toolCallId);
345
335
  const booking = yield* desk.lookup(key);
336
+
346
337
  if (Option.isSome(booking) && booking.value.status === "confirmed") {
347
338
  const confirmation = yield* encodeConfirmation(
348
339
  SupplierBookingConfirmation.make({
@@ -351,8 +342,10 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
351
342
  detail: booking.value.detail,
352
343
  }),
353
344
  ).pipe(Effect.flatMap(decodePersistedJson));
345
+
354
346
  return ReconciliationCompleted.make({ result: confirmation, isFailure: false });
355
347
  }
348
+
356
349
  return ReconciliationUncertain.make({
357
350
  reason: `The supplier desk shows no confirmed booking under ${key}; a write may still be in flight.`,
358
351
  });
@@ -384,7 +377,7 @@ export const TravelSupplierReconcilerLayer: Layer.Layer<
384
377
  * idempotent-by-contract cancellation, and one Durable Tool whose Steps carry supplier
385
378
  * idempotency keys.
386
379
  */
387
- export const TravelPlannerPhase5 = Agent.define("travel-planner-phase-5", {
380
+ export const TravelPlannerPhase5 = Agent.make("travel-planner-phase-5", {
388
381
  input: TripRequest,
389
382
  output: TravelBookingReport,
390
383
  instructions: [
@@ -413,6 +406,7 @@ export class TravelPlannerBookingEvidenceError extends Schema.TaggedError<Travel
413
406
  const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
414
407
  if (typeof result !== "object" || result === null) return [];
415
408
  const refs: Array<string> = [];
409
+
416
410
  for (const [field, value] of Object.entries(result)) {
417
411
  if (
418
412
  typeof value === "string" &&
@@ -424,6 +418,7 @@ const bookingResultRefs = (result: unknown): ReadonlyArray<string> => {
424
418
  refs.push(value);
425
419
  }
426
420
  }
421
+
427
422
  return refs;
428
423
  };
429
424
 
@@ -443,8 +438,10 @@ export const assertSettledBookingsExistAtSupplier = Effect.fn(
443
438
  const desk = yield* SupplierBookingDesk;
444
439
  const bookings = yield* desk.bookings;
445
440
  const knownRefs = new Set<string>(bookings.map((booking) => booking.bookingRef));
441
+
446
442
  for (const envelope of records) {
447
443
  const payload = envelope.record.payload;
444
+
448
445
  if (
449
446
  payload._tag !== "ToolCallSettled" ||
450
447
  payload.isFailure ||