effect-agent 0.1.0-beta.103 → 0.1.0-beta.106

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 (166) hide show
  1. package/dist/{Agent-CM6JubNX.d.mts → Agent-Bk-mLtov.d.mts} +2 -2
  2. package/dist/{AgentError-CggcAQ7V.d.mts → AgentError-Bn1ytXRD.d.mts} +20 -4
  3. package/dist/{AgentRuntime-B1SXMVN7.d.mts → AgentRuntime-BEXToYl1.d.mts} +8 -8
  4. package/dist/{AgentRuntime-BWOnNc0d.mjs → AgentRuntime-e2M5UoXn.mjs} +4 -3
  5. package/dist/AgentRuntime-e2M5UoXn.mjs.map +1 -0
  6. package/dist/{DurableAgentRuntime-C9eRZS9N.mjs → DurableAgentRuntime-wd1x0TcB.mjs} +67 -36
  7. package/dist/DurableAgentRuntime-wd1x0TcB.mjs.map +1 -0
  8. package/dist/{EventSource-K7XUzurY.d.mts → EventSource-pH-v0k42.d.mts} +3 -3
  9. package/dist/FailureDiagnostic-BElLOoED.mjs +274 -0
  10. package/dist/FailureDiagnostic-BElLOoED.mjs.map +1 -0
  11. package/dist/FailureDiagnostic-CaAkzGHL.d.mts +223 -0
  12. package/dist/{Messaging-QMYhp297.d.mts → Messaging-Bl4SBKpR.d.mts} +3 -3
  13. package/dist/{MessagingHost-gAxs5gme.d.mts → MessagingHost-nwBMdRoh.d.mts} +3 -3
  14. package/dist/{Output-CjocJzsl.d.mts → Output-B6RQ5jRU.d.mts} +2 -2
  15. package/dist/{PreparedInputAdmission-KLk5rGFI.d.mts → PreparedInputAdmission-D3-BgmA9.d.mts} +8 -4
  16. package/dist/{Records-Bidb7ByL.d.mts → Records-B4S02MX1.d.mts} +201 -14
  17. package/dist/{RunJournal-srpbEBEy.mjs → RunJournal-BdIlgAEj.mjs} +2 -2
  18. package/dist/{RunJournal-srpbEBEy.mjs.map → RunJournal-BdIlgAEj.mjs.map} +1 -1
  19. package/dist/{RunOptions-Bruk54SE.d.mts → RunOptions-qOcPv08p.d.mts} +10 -5
  20. package/dist/{Schedule-Cy9up8Uf.d.mts → Schedule-D2TVEu3W.d.mts} +7 -2
  21. package/dist/{Scheduling-CHCsSCGo.mjs → Scheduling-DPb2QyAK.mjs} +2 -2
  22. package/dist/{Scheduling-CHCsSCGo.mjs.map → Scheduling-DPb2QyAK.mjs.map} +1 -1
  23. package/dist/{Subagent-DP0CzO16.d.mts → Subagent-BxuDuCHt.d.mts} +172 -8
  24. package/dist/{Subagent-C2s8PFrd.mjs → Subagent-uXWHXiKb.mjs} +64 -29
  25. package/dist/Subagent-uXWHXiKb.mjs.map +1 -0
  26. package/dist/{SubagentHost-CYcxkpRc.d.mts → SubagentHost-DEPw4YME.d.mts} +6 -3
  27. package/dist/{Subscription-CHyoQelH.d.mts → Subscription-nX0rz4GQ.d.mts} +3 -3
  28. package/dist/{SubscriptionInput-DqrHW-Ja.d.mts → SubscriptionInput-CAG3_pyJ.d.mts} +3 -3
  29. package/dist/{Worker--j87JI__.d.mts → Worker-Bbj9uq5H.d.mts} +5 -2
  30. package/dist/{agent-registration-ByeHejH3.mjs → agent-registration-B5CI-Ygk.mjs} +7 -5
  31. package/dist/agent-registration-B5CI-Ygk.mjs.map +1 -0
  32. package/dist/capabilities/Commands.d.mts +1 -1
  33. package/dist/capabilities/Messaging.d.mts +2 -2
  34. package/dist/capabilities/RunHooks.d.mts +1 -1
  35. package/dist/capabilities/Subagent.d.mts +2 -2
  36. package/dist/capabilities/Subagent.mjs +1 -1
  37. package/dist/core/Agent.d.mts +1 -1
  38. package/dist/core/AgentError.d.mts +2 -2
  39. package/dist/core/AgentError.mjs +19 -2
  40. package/dist/core/AgentError.mjs.map +1 -1
  41. package/dist/core/FailureDiagnostic.d.mts +2 -0
  42. package/dist/core/FailureDiagnostic.mjs +2 -0
  43. package/dist/core/Worker.d.mts +1 -1
  44. package/dist/core/Worker.mjs +4 -1
  45. package/dist/core/Worker.mjs.map +1 -1
  46. package/dist/durable/ActivityStore.d.mts +1 -1
  47. package/dist/durable/ActivityStore.mjs +1 -1
  48. package/dist/durable/Admin.d.mts +1 -1
  49. package/dist/durable/Admin.mjs +1 -1
  50. package/dist/durable/AgentRegistration.d.mts +1 -1
  51. package/dist/durable/AgentRegistration.mjs +1 -1
  52. package/dist/durable/Certification.d.mts +1 -1
  53. package/dist/durable/CommittedActivity.d.mts +1 -1
  54. package/dist/durable/CommittedActivity.mjs +1 -1
  55. package/dist/durable/Digest.d.mts +1 -1
  56. package/dist/durable/Digest.mjs +7 -7
  57. package/dist/durable/Digest.mjs.map +1 -1
  58. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  59. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  60. package/dist/durable/EventSource.d.mts +1 -1
  61. package/dist/durable/EventSource.mjs +1 -1
  62. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  63. package/dist/durable/GitHubWorkflowSource.mjs +1 -1
  64. package/dist/durable/MessageDelivery.d.mts +15 -3
  65. package/dist/durable/MessageDelivery.mjs +44 -24
  66. package/dist/durable/MessageDelivery.mjs.map +1 -1
  67. package/dist/durable/MessageDeliveryStoreConformance.d.mts +4 -1
  68. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  69. package/dist/durable/PersistentHistory.d.mts +1 -1
  70. package/dist/durable/PersistentHistory.mjs +2 -2
  71. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  72. package/dist/durable/PreparedInputAdmission.mjs +8 -4
  73. package/dist/durable/PreparedInputAdmission.mjs.map +1 -1
  74. package/dist/durable/Records.d.mts +1 -1
  75. package/dist/durable/Records.mjs +831 -2
  76. package/dist/durable/Records.mjs.map +1 -0
  77. package/dist/durable/Recovery.d.mts +1 -1
  78. package/dist/durable/Recovery.mjs +1 -1
  79. package/dist/durable/RunJournal.d.mts +1 -1
  80. package/dist/durable/RunJournal.mjs +1 -1
  81. package/dist/durable/Schedule.d.mts +1 -1
  82. package/dist/durable/Schedule.mjs +12 -3
  83. package/dist/durable/Schedule.mjs.map +1 -1
  84. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  85. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  86. package/dist/durable/ScheduleTransition.d.mts +1 -1
  87. package/dist/durable/ScheduleTransition.mjs +1 -1
  88. package/dist/durable/Scheduling.d.mts +2 -2
  89. package/dist/durable/Scheduling.mjs +1 -1
  90. package/dist/durable/SqlMessageDeliveryStore.mjs +13 -11
  91. package/dist/durable/SqlMessageDeliveryStore.mjs.map +1 -1
  92. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  93. package/dist/durable/SqlStorageV2Upgrade.mjs +1 -1
  94. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  95. package/dist/durable/SqlSubscriptionStore.mjs +1 -1
  96. package/dist/durable/SqlThreadNativeReads.d.mts +1 -1
  97. package/dist/durable/SqlThreadNativeReads.mjs +2 -2
  98. package/dist/durable/SubmissionLedger.d.mts +1 -1
  99. package/dist/durable/SubmissionLedger.mjs +6 -3
  100. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  101. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  102. package/dist/durable/SubmissionLedgerConformance.mjs +1 -1
  103. package/dist/durable/SubmissionStatus.d.mts +1 -1
  104. package/dist/durable/Subscription.d.mts +1 -1
  105. package/dist/durable/Subscription.mjs +1 -1
  106. package/dist/durable/SubscriptionInput.d.mts +1 -1
  107. package/dist/durable/SubscriptionInput.mjs +2 -2
  108. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  109. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  110. package/dist/durable/SubscriptionTools.d.mts +3 -3
  111. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  112. package/dist/durable/SubscriptionTransition.mjs +1 -1
  113. package/dist/durable/Subscriptions.d.mts +5 -5
  114. package/dist/durable/Subscriptions.mjs +1 -1
  115. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  116. package/dist/durable/ThreadContextHistory.mjs +1 -1
  117. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  118. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  119. package/dist/durable/ThreadInvariants.d.mts +1 -1
  120. package/dist/durable/ThreadInvariants.mjs +2 -2
  121. package/dist/durable/ThreadProjection.d.mts +1 -1
  122. package/dist/durable/ThreadProjection.mjs +1 -1
  123. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  124. package/dist/durable/ThreadStore.d.mts +1 -1
  125. package/dist/durable/ThreadStore.mjs +2 -2
  126. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  127. package/dist/durable/ThreadStoreConformance.mjs +1 -1
  128. package/dist/durable/ToolReconciler.mjs +1 -1
  129. package/dist/durable/WorkerHost.d.mts +3 -3
  130. package/dist/engine/AgentRuntime.d.mts +1 -1
  131. package/dist/engine/AgentRuntime.mjs +1 -1
  132. package/dist/engine/MessagingHost.d.mts +1 -1
  133. package/dist/engine/Output.d.mts +1 -1
  134. package/dist/engine/RunOptions.d.mts +1 -1
  135. package/dist/engine/RunOptions.mjs +2 -0
  136. package/dist/engine/RunOptions.mjs.map +1 -1
  137. package/dist/engine/SubagentHost.d.mts +1 -1
  138. package/dist/engine/SubagentHost.mjs.map +1 -1
  139. package/dist/index.d.mts +18 -17
  140. package/dist/index.mjs +8 -7
  141. package/package.json +1 -1
  142. package/src/capabilities/internal/subagent-background.ts +66 -23
  143. package/src/capabilities/internal/subagent-contract.ts +4 -0
  144. package/src/core/AgentError.ts +20 -0
  145. package/src/core/FailureDiagnostic.ts +343 -0
  146. package/src/core/Worker.ts +4 -1
  147. package/src/durable/Digest.ts +22 -28
  148. package/src/durable/DurableAgentRuntime.ts +37 -13
  149. package/src/durable/MessageDelivery.ts +82 -24
  150. package/src/durable/PreparedInputAdmission.ts +17 -4
  151. package/src/durable/Records.ts +5 -15
  152. package/src/durable/Schedule.ts +11 -2
  153. package/src/durable/SqlMessageDeliveryStore.ts +21 -9
  154. package/src/durable/SubmissionLedger.ts +4 -1
  155. package/src/durable/internal/agent-registration.ts +46 -14
  156. package/src/durable/internal/worker-host.ts +32 -19
  157. package/src/engine/RunOptions.ts +11 -3
  158. package/src/engine/SubagentHost.ts +3 -0
  159. package/src/engine/internal/agent-runtime.ts +23 -16
  160. package/src/index.ts +1 -0
  161. package/dist/AgentRuntime-BWOnNc0d.mjs.map +0 -1
  162. package/dist/DurableAgentRuntime-C9eRZS9N.mjs.map +0 -1
  163. package/dist/Records-D8167fHn.mjs +0 -844
  164. package/dist/Records-D8167fHn.mjs.map +0 -1
  165. package/dist/Subagent-C2s8PFrd.mjs.map +0 -1
  166. package/dist/agent-registration-ByeHejH3.mjs.map +0 -1
@@ -0,0 +1,343 @@
1
+ import {
2
+ Cause as EffectCause,
3
+ Exit,
4
+ Predicate,
5
+ Redacted,
6
+ Schema,
7
+ SchemaTransformation,
8
+ } from "effect";
9
+
10
+ import { strictSchema } from "./internal/strict-schema.ts";
11
+
12
+ const Text = Schema.String.check(Schema.isMaxLength(16 * 1024));
13
+ const Name = Schema.String.check(Schema.isMaxLength(1_024));
14
+ const Scalar = Schema.Union([Text, Schema.Finite, Schema.Boolean, Schema.Null]);
15
+
16
+ /** Correlation only: never include inputs, tool parameters, request bodies, or credentials. */
17
+ export const Context = Schema.Struct({
18
+ agentId: Schema.optionalKey(Name),
19
+ threadId: Schema.optionalKey(Name),
20
+ runId: Schema.optionalKey(Name),
21
+ submissionId: Schema.optionalKey(Name),
22
+ attemptId: Schema.optionalKey(Name),
23
+ turnId: Schema.optionalKey(Name),
24
+ toolCallId: Schema.optionalKey(Name),
25
+ toolName: Schema.optionalKey(Name),
26
+ callId: Schema.optionalKey(Name),
27
+ check: Schema.optionalKey(Name),
28
+ operation: Schema.optionalKey(Name),
29
+ }).pipe(strictSchema);
30
+
31
+ export type Context = typeof Context.Type;
32
+
33
+ type CauseReason =
34
+ | { readonly _tag: "Fail"; readonly error: Diagnostic }
35
+ | { readonly _tag: "Die"; readonly defect: Diagnostic }
36
+ | { readonly _tag: "Interrupt"; readonly fiberId?: number };
37
+
38
+ /** A private diagnostic projection, not a JavaScript error reconstruction or model response. */
39
+ export type Diagnostic =
40
+ | {
41
+ readonly _tag: "Error";
42
+ readonly errorTag?: string;
43
+ readonly name?: string;
44
+ readonly message?: string;
45
+ readonly stack?: string;
46
+ readonly reason?: Diagnostic;
47
+ readonly code?: string | number;
48
+ readonly cause?: Diagnostic;
49
+ readonly errors?: ReadonlyArray<Diagnostic>;
50
+ readonly context?: Context;
51
+ readonly omittedFields?: ReadonlyArray<string>;
52
+ readonly truncated?: true;
53
+ }
54
+ | {
55
+ readonly _tag: "Cause";
56
+ readonly reasons: ReadonlyArray<CauseReason>;
57
+ readonly truncated?: true;
58
+ }
59
+ | { readonly _tag: "Value"; readonly value: string | number | boolean | null }
60
+ | { readonly _tag: "Omitted"; readonly reason: "redacted" | "cycle" | "limit" | "unsupported" };
61
+
62
+ const diagnosticSchema = <Nested extends Schema.Top>(nested: Nested) =>
63
+ Schema.Union([
64
+ Schema.Struct({
65
+ _tag: Schema.Literal("Error"),
66
+ errorTag: Schema.optionalKey(Name),
67
+ name: Schema.optionalKey(Name),
68
+ message: Schema.optionalKey(Text),
69
+ stack: Schema.optionalKey(Text),
70
+ reason: Schema.optionalKey(nested),
71
+ code: Schema.optionalKey(Schema.Union([Name, Schema.Finite])),
72
+ cause: Schema.optionalKey(nested),
73
+ errors: Schema.optionalKey(Schema.Array(nested).check(Schema.isMaxLength(128))),
74
+ context: Schema.optionalKey(Context),
75
+ omittedFields: Schema.optionalKey(Schema.Array(Name).check(Schema.isMaxLength(32))),
76
+ truncated: Schema.optionalKey(Schema.Literal(true)),
77
+ }),
78
+ Schema.Struct({
79
+ _tag: Schema.Literal("Cause"),
80
+ reasons: Schema.Array(
81
+ Schema.Union([
82
+ Schema.Struct({ _tag: Schema.Literal("Fail"), error: nested }),
83
+ Schema.Struct({ _tag: Schema.Literal("Die"), defect: nested }),
84
+ Schema.Struct({
85
+ _tag: Schema.Literal("Interrupt"),
86
+ fiberId: Schema.optionalKey(Schema.Finite),
87
+ }),
88
+ ]),
89
+ ).check(Schema.isMaxLength(128)),
90
+ truncated: Schema.optionalKey(Schema.Literal(true)),
91
+ }),
92
+ Schema.Struct({ _tag: Schema.Literal("Value"), value: Scalar }),
93
+ Schema.Struct({
94
+ _tag: Schema.Literal("Omitted"),
95
+ reason: Schema.Literals(["redacted", "cycle", "limit", "unsupported"]),
96
+ }),
97
+ ]);
98
+
99
+ /** Structured, allowlisted Error fields and ordered Effect Fail/Die/Interrupt reasons. */
100
+ export const Diagnostic: Schema.Codec<Diagnostic> = Schema.suspend(() =>
101
+ diagnosticSchema(Diagnostic),
102
+ ).pipe(strictSchema);
103
+
104
+ // Inspect one encoded node only; recursion must share capture's depth/node/text budgets.
105
+ const decodeDiagnosticNode = Schema.decodeUnknownExit(
106
+ diagnosticSchema(Schema.Unknown).pipe(strictSchema),
107
+ );
108
+
109
+ /** Remove common credential forms; hosts must keep arbitrary private payloads out of error text. */
110
+ export const redactText = (text: string): string =>
111
+ text
112
+ .replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [REDACTED]")
113
+ .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, "$1[REDACTED]@")
114
+ .replace(
115
+ /([?&](?:access_token|api_key|apikey|token|key|secret|password|signature)=)[^\s&#]*/gi,
116
+ "$1[REDACTED]",
117
+ )
118
+ .replace(
119
+ /\b((?:api[_-]?key|access[_-]?token|authorization|password|secret)\s*[=:]\s*)[^\s,;}]+/gi,
120
+ "$1[REDACTED]",
121
+ );
122
+
123
+ /**
124
+ * Capture only diagnostic fields. Local values are never mutated. Cycles and exhausted limits
125
+ * are explicit, not silently dropped; capture visits at most 1,024 nodes, with 56 JSON levels
126
+ * and 128 KiB text. Inner causes have priority over outer stacks when the text budget is exhausted.
127
+ * Text remains operator-private. No generic serializer can identify secrets embedded in arbitrary
128
+ * prose: application error messages must exclude payloads, and hosts may supply stricter redaction.
129
+ */
130
+ export const capture = (
131
+ value: unknown,
132
+ options?: { readonly redactText?: (text: string) => string },
133
+ ): Diagnostic => {
134
+ const seen = new WeakSet<object>();
135
+ let remainingNodes = 1_024;
136
+ let remainingText = 128 * 1024;
137
+
138
+ const text = (value: string, limit = 16 * 1024): string => {
139
+ const safe = (options?.redactText ?? redactText)(value);
140
+ const length = Math.max(0, Math.min(limit, remainingText));
141
+
142
+ const marker = "[truncated]".slice(0, length);
143
+
144
+ const result =
145
+ safe.length <= length ? safe : `${safe.slice(0, length - marker.length)}${marker}`;
146
+
147
+ remainingText -= result.length;
148
+
149
+ return result;
150
+ };
151
+
152
+ const visit = (value: unknown, depth: number): Diagnostic => {
153
+ if (remainingNodes <= 0 || depth >= 56) return { _tag: "Omitted", reason: "limit" };
154
+ remainingNodes--;
155
+ if (Redacted.isRedacted(value)) return { _tag: "Omitted", reason: "redacted" };
156
+ if (Predicate.isString(value)) return { _tag: "Value", value: text(value) };
157
+ if (
158
+ value === null ||
159
+ Predicate.isBoolean(value) ||
160
+ (Predicate.isNumber(value) && Number.isFinite(value))
161
+ )
162
+ return { _tag: "Value", value };
163
+ if (!Predicate.isObject(value)) return { _tag: "Omitted", reason: "unsupported" };
164
+ if (seen.has(value)) return { _tag: "Omitted", reason: "cycle" };
165
+ seen.add(value);
166
+ const nested = (value: unknown) => visit(value, depth + 1);
167
+ // Local errors retain their own tag, even when it happens to be "Error".
168
+ const decoded = value instanceof Error ? undefined : decodeDiagnosticNode(value);
169
+ const node = decoded !== undefined && Exit.isSuccess(decoded) ? decoded.value : undefined;
170
+
171
+ if (node?._tag === "Omitted" || node?._tag === "Value") {
172
+ seen.delete(value);
173
+
174
+ return node._tag === "Value" && typeof node.value === "string"
175
+ ? { _tag: "Value", value: text(node.value) }
176
+ : node;
177
+ }
178
+
179
+ const originalReasons = EffectCause.isCause(value)
180
+ ? value.reasons
181
+ : node?._tag === "Cause"
182
+ ? node.reasons
183
+ : undefined;
184
+
185
+ if (originalReasons !== undefined) {
186
+ const reasons: Array<CauseReason> = [];
187
+
188
+ for (const reason of originalReasons) {
189
+ if (reasons.length >= 128 || remainingNodes <= 0) break;
190
+ remainingNodes--;
191
+
192
+ // reasons -> array item -> error/defect contributes three JSON levels.
193
+ switch (reason._tag) {
194
+ case "Fail":
195
+ reasons.push({ _tag: "Fail", error: visit(reason.error, depth + 3) });
196
+ break;
197
+ case "Die":
198
+ reasons.push({ _tag: "Die", defect: visit(reason.defect, depth + 3) });
199
+ break;
200
+ case "Interrupt":
201
+ reasons.push({
202
+ _tag: "Interrupt",
203
+ ...(reason.fiberId === undefined ? {} : { fiberId: reason.fiberId }),
204
+ });
205
+ break;
206
+ }
207
+ }
208
+
209
+ const result: Diagnostic = {
210
+ _tag: "Cause",
211
+ ...(originalReasons.length > reasons.length || (node?._tag === "Cause" && node.truncated)
212
+ ? { truncated: true }
213
+ : {}),
214
+ reasons,
215
+ };
216
+
217
+ seen.delete(value);
218
+
219
+ return result;
220
+ }
221
+ const retained = node?._tag === "Error" ? node : undefined;
222
+ const omittedFields: Array<string> = [];
223
+
224
+ const read = (key: string): unknown => {
225
+ // A throwing accessor must not replace the original failure during diagnostic capture.
226
+ try {
227
+ return Reflect.get(value, key);
228
+ } catch {
229
+ omittedFields.push(key);
230
+
231
+ return undefined;
232
+ }
233
+ };
234
+
235
+ const tag = retained === undefined ? read("_tag") : retained.errorTag;
236
+ const name = read("name");
237
+ const message = read("message");
238
+ const stack = read("stack");
239
+ const reason = read("reason");
240
+ const code = read("code");
241
+ const cause = read("cause");
242
+ const errors = read("errors");
243
+ const capturedCause = cause === undefined ? undefined : nested(cause);
244
+ const capturedReason = reason === undefined ? undefined : nested(reason);
245
+ const capturedErrors: Array<Diagnostic> = [];
246
+
247
+ if (Array.isArray(errors))
248
+ for (const error of errors) {
249
+ if (capturedErrors.length >= 128 || remainingNodes <= 0) break;
250
+ capturedErrors.push(visit(error, depth + 2));
251
+ }
252
+
253
+ for (const field of retained?.omittedFields ?? []) omittedFields.push(text(field, 1_024));
254
+
255
+ const context: Record<string, string> = {};
256
+
257
+ for (const key of [
258
+ "agentId",
259
+ "threadId",
260
+ "runId",
261
+ "submissionId",
262
+ "attemptId",
263
+ "turnId",
264
+ "toolCallId",
265
+ "toolName",
266
+ "callId",
267
+ "check",
268
+ "operation",
269
+ ] as const) {
270
+ const field = retained === undefined ? read(key) : retained.context?.[key];
271
+
272
+ if (Predicate.isString(field)) context[key] = text(field, 1_024);
273
+ }
274
+
275
+ const result: Diagnostic = {
276
+ _tag: "Error",
277
+ ...(Predicate.isString(tag) ? { errorTag: text(tag, 1_024) } : {}),
278
+ ...(Predicate.isString(name) ? { name: text(name, 1_024) } : {}),
279
+ ...(Predicate.isString(message) ? { message: text(message) } : {}),
280
+ ...(Predicate.isString(stack) ? { stack: text(stack) } : {}),
281
+ ...(capturedReason === undefined ? {} : { reason: capturedReason }),
282
+ ...(Predicate.isString(code)
283
+ ? { code: text(code, 1_024) }
284
+ : Predicate.isNumber(code) && Number.isFinite(code)
285
+ ? { code }
286
+ : {}),
287
+ ...(capturedCause === undefined ? {} : { cause: capturedCause }),
288
+ ...(Array.isArray(errors) ? { errors: capturedErrors } : {}),
289
+ ...(Object.keys(context).length === 0 ? {} : { context }),
290
+ ...(omittedFields.length === 0 ? {} : { omittedFields }),
291
+ ...(retained?.truncated ||
292
+ (Array.isArray(errors) && errors.length > capturedErrors.length) ||
293
+ remainingText === 0
294
+ ? { truncated: true }
295
+ : {}),
296
+ };
297
+
298
+ seen.delete(value);
299
+
300
+ return result;
301
+ };
302
+
303
+ return visit(value, 0);
304
+ };
305
+
306
+ /** Bound and redact a diagnostic copy; canonical identities stay in their owning records. */
307
+ export const captureContext = (context: Context): Context => {
308
+ const diagnostic = capture(context);
309
+
310
+ return diagnostic._tag === "Error" ? (diagnostic.context ?? {}) : {};
311
+ };
312
+
313
+ /**
314
+ * Preserve the original value in-process; across JSON retain its structured diagnostic projection.
315
+ * Decoding returns Diagnostic data, never claims to reconstruct an application error class.
316
+ */
317
+ export const Value = Diagnostic.pipe(
318
+ Schema.decodeTo(
319
+ Schema.Unknown,
320
+ SchemaTransformation.transform({
321
+ decode: (diagnostic): unknown => diagnostic,
322
+ encode: (value: unknown) => capture(value),
323
+ }),
324
+ ),
325
+ );
326
+
327
+ /** Original local Effect Cause; its JSON codec preserves ordered typed, defect and interrupt reasons. */
328
+ export const Cause = Schema.Cause(Value, Value).pipe(
329
+ Schema.decodeTo(
330
+ Schema.declare<EffectCause.Cause<unknown>>(EffectCause.isCause),
331
+ SchemaTransformation.transform({ decode: (cause) => cause, encode: (cause) => cause }),
332
+ ),
333
+ );
334
+
335
+ /** Safe summary plus optional operator-private causal evidence retained at a terminal boundary. */
336
+ export const Failure = Schema.Struct({
337
+ errorTag: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
338
+ message: Text,
339
+ diagnostic: Schema.optionalKey(Diagnostic),
340
+ context: Schema.optionalKey(Context),
341
+ }).pipe(strictSchema);
342
+
343
+ export type Failure = typeof Failure.Type;
@@ -2,6 +2,7 @@ import { Schema } from "effect";
2
2
 
3
3
  import { AgentPolicy } from "./AgentPolicy.ts";
4
4
  import { Update } from "./AgentUpdates.ts";
5
+ import * as FailureDiagnostic from "./FailureDiagnostic.ts";
5
6
  import { AgentId, DelegationId, RunId, SettlementId, ThreadId, ToolCallId } from "./Identifiers.ts";
6
7
  import { Receipt } from "./Receipt.ts";
7
8
  import { SubagentExecutionFailure, SubagentGrant } from "./SubagentContract.ts";
@@ -82,11 +83,13 @@ export const WorkerHistoryEntry = Schema.Struct({
82
83
  export type WorkerHistoryEntry = typeof WorkerHistoryEntry.Type;
83
84
 
84
85
  /**
85
- * Closed host-boundary failures. Infrastructure diagnostics remain in host telemetry.
86
+ * Closed host-boundary failures. Original causes stay private and survive diagnostic transport.
86
87
  * `delivery-pending` confirms retained input, not destination acceptance or execution.
87
88
  * Keep the same idempotency key and parameters when reconciling; never launch a replacement.
88
89
  */
89
90
  export class WorkerError extends Schema.TaggedError<WorkerError>()("WorkerError", {
91
+ cause: Schema.optionalKey(FailureDiagnostic.Value),
92
+ stack: Schema.optionalKey(Schema.String),
90
93
  /** Pending input or concurrency pressure may clear without changing the request. */
91
94
  retryable: Schema.optionalKey(Schema.Literal(true)),
92
95
  operation: Schema.Literals([
@@ -34,7 +34,7 @@ const canonicalJson = (value: Schema.Json): string => {
34
34
  };
35
35
 
36
36
  /** Digest a JSON value using a stable, locale-independent object-key ordering (UTF-16 code units, RFC 8785 style). */
37
- export const digestJson = Effect.fn("Thread.digestJson")(function* (
37
+ export const digestJson = Effect.fnUntraced(function* (
38
38
  value: Schema.Json,
39
39
  ): Effect.fn.Return<Digest, DigestError, Crypto.Crypto> {
40
40
  const crypto = yield* Crypto.Crypto;
@@ -55,38 +55,32 @@ export const digestJson = Effect.fn("Thread.digestJson")(function* (
55
55
  });
56
56
 
57
57
  /** Digest a canonical batch together with the prior tail to form an append-only hash chain. */
58
- export const digestCanonicalBatch = Effect.fn("Thread.digestCanonicalBatch")(
59
- (
60
- previousTailDigest: Digest,
61
- batch: CanonicalBatch,
62
- ): Effect.Effect<Digest, DigestError, Crypto.Crypto> =>
63
- Schema.encodeEffect(CanonicalBatch)(batch).pipe(
64
- Effect.mapError(() => DigestError.make({ message: "Canonical batch encoding failed" })),
65
- Effect.flatMap((encoded) =>
66
- digestJson({
67
- previousTailDigest,
68
- batch: encoded,
69
- }),
70
- ),
58
+ export const digestCanonicalBatch = (
59
+ previousTailDigest: Digest,
60
+ batch: CanonicalBatch,
61
+ ): Effect.Effect<Digest, DigestError, Crypto.Crypto> =>
62
+ Schema.encodeEffect(CanonicalBatch)(batch).pipe(
63
+ Effect.mapError(() => DigestError.make({ message: "Canonical batch encoding failed" })),
64
+ Effect.flatMap((encoded) =>
65
+ digestJson({
66
+ previousTailDigest,
67
+ batch: encoded,
68
+ }),
71
69
  ),
72
- );
70
+ );
73
71
 
74
72
  /** Digest one schema-encoded Agent, Model, or Toolkit definition. */
75
- export const digestDefinition = Effect.fn("Thread.digestDefinition")((definition: Schema.Json) =>
76
- digestJson(definition),
77
- );
73
+ export const digestDefinition = (definition: Schema.Json) => digestJson(definition);
78
74
 
79
75
  /** Digest all replay-relevant definitions without hiding which authority changed. */
80
- export const digestDefinitions = Effect.fn("Thread.digestDefinitions")(
81
- (
82
- definitions: DefinitionDigestInput,
83
- ): Effect.Effect<DefinitionDigests, DigestError, Crypto.Crypto> =>
84
- Effect.all({
85
- agent: digestDefinition(definitions.agent),
86
- model: digestDefinition(definitions.model),
87
- tools: digestDefinition(definitions.tools),
88
- }).pipe(Effect.map((digests) => DefinitionDigests.make(digests))),
89
- );
76
+ export const digestDefinitions = (
77
+ definitions: DefinitionDigestInput,
78
+ ): Effect.Effect<DefinitionDigests, DigestError, Crypto.Crypto> =>
79
+ Effect.all({
80
+ agent: digestDefinition(definitions.agent),
81
+ model: digestDefinition(definitions.model),
82
+ tools: digestDefinition(definitions.tools),
83
+ }).pipe(Effect.map((digests) => DefinitionDigests.make(digests)));
90
84
 
91
85
  export const EMPTY_TAIL_DIGEST = Schema.decodeSync(Digest)(
92
86
  "0000000000000000000000000000000000000000000000000000000000000000",
@@ -31,10 +31,12 @@ import {
31
31
  AgentApprovalPending,
32
32
  AgentInputError,
33
33
  AgentToolAuthorizationDenied,
34
+ type AgentToolAuthorizationCheckError,
34
35
  PolicyLimit,
35
36
  } from "../core/AgentError.ts";
36
37
  import { AgentPolicy } from "../core/AgentPolicy.ts";
37
38
  import { UpdateError } from "../core/AgentUpdates.ts";
39
+ import * as FailureDiagnostic from "../core/FailureDiagnostic.ts";
38
40
  import {
39
41
  type ReceiptId,
40
42
  ThreadId,
@@ -2922,7 +2924,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
2922
2924
  .completeInput(submission)
2923
2925
  .pipe(
2924
2926
  Effect.mapError((cause) =>
2925
- LedgerError.make({ operation: "worker-completion", message: cause.reason }),
2927
+ LedgerError.make({ operation: "worker-completion", message: cause.reason, cause }),
2926
2928
  ),
2927
2929
  );
2928
2930
  const linkage = submission.parentLinkage;
@@ -4231,12 +4233,18 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
4231
4233
  }
4232
4234
  });
4233
4235
 
4234
- const failureOutcome = (error: unknown): Effect.Effect<AttemptOutcome> =>
4236
+ const failureOutcome = (
4237
+ error: unknown,
4238
+ cause: Cause.Cause<unknown>,
4239
+ context: FailureDiagnostic.Context,
4240
+ ): Effect.Effect<AttemptOutcome> =>
4235
4241
  Schema.decodeEffect(SettlementFailureDiagnostic)({
4236
4242
  errorTag: errorTagOf(error).slice(0, 256) || "UnknownError",
4237
4243
  message: errorMessageOf(error).slice(0, MAX_FAILURE_MESSAGE_LENGTH),
4244
+ diagnostic: FailureDiagnostic.capture(cause),
4245
+ context: FailureDiagnostic.captureContext(context),
4238
4246
  }).pipe(
4239
- // The exact diagnostic Schema admits no raw Cause, stack, or provider payload.
4247
+ // The exact diagnostic Schema admits only causal fields, never arbitrary provider payloads.
4240
4248
  Effect.orDie,
4241
4249
  Effect.map((result) => ({
4242
4250
  _tag: "failed" as const,
@@ -6729,7 +6737,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
6729
6737
  join: joinSubagent,
6730
6738
  };
6731
6739
 
6732
- const toolAuthorization: RunToolAuthorizationHook = {
6740
+ const toolAuthorization: RunToolAuthorizationHook<AgentToolAuthorizationCheckError> = {
6733
6741
  authorize: (request: RunToolAuthorizationRequest) =>
6734
6742
  inheritedGrant !== undefined &&
6735
6743
  !inheritedGrant.allowedToolNames.includes(request.call.toolName)
@@ -6750,6 +6758,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
6750
6758
 
6751
6759
  const options: RunOptions<
6752
6760
  | CoordinatorHalt
6761
+ | AgentToolAuthorizationCheckError
6753
6762
  | CompactionError
6754
6763
  | RunContextPreparationError
6755
6764
  | Agent.Failure<typeof agent>,
@@ -7364,9 +7373,11 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7364
7373
  ).pipe(Effect.provideService(IdGenerator, idGenerator));
7365
7374
 
7366
7375
  const result = yield* raced.pipe(
7367
- Effect.catch(
7376
+ Effect.catchCauseFilter(
7377
+ Cause.findError,
7368
7378
  (
7369
7379
  error,
7380
+ cause,
7370
7381
  ): Effect.Effect<
7371
7382
  | { readonly _tag: "failedRun"; readonly outcome: AttemptOutcome }
7372
7383
  | { readonly _tag: "aborted" }
@@ -7430,14 +7441,19 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7430
7441
  }
7431
7442
  }
7432
7443
 
7433
- // Settlement keeps only a bounded diagnostic. Report the live failure here,
7444
+ // Report the original live Cause here, before its private diagnostic projection,
7434
7445
  // after excluding suspensions, so hosts retain its cause and stack exactly
7435
7446
  // once per failed Run; reading or retrying its receipt never reports again.
7436
7447
  if (
7437
- !(error instanceof AgentApprovalDenied) &&
7438
- !(error instanceof AgentToolAuthorizationDenied)
7448
+ cause.reasons.some(
7449
+ (reason) =>
7450
+ reason._tag === "Die" ||
7451
+ (reason._tag === "Fail" &&
7452
+ !(reason.error instanceof AgentApprovalDenied) &&
7453
+ !(reason.error instanceof AgentToolAuthorizationDenied)),
7454
+ )
7439
7455
  )
7440
- yield* Effect.logError("Agent run failed", Cause.fail(error)).pipe(
7456
+ yield* Effect.logError("Agent run failed", cause).pipe(
7441
7457
  Effect.annotateLogs({
7442
7458
  agentId: agent.definition.id,
7443
7459
  runId,
@@ -7449,7 +7465,13 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7449
7465
 
7450
7466
  return {
7451
7467
  _tag: "failedRun" as const,
7452
- outcome: yield* failureOutcome(error),
7468
+ outcome: yield* failureOutcome(error, cause, {
7469
+ agentId: agent.definition.id,
7470
+ runId,
7471
+ threadId: ctx.threadId,
7472
+ submissionId,
7473
+ attemptId: lineage.attemptId,
7474
+ }),
7453
7475
  };
7454
7476
  });
7455
7477
  },
@@ -7574,7 +7596,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7574
7596
  .ensureOrigin(submission.workerAdmission.origin)
7575
7597
  .pipe(
7576
7598
  Effect.mapError((cause) =>
7577
- LedgerError.make({ operation: "worker-origin", message: cause.reason }),
7599
+ LedgerError.make({ operation: "worker-origin", message: cause.reason, cause }),
7578
7600
  ),
7579
7601
  );
7580
7602
  }
@@ -9007,7 +9029,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
9007
9029
  .ensureOrigin(submission.workerAdmission.origin)
9008
9030
  .pipe(
9009
9031
  Effect.mapError((cause) =>
9010
- LedgerError.make({ operation: "worker-origin", message: cause.reason }),
9032
+ LedgerError.make({ operation: "worker-origin", message: cause.reason, cause }),
9011
9033
  ),
9012
9034
  );
9013
9035
  }
@@ -9578,6 +9600,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
9578
9600
  ? "occupied"
9579
9601
  : "refused",
9580
9602
  code: `worker-${cause.reason}`,
9603
+ cause,
9581
9604
  }),
9582
9605
  ),
9583
9606
  );
@@ -9609,6 +9632,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
9609
9632
  ? "unavailable"
9610
9633
  : "refused",
9611
9634
  code: `message-${cause.reason}`,
9635
+ cause,
9612
9636
  }),
9613
9637
  ),
9614
9638
  );
@@ -9701,7 +9725,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
9701
9725
  .ensureOrigin(workerAdmission.origin)
9702
9726
  .pipe(
9703
9727
  Effect.mapError((cause) =>
9704
- LedgerError.make({ operation: "worker-origin", message: cause.reason }),
9728
+ LedgerError.make({ operation: "worker-origin", message: cause.reason, cause }),
9705
9729
  ),
9706
9730
  );
9707
9731
  }