@yielded/agent 0.1.0-beta.172 → 0.1.0-beta.174

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 (226) hide show
  1. package/dist/{Admin-BX9ns28E.d.mts → Admin-BHmp2V9p.d.mts} +25 -16
  2. package/dist/{Agent-9aBoIi74.d.mts → Agent-B39jRnFv.d.mts} +2 -2
  3. package/dist/{Agent-0WvBWWbM.mjs → Agent-BiRbr73L.mjs} +7 -6
  4. package/dist/Agent-BiRbr73L.mjs.map +1 -0
  5. package/dist/{AgentRuntime-CQ-qcETl.mjs → AgentRuntime-B6qZlD3f.mjs} +199 -191
  6. package/dist/AgentRuntime-B6qZlD3f.mjs.map +1 -0
  7. package/dist/{AgentRuntime-BfP14534.d.mts → AgentRuntime-BD3ZHAMq.d.mts} +10 -10
  8. package/dist/{Digest-DCuPlhNk.mjs → Digest-EpeqWEdv.mjs} +41 -14
  9. package/dist/Digest-EpeqWEdv.mjs.map +1 -0
  10. package/dist/{DurableAgentRuntime-BAmSkWYr.mjs → DurableAgentRuntime-CBBjRNJN.mjs} +1447 -1424
  11. package/dist/DurableAgentRuntime-CBBjRNJN.mjs.map +1 -0
  12. package/dist/{EventSource-BwliwQz9.d.mts → EventSource-C9aaJaXT.d.mts} +2 -2
  13. package/dist/{MessageDelivery-CeSDfHZa.d.mts → MessageDelivery-BGDm-c46.d.mts} +4 -4
  14. package/dist/{Messaging-C_6OOg_s.d.mts → Messaging-SVu6rWV8.d.mts} +12 -6
  15. package/dist/{MessagingHost-B8oKM7AC.d.mts → MessagingHost-C6SOAcoG.d.mts} +2 -2
  16. package/dist/{Output-Dsx6k52t.d.mts → Output-CRamN1Fy.d.mts} +2 -2
  17. package/dist/{PreparedInputAdmission-CxSJSiTl.d.mts → PreparedInputAdmission-BvDqlgiP.d.mts} +3 -3
  18. package/dist/{RunContinuation-DHYNAQFi.mjs → RunContinuation-4ZATcVLB.mjs} +113 -56
  19. package/dist/RunContinuation-4ZATcVLB.mjs.map +1 -0
  20. package/dist/{RunContinuation-FHM7XPjq.d.mts → RunContinuation-CTJ1jGrO.d.mts} +6 -6
  21. package/dist/{RunJournal-kfKVxJj7.mjs → RunJournal-CNZqA3Ge.mjs} +33 -5
  22. package/dist/RunJournal-CNZqA3Ge.mjs.map +1 -0
  23. package/dist/{RunOptions-D8zxFIrZ.d.mts → RunOptions-Be77lKVZ.d.mts} +2 -2
  24. package/dist/{RunStorage-uqPxps09.mjs → RunStorage-BHjuuERI.mjs} +9 -6
  25. package/dist/RunStorage-BHjuuERI.mjs.map +1 -0
  26. package/dist/{Scheduling-CVyfXlBR.mjs → Scheduling-NG8slKdI.mjs} +11 -11
  27. package/dist/Scheduling-NG8slKdI.mjs.map +1 -0
  28. package/dist/{Subagent-vpffemql.mjs → Subagent-CTIpnzlB.mjs} +53 -40
  29. package/dist/Subagent-CTIpnzlB.mjs.map +1 -0
  30. package/dist/{Subagent-E6tvd7-k.d.mts → Subagent-DKhZA72u.d.mts} +19 -8
  31. package/dist/{SubagentHost-B6w2m-SI.d.mts → SubagentHost-D1xBgn9g.d.mts} +2 -2
  32. package/dist/{Subscription-DONig54Q.d.mts → Subscription-BNg9VCRl.d.mts} +2 -2
  33. package/dist/{SubscriptionInput-mJIRxWJM.d.mts → SubscriptionInput-BUoXv-_K.d.mts} +2 -2
  34. package/dist/{ThreadImport-CoQTZywm.d.mts → ThreadImport-CMgzttvr.d.mts} +43 -43
  35. package/dist/{ThreadImport-DckUMEqJ.mjs → ThreadImport-CodYoJoa.mjs} +7 -7
  36. package/dist/ThreadImport-CodYoJoa.mjs.map +1 -0
  37. package/dist/{ThreadStore-CEekRa80.mjs → ThreadStore-59uDGXul.mjs} +163 -84
  38. package/dist/ThreadStore-59uDGXul.mjs.map +1 -0
  39. package/dist/{ThreadTransfer-B4gTQLIl.mjs → ThreadTransfer-3jigUl3g.mjs} +3 -3
  40. package/dist/{ThreadTransfer-B4gTQLIl.mjs.map → ThreadTransfer-3jigUl3g.mjs.map} +1 -1
  41. package/dist/{WorkerAdmission-DFVbbVh2.mjs → WorkerAdmission-DDYJdOsU.mjs} +4 -4
  42. package/dist/{WorkerAdmission-DFVbbVh2.mjs.map → WorkerAdmission-DDYJdOsU.mjs.map} +1 -1
  43. package/dist/{agent-registration-ByMZ_cK5.mjs → agent-registration-DRbyhAu-.mjs} +69 -56
  44. package/dist/agent-registration-DRbyhAu-.mjs.map +1 -0
  45. package/dist/capabilities/Approval.mjs +2 -2
  46. package/dist/capabilities/Approval.mjs.map +1 -1
  47. package/dist/capabilities/BrowserUse.d.mts +102 -1867
  48. package/dist/capabilities/BrowserUse.mjs +74 -798
  49. package/dist/capabilities/BrowserUse.mjs.map +1 -1
  50. package/dist/capabilities/Budget.mjs +3 -1
  51. package/dist/capabilities/Budget.mjs.map +1 -1
  52. package/dist/capabilities/CodeMode.d.mts +1 -1
  53. package/dist/capabilities/CodeMode.mjs +16 -9
  54. package/dist/capabilities/CodeMode.mjs.map +1 -1
  55. package/dist/capabilities/Commands.d.mts +1 -1
  56. package/dist/capabilities/McpClient.mjs +1 -1
  57. package/dist/capabilities/McpClient.mjs.map +1 -1
  58. package/dist/capabilities/Messaging.d.mts +1 -1
  59. package/dist/capabilities/Messaging.mjs +4 -4
  60. package/dist/capabilities/Messaging.mjs.map +1 -1
  61. package/dist/capabilities/ModelContext.mjs +7 -5
  62. package/dist/capabilities/ModelContext.mjs.map +1 -1
  63. package/dist/capabilities/Redaction.mjs +49 -19
  64. package/dist/capabilities/Redaction.mjs.map +1 -1
  65. package/dist/capabilities/RunHooks.d.mts +1 -1
  66. package/dist/capabilities/RunHooks.mjs +1 -1
  67. package/dist/capabilities/RunHooks.mjs.map +1 -1
  68. package/dist/capabilities/Subagent.d.mts +1 -1
  69. package/dist/capabilities/Subagent.mjs +1 -1
  70. package/dist/capabilities/SubagentReservations.mjs +3 -1
  71. package/dist/capabilities/SubagentReservations.mjs.map +1 -1
  72. package/dist/capabilities/WebCapture.mjs.map +1 -1
  73. package/dist/capabilities/WebSearch.mjs +7 -7
  74. package/dist/capabilities/WebSearch.mjs.map +1 -1
  75. package/dist/core/Agent.d.mts +1 -1
  76. package/dist/core/Agent.mjs +1 -1
  77. package/dist/core/AgentUpdates.mjs +8 -8
  78. package/dist/core/AgentUpdates.mjs.map +1 -1
  79. package/dist/core/Thread.mjs +1 -1
  80. package/dist/core/Thread.mjs.map +1 -1
  81. package/dist/core/Usage.mjs +2 -2
  82. package/dist/core/Usage.mjs.map +1 -1
  83. package/dist/durable/Admin.d.mts +1 -1
  84. package/dist/durable/AgentRegistration.d.mts +1 -1
  85. package/dist/durable/AgentRegistration.mjs +1 -1
  86. package/dist/durable/Certification.d.mts +1 -1
  87. package/dist/durable/CommittedActivity.d.mts +1 -1
  88. package/dist/durable/CommittedActivity.mjs +2 -2
  89. package/dist/durable/Digest.mjs +1 -1
  90. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  91. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  92. package/dist/durable/EventSource.d.mts +1 -1
  93. package/dist/durable/EventSource.mjs +6 -6
  94. package/dist/durable/EventSource.mjs.map +1 -1
  95. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  96. package/dist/durable/LifecyclePublication.d.mts +1 -1
  97. package/dist/durable/LifecyclePublication.mjs +1 -1
  98. package/dist/durable/MessageDelivery.d.mts +1 -1
  99. package/dist/durable/MessageDelivery.mjs +1 -1
  100. package/dist/durable/MessageDeliveryStoreConformance.d.mts +2 -2
  101. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  102. package/dist/durable/PersistentHistory.d.mts +1 -1
  103. package/dist/durable/PersistentHistory.mjs +3 -3
  104. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  105. package/dist/durable/RecordFormat.mjs +2 -2
  106. package/dist/durable/RecordFormat.mjs.map +1 -1
  107. package/dist/durable/Recovery.d.mts +1 -1
  108. package/dist/durable/RunContinuation.d.mts +2 -2
  109. package/dist/durable/RunContinuation.mjs +2 -2
  110. package/dist/durable/RunJournal.d.mts +1 -1
  111. package/dist/durable/RunJournal.mjs +1 -1
  112. package/dist/durable/RunStorage.d.mts +1 -1
  113. package/dist/durable/RunStorage.mjs +1 -1
  114. package/dist/durable/Scheduling.d.mts +1 -1
  115. package/dist/durable/Scheduling.mjs +1 -1
  116. package/dist/durable/SettlementPublisher.d.mts +1 -1
  117. package/dist/durable/SettlementPublisher.mjs +2 -2
  118. package/dist/durable/SubmissionLedger.d.mts +1 -1
  119. package/dist/durable/SubmissionLedger.mjs +5 -1
  120. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  121. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  122. package/dist/durable/SubmissionLedgerConformance.mjs +4 -4
  123. package/dist/durable/SubmissionStatus.d.mts +1 -1
  124. package/dist/durable/Subscription.d.mts +1 -1
  125. package/dist/durable/SubscriptionInput.d.mts +1 -1
  126. package/dist/durable/SubscriptionInput.mjs +3 -3
  127. package/dist/durable/SubscriptionInput.mjs.map +1 -1
  128. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  129. package/dist/durable/SubscriptionStoreConformance.mjs +2 -2
  130. package/dist/durable/SubscriptionStoreConformance.mjs.map +1 -1
  131. package/dist/durable/SubscriptionTools.d.mts +2 -2
  132. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  133. package/dist/durable/Subscriptions.d.mts +5 -5
  134. package/dist/durable/Subscriptions.mjs +2 -2
  135. package/dist/durable/Subscriptions.mjs.map +1 -1
  136. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  137. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  138. package/dist/durable/ThreadContextHistory.mjs +1 -1
  139. package/dist/durable/ThreadImport.d.mts +1 -1
  140. package/dist/durable/ThreadImport.mjs +1 -1
  141. package/dist/durable/ThreadInvariants.d.mts +3 -3
  142. package/dist/durable/ThreadInvariants.mjs +4 -4
  143. package/dist/durable/ThreadInvariants.mjs.map +1 -1
  144. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  145. package/dist/durable/ThreadReader.d.mts +1 -1
  146. package/dist/durable/ThreadReader.mjs +1 -1
  147. package/dist/durable/ThreadStore.d.mts +1 -1
  148. package/dist/durable/ThreadStore.mjs +1 -1
  149. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  150. package/dist/durable/ThreadStoreConformance.mjs +3 -3
  151. package/dist/durable/ThreadTransfer.d.mts +1 -1
  152. package/dist/durable/ThreadTransfer.mjs +1 -1
  153. package/dist/durable/ThreadWork.d.mts +1 -1
  154. package/dist/durable/ThreadWork.mjs +4 -4
  155. package/dist/durable/ThreadWork.mjs.map +1 -1
  156. package/dist/durable/WorkerAdmission.d.mts +1 -1
  157. package/dist/durable/WorkerAdmission.mjs +1 -1
  158. package/dist/durable/WorkerHost.d.mts +2 -2
  159. package/dist/engine/AgentRuntime.d.mts +1 -1
  160. package/dist/engine/AgentRuntime.mjs +1 -1
  161. package/dist/engine/MessagingHost.d.mts +1 -1
  162. package/dist/engine/Output.d.mts +1 -1
  163. package/dist/engine/RunOptions.d.mts +1 -1
  164. package/dist/engine/SubagentHost.d.mts +1 -1
  165. package/dist/index.d.mts +19 -19
  166. package/dist/index.mjs +14 -14
  167. package/dist/{run-context-reader-OcsZr4bk.d.mts → run-context-reader-CC7ZD95F.d.mts} +2 -2
  168. package/package.json +1 -1
  169. package/src/capabilities/Approval.ts +58 -60
  170. package/src/capabilities/BrowserUse.ts +89 -944
  171. package/src/capabilities/Budget.ts +8 -1
  172. package/src/capabilities/CodeMode.ts +57 -33
  173. package/src/capabilities/McpClient.ts +2 -2
  174. package/src/capabilities/Messaging.ts +51 -55
  175. package/src/capabilities/ModelContext.ts +5 -2
  176. package/src/capabilities/Redaction.ts +64 -34
  177. package/src/capabilities/RunHooks.ts +52 -55
  178. package/src/capabilities/Subagent.ts +33 -33
  179. package/src/capabilities/SubagentReservations.ts +9 -1
  180. package/src/capabilities/WebCapture.ts +6 -0
  181. package/src/capabilities/WebSearch.ts +10 -7
  182. package/src/capabilities/internal/subagent-background.ts +202 -158
  183. package/src/core/Agent.ts +11 -6
  184. package/src/core/AgentUpdates.ts +8 -8
  185. package/src/core/Thread.ts +9 -10
  186. package/src/core/Usage.ts +7 -8
  187. package/src/core/internal/agent-updates.ts +1 -0
  188. package/src/durable/DurableAgentRuntime.ts +645 -625
  189. package/src/durable/EventSource.ts +15 -9
  190. package/src/durable/RecordFormat.ts +5 -3
  191. package/src/durable/RunContinuation.ts +233 -81
  192. package/src/durable/RunJournal.ts +10 -9
  193. package/src/durable/RunStorage.ts +122 -124
  194. package/src/durable/Scheduling.ts +639 -651
  195. package/src/durable/SubmissionLedger.ts +4 -0
  196. package/src/durable/SubscriptionInput.ts +5 -3
  197. package/src/durable/SubscriptionStoreConformance.ts +23 -23
  198. package/src/durable/Subscriptions.ts +18 -19
  199. package/src/durable/ThreadImport.ts +18 -19
  200. package/src/durable/ThreadInvariants.ts +2 -1
  201. package/src/durable/ThreadStore.ts +215 -111
  202. package/src/durable/ThreadWork.ts +1 -2
  203. package/src/durable/internal/StorageOwner.ts +10 -4
  204. package/src/durable/internal/agent-registration.ts +96 -89
  205. package/src/durable/internal/canonical-json.ts +61 -20
  206. package/src/durable/internal/prompt-history-cache.ts +13 -4
  207. package/src/durable/internal/record-encoding.ts +120 -64
  208. package/src/durable/internal/schedule-time.ts +6 -6
  209. package/src/durable/internal/usage-totals.ts +38 -0
  210. package/src/durable/internal/worker-host.ts +69 -74
  211. package/src/engine/internal/agent-runtime.ts +1067 -1018
  212. package/src/engine/internal/provisional-text.ts +12 -10
  213. package/src/engine/internal/tool-exposure.ts +22 -18
  214. package/src/engine/internal/tool-telemetry.ts +12 -7
  215. package/dist/Agent-0WvBWWbM.mjs.map +0 -1
  216. package/dist/AgentRuntime-CQ-qcETl.mjs.map +0 -1
  217. package/dist/Digest-DCuPlhNk.mjs.map +0 -1
  218. package/dist/DurableAgentRuntime-BAmSkWYr.mjs.map +0 -1
  219. package/dist/RunContinuation-DHYNAQFi.mjs.map +0 -1
  220. package/dist/RunJournal-kfKVxJj7.mjs.map +0 -1
  221. package/dist/RunStorage-uqPxps09.mjs.map +0 -1
  222. package/dist/Scheduling-CVyfXlBR.mjs.map +0 -1
  223. package/dist/Subagent-vpffemql.mjs.map +0 -1
  224. package/dist/ThreadImport-DckUMEqJ.mjs.map +0 -1
  225. package/dist/ThreadStore-CEekRa80.mjs.map +0 -1
  226. package/dist/agent-registration-ByMZ_cK5.mjs.map +0 -1
@@ -961,24 +961,20 @@ const schemaClassToolPartPreflight = <Tools extends Record<string, Tool.Any>>(
961
961
  try {
962
962
  if (part === null || typeof part !== "object") return undefined;
963
963
 
964
- const read = (key: string): unknown => {
964
+ const read = (key: string): PropertyDescriptor | undefined => {
965
965
  const descriptor = Object.getOwnPropertyDescriptor(part, key);
966
966
 
967
- if (descriptor === undefined || !("value" in descriptor)) {
968
- throw new TypeError(`response part ${key} must be an own data property`);
969
- }
970
-
971
- return descriptor.value;
967
+ return descriptor !== undefined && "value" in descriptor ? descriptor : undefined;
972
968
  };
973
969
 
974
- const type = read("type");
970
+ const type = read("type")?.value;
975
971
 
976
972
  if (type !== "tool-call" && type !== "tool-result") return undefined;
977
- const name = read("name");
973
+ const name = read("name")?.value;
978
974
 
979
975
  if (typeof name !== "string" || !hasTool(toolkit.tools, name)) return undefined;
980
976
  const tool = toolkit.tools[name];
981
- const payload = read(type === "tool-call" ? "params" : "result");
977
+ const payload: unknown = read(type === "tool-call" ? "params" : "result")?.value;
982
978
 
983
979
  if (payload === null || typeof payload !== "object") return undefined;
984
980
  const payloadPrototype = Object.getPrototypeOf(payload);
@@ -1007,7 +1003,10 @@ const schemaClassToolPartPreflight = <Tools extends Record<string, Tool.Any>>(
1007
1003
  ];
1008
1004
 
1009
1005
  for (const key of retainedKeys) {
1010
- preflight[key] = read(key);
1006
+ const descriptor = read(key);
1007
+
1008
+ if (descriptor === undefined) return undefined;
1009
+ preflight[key] = descriptor.value;
1011
1010
  }
1012
1011
 
1013
1012
  return preflight;
@@ -1170,11 +1169,14 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1170
1169
 
1171
1170
  const retainedBytes = yield* inspectModelResponsePartCapacity(usage, encoded, limits);
1172
1171
 
1172
+ if (typeof structuredCloneFunction !== "function") {
1173
+ return yield* ModelProtocolError.make({
1174
+ message: "Model response part could not be converted into engine-owned data",
1175
+ });
1176
+ }
1177
+
1173
1178
  const ownedEncoded = yield* Effect.try({
1174
1179
  try: () => {
1175
- if (typeof structuredCloneFunction !== "function") {
1176
- throw new TypeError("structuredClone is unavailable");
1177
- }
1178
1180
  const cloned: unknown = Reflect.apply(structuredCloneFunction, globalThis, [encoded]);
1179
1181
 
1180
1182
  return cloned;
@@ -1244,7 +1246,10 @@ interface PreparedToolCall<Tools extends Record<string, Tool.Any>> {
1244
1246
  readonly call: Response.ToolCallPart<string, Schema.Json>;
1245
1247
  readonly name: keyof Tools & string;
1246
1248
  readonly toolCallId: ToolCallId;
1247
- readonly validation: Result.Result<Tool.Parameters<ToolUnion<Tools>>, ToolParameterRejection>;
1249
+ readonly validation: Result.Result<
1250
+ Option.Option<Tool.Parameters<ToolUnion<Tools>>>,
1251
+ ToolParameterRejection
1252
+ >;
1248
1253
  readonly tool: ToolUnion<Tools>;
1249
1254
  readonly declarationIndex: number;
1250
1255
  }
@@ -1375,9 +1380,15 @@ const decodeToolCallParameters = <Tools extends Record<string, Tool.Any>>(
1375
1380
  );
1376
1381
  };
1377
1382
 
1383
+ /** Native identity proves encoding does not transform the already validated parameters. */
1384
+ const reusesValidatedToolParameters = (tool: Tool.Any): boolean =>
1385
+ (tool.needsApproval === undefined || tool.needsApproval === false) &&
1386
+ SchemaAST.toEncoded(tool.parametersSchema.ast) === tool.parametersSchema.ast;
1387
+
1378
1388
  /**
1379
- * Decode executable parameters for approval while retaining their encoded form for
1380
- * Toolkit.handle. A recorded rejection is a terminal failure, never an executable call.
1389
+ * Fresh responses and resumed batches validate every call before reaching this boundary.
1390
+ * Identity encodings with no approval need no second decoded value. Other codecs retain the
1391
+ * complete batch preflight, including validation of their canonical round trip, before dispatch.
1381
1392
  */
1382
1393
  const prepareToolCall = <Tools extends Record<string, Tool.Any>>(
1383
1394
  toolkit: Toolkit.WithHandler<Tools>,
@@ -1398,17 +1409,26 @@ const prepareToolCall = <Tools extends Record<string, Tool.Any>>(
1398
1409
  }
1399
1410
  const tool = toolkit.tools[name] as ToolUnion<Tools>;
1400
1411
 
1401
- return Effect.gen(function* () {
1402
- const toolCallId = yield* decodeToolCallId(call.id);
1412
+ return Effect.flatMap(decodeToolCallId(call.id), (toolCallId) => {
1413
+ const prepared = (
1414
+ validation: PreparedToolCall<Tools>["validation"],
1415
+ ): PreparedToolCall<Tools> => ({
1416
+ call,
1417
+ name,
1418
+ toolCallId,
1419
+ validation,
1420
+ tool,
1421
+ declarationIndex,
1422
+ });
1403
1423
 
1404
- const validation: PreparedToolCall<Tools>["validation"] =
1405
- rejection === undefined
1406
- ? Result.succeed(
1407
- yield* decodeToolCallParameters<Tools>(tool, call.name, copyJson(call.params)),
1408
- )
1409
- : Result.fail(rejection);
1424
+ if (rejection !== undefined) return Effect.succeed(prepared(Result.fail(rejection)));
1425
+ if (reusesValidatedToolParameters(tool))
1426
+ return Effect.succeed(prepared(Result.succeed(Option.none())));
1410
1427
 
1411
- return { call, name, toolCallId, validation, tool, declarationIndex };
1428
+ return Effect.map(
1429
+ decodeToolCallParameters<Tools>(tool, call.name, copyJson(call.params)),
1430
+ (parameters) => prepared(Result.succeed(Option.some(parameters))),
1431
+ );
1412
1432
  });
1413
1433
  };
1414
1434
 
@@ -1453,47 +1473,56 @@ const effectiveRunBounds = (
1453
1473
  maxToolCalls: boundedAllowance(policy.maxToolCalls, options.toolCallAllowance),
1454
1474
  });
1455
1475
 
1456
- const decodeResumedSettledCall = Effect.fnUntraced(function* (input: unknown) {
1457
- const raw = yield* Effect.try({
1458
- try: () => {
1459
- if (input === null || typeof input !== "object") {
1460
- throw new TypeError("settled Tool Call must be an object");
1461
- }
1462
-
1463
- const readOwnDataProperty = (key: "id" | "result" | "isFailure"): unknown => {
1464
- const descriptor = Object.getOwnPropertyDescriptor(input, key);
1465
-
1466
- if (descriptor === undefined || !("value" in descriptor)) {
1467
- throw new TypeError(`settled Tool Call ${key} must be an own data property`);
1468
- }
1476
+ const readSnapshotProperty = Effect.fnUntraced(function* (
1477
+ input: object,
1478
+ key: string,
1479
+ invalid: () => ModelProtocolError,
1480
+ optional = false,
1481
+ ): Effect.fn.Return<unknown, ModelProtocolError> {
1482
+ const descriptor = yield* Effect.try({
1483
+ try: () => Object.getOwnPropertyDescriptor(input, key),
1484
+ catch: invalid,
1485
+ });
1469
1486
 
1470
- return descriptor.value;
1471
- };
1487
+ if (descriptor === undefined && optional) return undefined;
1488
+ if (descriptor === undefined || !("value" in descriptor)) return yield* invalid();
1472
1489
 
1473
- const selection = Object.getOwnPropertyDescriptor(input, "toolSelection");
1490
+ return descriptor.value;
1491
+ });
1474
1492
 
1475
- if (selection !== undefined && !("value" in selection))
1476
- throw new TypeError("settled Tool selection must be an own data property");
1477
- const rejected = Object.getOwnPropertyDescriptor(input, "budgetRejected");
1493
+ const decodeResumedSettledCall = Effect.fnUntraced(function* (input: unknown) {
1494
+ const invalid = () =>
1495
+ ModelProtocolError.make({
1496
+ message: "Turn resume contains an invalid settled Tool Call",
1497
+ });
1478
1498
 
1479
- if (rejected !== undefined && !("value" in rejected)) {
1480
- throw new TypeError("settled Tool Call budgetRejected must be an own data property");
1481
- }
1499
+ if (input === null || typeof input !== "object") return yield* invalid();
1500
+ const id = yield* readSnapshotProperty(input, "id", invalid);
1501
+ const resultValue = yield* readSnapshotProperty(input, "result", invalid);
1502
+ const isFailure = yield* readSnapshotProperty(input, "isFailure", invalid);
1482
1503
 
1483
- return {
1484
- id: readOwnDataProperty("id"),
1485
- result: readOwnDataProperty("result"),
1486
- isFailure: readOwnDataProperty("isFailure"),
1487
- ...(rejected === undefined ? {} : { budgetRejected: rejected.value }),
1488
- ...(selection === undefined ? {} : { toolSelection: selection.value }),
1489
- };
1490
- },
1491
- catch: () =>
1492
- ModelProtocolError.make({
1493
- message: "Turn resume contains an invalid settled Tool Call",
1494
- }),
1504
+ const { selection, rejected } = yield* Effect.try({
1505
+ try: () => ({
1506
+ selection: Object.getOwnPropertyDescriptor(input, "toolSelection"),
1507
+ rejected: Object.getOwnPropertyDescriptor(input, "budgetRejected"),
1508
+ }),
1509
+ catch: invalid,
1495
1510
  });
1496
1511
 
1512
+ if (
1513
+ (selection !== undefined && !("value" in selection)) ||
1514
+ (rejected !== undefined && !("value" in rejected))
1515
+ )
1516
+ return yield* invalid();
1517
+
1518
+ const raw = {
1519
+ id,
1520
+ result: resultValue,
1521
+ isFailure,
1522
+ ...(rejected === undefined ? {} : { budgetRejected: rejected.value }),
1523
+ ...(selection === undefined ? {} : { toolSelection: selection.value }),
1524
+ };
1525
+
1497
1526
  const result = boundedCanonicalJsonSnapshot(raw.result, MAX_RESUMED_RESULT_BYTES);
1498
1527
 
1499
1528
  if (result === undefined) {
@@ -1514,106 +1543,73 @@ const decodeResumedSettledCall = Effect.fnUntraced(function* (input: unknown) {
1514
1543
  );
1515
1544
  });
1516
1545
 
1517
- const snapshotResumedSettledCalls = (resume: unknown, maximum: number) =>
1518
- Effect.try({
1519
- try: () => {
1520
- if (resume === null || typeof resume !== "object") {
1521
- throw new TypeError("Turn resume must be an object");
1522
- }
1523
- const settledDescriptor = Object.getOwnPropertyDescriptor(resume, "settled");
1524
-
1525
- if (settledDescriptor === undefined || !("value" in settledDescriptor)) {
1526
- throw new TypeError("Turn resume settled must be an own data property");
1527
- }
1528
- const settled = settledDescriptor.value;
1546
+ const snapshotResumedSettledCalls = Effect.fnUntraced(function* (resume: unknown, maximum: number) {
1547
+ const invalid = () =>
1548
+ ModelProtocolError.make({
1549
+ message: "Turn resume contains an invalid settled Tool Call collection",
1550
+ });
1529
1551
 
1530
- if (!Array.isArray(settled)) {
1531
- throw new TypeError("Turn resume settled must be an array");
1532
- }
1533
- const lengthDescriptor = Object.getOwnPropertyDescriptor(settled, "length");
1552
+ if (resume === null || typeof resume !== "object") return yield* invalid();
1553
+ const settled = yield* readSnapshotProperty(resume, "settled", invalid);
1554
+ const isArray = yield* Effect.try({ try: () => Array.isArray(settled), catch: invalid });
1534
1555
 
1535
- if (
1536
- lengthDescriptor === undefined ||
1537
- !("value" in lengthDescriptor) ||
1538
- !Number.isSafeInteger(lengthDescriptor.value) ||
1539
- lengthDescriptor.value < 0 ||
1540
- lengthDescriptor.value > maximum
1541
- ) {
1542
- throw new TypeError("Turn resume settled has an invalid length");
1543
- }
1544
- const snapshot: Array<unknown> = [];
1556
+ if (!isArray || settled === null || typeof settled !== "object") return yield* invalid();
1557
+ const length = yield* readSnapshotProperty(settled, "length", invalid);
1545
1558
 
1546
- for (let index = 0; index < lengthDescriptor.value; index += 1) {
1547
- const entryDescriptor = Object.getOwnPropertyDescriptor(settled, String(index));
1559
+ if (
1560
+ typeof length !== "number" ||
1561
+ !Number.isSafeInteger(length) ||
1562
+ length < 0 ||
1563
+ length > maximum
1564
+ ) {
1565
+ return yield* invalid();
1566
+ }
1567
+ const snapshot: Array<unknown> = [];
1548
1568
 
1549
- if (entryDescriptor === undefined || !("value" in entryDescriptor)) {
1550
- throw new TypeError("Turn resume settled entries must be own data properties");
1551
- }
1552
- snapshot.push(entryDescriptor.value);
1553
- }
1569
+ for (let index = 0; index < length; index += 1) {
1570
+ snapshot.push(yield* readSnapshotProperty(settled, String(index), invalid));
1571
+ }
1554
1572
 
1555
- return snapshot;
1556
- },
1557
- catch: () =>
1558
- ModelProtocolError.make({
1559
- message: "Turn resume contains an invalid settled Tool Call collection",
1560
- }),
1561
- });
1573
+ return snapshot;
1574
+ });
1562
1575
 
1563
1576
  const decodeResumeUsage = Effect.fnUntraced(function* (input: unknown) {
1564
- const snapshot = yield* Effect.try({
1565
- try: () => {
1566
- if (input === null || typeof input !== "object") {
1567
- throw new TypeError("Run resume usage must be an object");
1568
- }
1569
-
1570
- const read = (key: keyof RunResumeUsage, optional = false): unknown => {
1571
- const descriptor = Object.getOwnPropertyDescriptor(input, key);
1572
-
1573
- if (descriptor === undefined && optional) return undefined;
1574
- if (descriptor === undefined || !("value" in descriptor)) {
1575
- throw new TypeError(`Run resume usage ${key} must be an own data property`);
1576
- }
1577
+ const invalid = () =>
1578
+ ModelProtocolError.make({
1579
+ message:
1580
+ "Run resume usage requires own data properties with non-negative safe-integer totals and last-call tokens no greater than their cumulative totals",
1581
+ });
1577
1582
 
1578
- return descriptor.value;
1579
- };
1583
+ if (input === null || typeof input !== "object") return yield* invalid();
1584
+ const snapshot: Record<string, unknown> = {};
1580
1585
 
1581
- const optional = Object.fromEntries(
1582
- (
1583
- [
1584
- "usageStatus",
1585
- "pricingStatus",
1586
- "unobservedModelCalls",
1587
- "children",
1588
- "modelRestarts",
1589
- "webSearchCalls",
1590
- ] as const
1591
- )
1592
- .map((key) => [key, read(key, true)])
1593
- .filter(([, value]) => value !== undefined),
1594
- );
1586
+ for (const key of [
1587
+ "usageStatus",
1588
+ "pricingStatus",
1589
+ "unobservedModelCalls",
1590
+ "children",
1591
+ "modelRestarts",
1592
+ "webSearchCalls",
1593
+ ] satisfies ReadonlyArray<keyof RunResumeUsage>) {
1594
+ const value = yield* readSnapshotProperty(input, key, invalid, true);
1595
1595
 
1596
- return {
1597
- ...optional,
1598
- modelCalls: read("modelCalls"),
1599
- inputTokens: read("inputTokens"),
1600
- outputTokens: read("outputTokens"),
1601
- lastInputTokens: read("lastInputTokens"),
1602
- lastOutputTokens: read("lastOutputTokens"),
1603
- costMicrousd: read("costMicrousd"),
1604
- committedTurns: read("committedTurns"),
1605
- toolCalls: read("toolCalls"),
1606
- programmaticToolCalls: read("programmaticToolCalls"),
1607
- consecutiveToolFailures: read("consecutiveToolFailures"),
1608
- finalizationUsed: read("finalizationUsed"),
1609
- };
1610
- },
1611
- catch: () =>
1612
- ModelProtocolError.make({
1613
- message:
1614
- "Run resume usage requires own data properties with non-negative safe-integer totals and last-call tokens no greater than their cumulative totals",
1615
- }),
1616
- });
1596
+ if (value !== undefined) snapshot[key] = value;
1597
+ }
1598
+ for (const key of [
1599
+ "modelCalls",
1600
+ "inputTokens",
1601
+ "outputTokens",
1602
+ "lastInputTokens",
1603
+ "lastOutputTokens",
1604
+ "costMicrousd",
1605
+ "committedTurns",
1606
+ "toolCalls",
1607
+ "programmaticToolCalls",
1608
+ "consecutiveToolFailures",
1609
+ "finalizationUsed",
1610
+ ] satisfies ReadonlyArray<keyof RunResumeUsage>) {
1611
+ snapshot[key] = yield* readSnapshotProperty(input, key, invalid);
1612
+ }
1617
1613
 
1618
1614
  return yield* Schema.decodeUnknownEffect(RunResumeUsageSchema)(snapshot).pipe(
1619
1615
  Effect.mapError(() =>
@@ -1802,7 +1798,8 @@ const prepareApprovalRequest = <Tools extends Record<string, Tool.Any>>(
1802
1798
  context,
1803
1799
  Effect.gen(function* () {
1804
1800
  if (Result.isFailure(prepared.validation)) return undefined;
1805
- const decodedParams = prepared.validation.success;
1801
+ if (Option.isNone(prepared.validation.success)) return undefined;
1802
+ const decodedParams = prepared.validation.success.value;
1806
1803
  const approval = prepared.tool.needsApproval;
1807
1804
 
1808
1805
  const required =
@@ -2392,22 +2389,21 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2392
2389
  if (result.isFailure) return yield* failureMarker();
2393
2390
  });
2394
2391
 
2395
- const failTerminalResult = (cause: Cause.Cause<ToolExecutionError>) =>
2396
- Effect.gen(function* () {
2397
- terminal = true;
2398
- terminalResult = undefined;
2399
- propagatedFailure = cause;
2400
- // The raw failure has no canonical success/result, matching the prior propagated path.
2401
- yield* decodeToolCallId(call.id);
2402
- trace.finalToolResultIds.add(call.id);
2403
- terminalResultCommitted = true;
2404
- yield* terminalTelemetry("failure", "propagated");
2405
- yield* publishEvent(context, () =>
2406
- makeToolFailedEvent(context, turnId, call, Cause.squash(cause), "propagated"),
2407
- );
2392
+ const failTerminalResult = Effect.fnUntraced(function* (cause: Cause.Cause<ToolExecutionError>) {
2393
+ terminal = true;
2394
+ terminalResult = undefined;
2395
+ propagatedFailure = cause;
2396
+ // The raw failure has no canonical success/result, matching the prior propagated path.
2397
+ yield* decodeToolCallId(call.id);
2398
+ trace.finalToolResultIds.add(call.id);
2399
+ terminalResultCommitted = true;
2400
+ yield* terminalTelemetry("failure", "propagated");
2401
+ yield* publishEvent(context, () =>
2402
+ makeToolFailedEvent(context, turnId, call, Cause.squash(cause), "propagated"),
2403
+ );
2408
2404
 
2409
- return yield* failureMarker();
2410
- });
2405
+ return yield* failureMarker();
2406
+ });
2411
2407
 
2412
2408
  const measured = started.pipe(
2413
2409
  Effect.andThen(results),
@@ -2491,6 +2487,7 @@ const executeToolBatch = Effect.fnUntraced(function* <
2491
2487
  | AgentUpdateAcceptance
2492
2488
  | ToolSpanTelemetry
2493
2489
  | ProgrammaticToolAuthorization
2490
+ | Scope.Scope
2494
2491
  | Tool.HandlerServices<ToolUnion<Tools>>
2495
2492
  > {
2496
2493
  const exposed = context.toolExposure?.exposedToolNames;
@@ -2513,22 +2510,38 @@ const executeToolBatch = Effect.fnUntraced(function* <
2513
2510
  message: "Tool batch calls a Tool outside its request exposure or current visibility",
2514
2511
  });
2515
2512
 
2516
- const prepared = yield* prepareWithinDeadline(
2517
- context,
2518
- Effect.forEach(
2519
- calls.flatMap((call, declarationIndex) =>
2520
- settledCallIds?.has(call.id) ? [] : [{ call, declarationIndex }],
2521
- ),
2522
- ({ call, declarationIndex }) =>
2523
- prepareToolCall(
2524
- toolkit,
2525
- call,
2526
- declarationIndex,
2527
- trace.toolParameterRejections.get(call.id),
2528
- ),
2513
+ const prepareCalls = Effect.forEach(
2514
+ calls.flatMap((call, declarationIndex) =>
2515
+ settledCallIds?.has(call.id) ? [] : [{ call, declarationIndex }],
2529
2516
  ),
2517
+ ({ call, declarationIndex }) =>
2518
+ prepareToolCall(toolkit, call, declarationIndex, trace.toolParameterRejections.get(call.id)),
2519
+ );
2520
+
2521
+ const needsParameterDecoding = calls.some(
2522
+ (call) =>
2523
+ !settledCallIds?.has(call.id) &&
2524
+ !trace.toolParameterRejections.has(call.id) &&
2525
+ hasTool(toolkit.tools, call.name) &&
2526
+ !reusesValidatedToolParameters(toolkit.tools[call.name]),
2530
2527
  );
2531
2528
 
2529
+ const prepared = yield* needsParameterDecoding
2530
+ ? prepareWithinDeadline(context, prepareCalls)
2531
+ : Effect.flatMap(
2532
+ Clock.currentTimeMillis,
2533
+ (
2534
+ now,
2535
+ ): Effect.Effect<
2536
+ Array<PreparedToolCall<Tools>>,
2537
+ ModelProtocolError | AgentPolicyError,
2538
+ Tool.HandlerServices<ToolUnion<Tools>>
2539
+ > =>
2540
+ context.durationDeadlineMillis - now <= 0
2541
+ ? Effect.fail(context.durationFailure)
2542
+ : prepareCalls,
2543
+ );
2544
+
2532
2545
  if (
2533
2546
  prepared.some((call) => Context.get(call.tool.annotations, ContextRolloverTool)) &&
2534
2547
  calls.length !== 1
@@ -2692,7 +2705,7 @@ const executeToolBatch = Effect.fnUntraced(function* <
2692
2705
  "SubagentInterrupted",
2693
2706
  "SubagentJoined",
2694
2707
  ].includes(payload._tag)
2695
- ? Effect.fail(new RunEventSinkClosedError({ message: "Unsupported Tool event payload" }))
2708
+ ? Effect.fail(RunEventSinkClosedError.make({ message: "Unsupported Tool event payload" }))
2696
2709
  : emitPayload(payload),
2697
2710
  };
2698
2711
 
@@ -2702,28 +2715,28 @@ const executeToolBatch = Effect.fnUntraced(function* <
2702
2715
  emit: (request) =>
2703
2716
  context.updatePermits.withPermit(
2704
2717
  Effect.gen(function* () {
2705
- if (!updatesOpen) return yield* new UpdateError({ reason: "unavailable" });
2718
+ if (!updatesOpen) return yield* UpdateError.make({ reason: "unavailable" });
2706
2719
  if (
2707
2720
  request.target.id !== context.agentId ||
2708
2721
  request.target.updates !== context.definition.updates ||
2709
2722
  context.definition.updates === undefined
2710
2723
  )
2711
- return yield* new UpdateError({ reason: "identity" });
2724
+ return yield* UpdateError.make({ reason: "identity" });
2712
2725
 
2713
2726
  const updateId = yield* Schema.decodeEffect(IdempotencyKey)(request.updateId).pipe(
2714
- Effect.mapError(() => new UpdateError({ reason: "validation" })),
2727
+ Effect.mapError(() => UpdateError.make({ reason: "validation" })),
2715
2728
  );
2716
2729
 
2717
2730
  const value = yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Json))(
2718
2731
  JSON.stringify(request.value),
2719
- ).pipe(Effect.mapError(() => new UpdateError({ reason: "validation" })));
2732
+ ).pipe(Effect.mapError(() => UpdateError.make({ reason: "validation" })));
2720
2733
 
2721
2734
  yield* context.validateUpdate(value);
2722
2735
  const existing = context.updates.get(updateId);
2723
2736
 
2724
2737
  if (existing !== undefined) {
2725
2738
  if (!Schema.toEquivalence(Schema.Json)(existing.value, value))
2726
- return yield* new UpdateError({ reason: "conflict" });
2739
+ return yield* UpdateError.make({ reason: "conflict" });
2727
2740
 
2728
2741
  return existing;
2729
2742
  }
@@ -2739,11 +2752,11 @@ const executeToolBatch = Effect.fnUntraced(function* <
2739
2752
  context.updates.size >= maxCount ||
2740
2753
  context.updateBytes + bytes > maxBytes
2741
2754
  )
2742
- return yield* new UpdateError({ reason: "capacity" });
2755
+ return yield* UpdateError.make({ reason: "capacity" });
2743
2756
 
2744
2757
  const snapshot = boundedCanonicalJsonSnapshot(value, bytes);
2745
2758
 
2746
- if (snapshot === undefined) return yield* new UpdateError({ reason: "validation" });
2759
+ if (snapshot === undefined) return yield* UpdateError.make({ reason: "validation" });
2747
2760
 
2748
2761
  const accepted = yield* updateAcceptance.accept(
2749
2762
  Object.freeze(
@@ -2760,7 +2773,7 @@ const executeToolBatch = Effect.fnUntraced(function* <
2760
2773
  );
2761
2774
 
2762
2775
  const validated = yield* Schema.decodeEffect(Update)(accepted).pipe(
2763
- Effect.mapError(() => new UpdateError({ reason: "validation" })),
2776
+ Effect.mapError(() => UpdateError.make({ reason: "validation" })),
2764
2777
  );
2765
2778
 
2766
2779
  if (
@@ -2770,7 +2783,7 @@ const executeToolBatch = Effect.fnUntraced(function* <
2770
2783
  validated.updateId !== request.updateId ||
2771
2784
  !Schema.toEquivalence(Schema.Json)(validated.value, value)
2772
2785
  )
2773
- return yield* new UpdateError({ reason: "identity" });
2786
+ return yield* UpdateError.make({ reason: "identity" });
2774
2787
 
2775
2788
  // Keep acknowledgements, replay, and retries on the same owned value even when
2776
2789
  // the accepting host retains its own mutable Update instance.
@@ -2780,7 +2793,7 @@ const executeToolBatch = Effect.fnUntraced(function* <
2780
2793
  context.updateBytes += bytes;
2781
2794
 
2782
2795
  yield* emitPayload({ _tag: "AgentUpdateEmitted", update }).pipe(
2783
- Effect.mapError(() => new UpdateError({ reason: "unavailable" })),
2796
+ Effect.mapError(() => UpdateError.make({ reason: "unavailable" })),
2784
2797
  );
2785
2798
 
2786
2799
  return update;
@@ -2788,9 +2801,9 @@ const executeToolBatch = Effect.fnUntraced(function* <
2788
2801
  ),
2789
2802
  };
2790
2803
 
2791
- // One finite permit owner; a call's own fiber fully exits before the supervisor releases it.
2792
- // The extra scoped child is required even for a singleton group: it closes handler descendants
2793
- // before another group can enter and retains cleanup failures in that call's Cause.
2804
+ // The permit covers the child's complete lifetime, including descendant cleanup. The call
2805
+ // owns its resource Scope and its fiber belongs to the enclosing Turn Scope. The native bracket
2806
+ // joins or interrupts it before releasing the permit, without another supervisor Scope.
2794
2807
  const permits = yield* Semaphore.make(concurrency);
2795
2808
 
2796
2809
  const handlers = Effect.gen(function* () {
@@ -2799,9 +2812,9 @@ const executeToolBatch = Effect.fnUntraced(function* <
2799
2812
  group,
2800
2813
  (call) =>
2801
2814
  permits.withPermit(
2802
- Effect.scoped(
2803
- Effect.gen(function* () {
2804
- const callBody = Effect.scoped(
2815
+ Effect.acquireUseRelease(
2816
+ Effect.forkScoped(
2817
+ Effect.scoped(
2805
2818
  Effect.gen(function* () {
2806
2819
  yield* checkpointExecution(context, durability);
2807
2820
  const subagentHost = yield* SubagentHost.forTool;
@@ -2827,6 +2840,11 @@ const executeToolBatch = Effect.fnUntraced(function* <
2827
2840
  hookServices,
2828
2841
  });
2829
2842
 
2843
+ const step = stepServiceFor(call);
2844
+ const subagent = subagentHost(source);
2845
+ const messaging = messagingHost(source);
2846
+ const catalog = { entries: context.toolCatalog };
2847
+
2830
2848
  return yield* executePreparedToolCall(
2831
2849
  context,
2832
2850
  turnId,
@@ -2838,23 +2856,31 @@ const executeToolBatch = Effect.fnUntraced(function* <
2838
2856
  waitingByDeclaration.set(call.declarationIndex, waiting);
2839
2857
  },
2840
2858
  ).pipe(
2841
- Effect.provideService(DurableStep, stepServiceFor(call)),
2842
- Effect.provideService(SubagentHost, subagentHost(source)),
2843
- Effect.provideService(MessagingHost, messagingHost(source)),
2844
- Effect.provideService(ToolBroker, broker.service),
2845
- Effect.provideService(CurrentToolCatalog, { entries: context.toolCatalog }),
2859
+ // Match the original providers' outer-to-inner order with one restoration.
2860
+ Effect.updateContext(
2861
+ (
2862
+ services: Context.Context<
2863
+ ToolSpanTelemetry | Tool.HandlerServices<ToolUnion<Tools>>
2864
+ >,
2865
+ ) =>
2866
+ services.pipe(
2867
+ Context.add(CurrentToolCatalog, catalog),
2868
+ Context.add(ToolBroker, broker.service),
2869
+ Context.add(MessagingHost, messaging),
2870
+ Context.add(SubagentHost, subagent),
2871
+ Context.add(DurableStep, step),
2872
+ ),
2873
+ ),
2846
2874
  Effect.ensuring(Effect.sync(() => broker.close())),
2847
2875
  );
2848
2876
  }),
2849
- );
2850
-
2851
- const fiber = yield* Effect.forkScoped(callBody);
2852
-
2853
- return yield* Fiber.join(fiber);
2854
- }),
2877
+ ),
2878
+ ),
2879
+ Fiber.join,
2880
+ Fiber.interrupt,
2855
2881
  ),
2856
2882
  ),
2857
- { concurrency: "unbounded", discard: true },
2883
+ { concurrency: group.length === 1 ? 1 : "unbounded", discard: true },
2858
2884
  );
2859
2885
  yield* checkpointExecution(context, durability);
2860
2886
  }
@@ -4127,76 +4153,75 @@ const compactContext = <AgentValue extends Agent.Any, HookError, HookRequirement
4127
4153
  const textParts = new Map<string, PartLifecycle>();
4128
4154
  const reasoningParts = new Map<string, PartLifecycle>();
4129
4155
 
4130
- const consumeSummaryPart = (owned: OwnedModelResponsePart) =>
4131
- Effect.gen(function* () {
4132
- responseUsage.responsePartCount++;
4133
- responseUsage.responsePartBytes += owned.retainedBytes;
4134
- const ownedPart = owned.ownedPart;
4135
-
4136
- if (ownedPart.type === "text-delta") {
4137
- pieces.push(ownedPart.delta);
4138
- } else if (ownedPart.type === "finish") {
4139
- if (summaryUsage === undefined) {
4140
- summaryUsage = ownedPart.usage;
4141
- summaryFinishMetadata = ownedPart.metadata;
4142
- }
4143
- } else if (ownedPart.type === "response-metadata") {
4144
- summaryResponse = yield* responseIdentity(ownedPart, summaryResponse);
4156
+ const consumeSummaryPart = Effect.fnUntraced(function* (owned: OwnedModelResponsePart) {
4157
+ responseUsage.responsePartCount++;
4158
+ responseUsage.responsePartBytes += owned.retainedBytes;
4159
+ const ownedPart = owned.ownedPart;
4160
+
4161
+ if (ownedPart.type === "text-delta") {
4162
+ pieces.push(ownedPart.delta);
4163
+ } else if (ownedPart.type === "finish") {
4164
+ if (summaryUsage === undefined) {
4165
+ summaryUsage = ownedPart.usage;
4166
+ summaryFinishMetadata = ownedPart.metadata;
4145
4167
  }
4146
- // Drain malformed responses within the buffer bounds so reported usage is charged.
4147
- yield* Effect.gen(function* () {
4148
- switch (ownedPart.type) {
4149
- case "text-start":
4150
- return yield* startPart(textParts, ownedPart.id, "compaction text");
4151
- case "text-delta":
4152
- return yield* continuePart(textParts, ownedPart.id, "compaction text delta");
4153
- case "text-end":
4154
- return yield* endPart(textParts, ownedPart.id, "compaction text");
4155
- case "reasoning-start":
4156
- return yield* startPart(reasoningParts, ownedPart.id, "compaction reasoning");
4157
- case "reasoning-delta":
4158
- return yield* continuePart(
4159
- reasoningParts,
4160
- ownedPart.id,
4161
- "compaction reasoning delta",
4162
- );
4163
- case "reasoning-end":
4164
- return yield* endPart(reasoningParts, ownedPart.id, "compaction reasoning");
4165
- case "finish": {
4166
- summaryFinished = true;
4167
- if (
4168
- ownedPart.reason !== "stop" ||
4169
- [...textParts.values(), ...reasoningParts.values()].includes("open")
4170
- ) {
4171
- return yield* ModelProtocolError.make({
4172
- message:
4173
- "Compaction response did not finish with complete text and a stop reason",
4174
- });
4175
- }
4176
-
4177
- return;
4178
- }
4179
- case "tool-params-start":
4180
- case "tool-params-delta":
4181
- case "tool-params-end":
4182
- case "tool-approval-request":
4183
- case "error":
4168
+ } else if (ownedPart.type === "response-metadata") {
4169
+ summaryResponse = yield* responseIdentity(ownedPart, summaryResponse);
4170
+ }
4171
+ // Drain malformed responses within the buffer bounds so reported usage is charged.
4172
+ yield* Effect.gen(function* () {
4173
+ switch (ownedPart.type) {
4174
+ case "text-start":
4175
+ return yield* startPart(textParts, ownedPart.id, "compaction text");
4176
+ case "text-delta":
4177
+ return yield* continuePart(textParts, ownedPart.id, "compaction text delta");
4178
+ case "text-end":
4179
+ return yield* endPart(textParts, ownedPart.id, "compaction text");
4180
+ case "reasoning-start":
4181
+ return yield* startPart(reasoningParts, ownedPart.id, "compaction reasoning");
4182
+ case "reasoning-delta":
4183
+ return yield* continuePart(
4184
+ reasoningParts,
4185
+ ownedPart.id,
4186
+ "compaction reasoning delta",
4187
+ );
4188
+ case "reasoning-end":
4189
+ return yield* endPart(reasoningParts, ownedPart.id, "compaction reasoning");
4190
+ case "finish": {
4191
+ summaryFinished = true;
4192
+ if (
4193
+ ownedPart.reason !== "stop" ||
4194
+ [...textParts.values(), ...reasoningParts.values()].includes("open")
4195
+ ) {
4184
4196
  return yield* ModelProtocolError.make({
4185
- message: `Compaction response contained an unusable ${ownedPart.type} part`,
4197
+ message:
4198
+ "Compaction response did not finish with complete text and a stop reason",
4186
4199
  });
4187
- case "file":
4188
- case "response-metadata":
4189
- case "source":
4190
- return;
4200
+ }
4201
+
4202
+ return;
4191
4203
  }
4192
- }).pipe(
4193
- Effect.catch((error) =>
4194
- Effect.sync(() => {
4195
- summaryFailure ??= error;
4196
- }),
4197
- ),
4198
- );
4199
- });
4204
+ case "tool-params-start":
4205
+ case "tool-params-delta":
4206
+ case "tool-params-end":
4207
+ case "tool-approval-request":
4208
+ case "error":
4209
+ return yield* ModelProtocolError.make({
4210
+ message: `Compaction response contained an unusable ${ownedPart.type} part`,
4211
+ });
4212
+ case "file":
4213
+ case "response-metadata":
4214
+ case "source":
4215
+ return;
4216
+ }
4217
+ }).pipe(
4218
+ Effect.catch((error) =>
4219
+ Effect.sync(() => {
4220
+ summaryFailure ??= error;
4221
+ }),
4222
+ ),
4223
+ );
4224
+ });
4200
4225
 
4201
4226
  const summaryExit = yield* enforceDurationDeadline(
4202
4227
  guardBudgetStream(LanguageModel.streamText({ prompt: summarizerPrompt }), options.budget),
@@ -4449,7 +4474,7 @@ const decodeInput = <AgentValue extends Agent.Any>(
4449
4474
  AgentInputDecodeError,
4450
4475
  AgentValue["definition"]["input"]["DecodingServices"]
4451
4476
  > =>
4452
- Schema.decodeUnknownEffect(agent.definition.input)(input).pipe(
4477
+ Schema.decodeUnknownEffect<AgentValue["definition"]["input"]>(agent.definition.input)(input).pipe(
4453
4478
  Effect.mapError((cause) =>
4454
4479
  AgentInputDecodeError.make({
4455
4480
  message: cause.message,
@@ -4475,7 +4500,7 @@ const encodeInput = <AgentValue extends Agent.Any>(
4475
4500
  AgentInputError,
4476
4501
  AgentValue["definition"]["input"]["EncodingServices"]
4477
4502
  > =>
4478
- Schema.encodeEffect(agent.definition.input)(input).pipe(
4503
+ Schema.encodeEffect<AgentValue["definition"]["input"]>(agent.definition.input)(input).pipe(
4479
4504
  Effect.mapError((cause) =>
4480
4505
  AgentInputError.make({
4481
4506
  message: `Unable to encode Agent input: ${cause.message}`,
@@ -4489,20 +4514,21 @@ const renderInputPromptEffect = <Input, Error, Services>(
4489
4514
  encodedInput: unknown,
4490
4515
  ): Effect.Effect<Prompt.RawInput, Error | AgentInputError, Services> =>
4491
4516
  inputPrompt === undefined
4492
- ? Effect.try({
4493
- try: () => {
4494
- const encoded = JSON.stringify(encodedInput);
4517
+ ? Effect.gen(function* () {
4518
+ const encoded = yield* Effect.try({
4519
+ try: () => JSON.stringify(encodedInput),
4520
+ catch: (cause) =>
4521
+ AgentInputError.make({
4522
+ message: `Unable to materialize Agent input: ${errorMessage(cause)}`,
4523
+ }),
4524
+ });
4495
4525
 
4496
- if (encoded === undefined) {
4497
- throw new Error("Agent input cannot be represented as JSON");
4498
- }
4526
+ if (encoded === undefined)
4527
+ return yield* AgentInputError.make({
4528
+ message: "Unable to materialize Agent input: Agent input cannot be represented as JSON",
4529
+ });
4499
4530
 
4500
- return encoded;
4501
- },
4502
- catch: (cause) =>
4503
- AgentInputError.make({
4504
- message: `Unable to materialize Agent input: ${errorMessage(cause)}`,
4505
- }),
4531
+ return encoded;
4506
4532
  })
4507
4533
  : Effect.suspend(() => {
4508
4534
  const result = inputPrompt(decodedInput);
@@ -4524,11 +4550,11 @@ export function renderInputPrompt<
4524
4550
  InputPromptRequirementsOf<InputPromptValue, Input>
4525
4551
  >;
4526
4552
 
4527
- export function renderInputPrompt<Input>(
4528
- inputPrompt: InputPromptSource<Input, unknown, unknown> | undefined,
4553
+ export function renderInputPrompt<Input, Error, Requirements>(
4554
+ inputPrompt: InputPromptSource<Input, Error, Requirements> | undefined,
4529
4555
  decodedInput: Input,
4530
4556
  encodedInput: unknown,
4531
- ): Effect.Effect<Prompt.RawInput, unknown, unknown> {
4557
+ ): Effect.Effect<Prompt.RawInput, Error | AgentInputError, Requirements> {
4532
4558
  return renderInputPromptEffect(inputPrompt, decodedInput, encodedInput);
4533
4559
  }
4534
4560
 
@@ -4908,7 +4934,7 @@ const processModelPart = Effect.fnUntraced(function* <Tools extends Record<strin
4908
4934
  const error = AiError.make({
4909
4935
  module: "Toolkit",
4910
4936
  method: `${part.name}.handle`,
4911
- reason: new AiError.ToolParameterValidationError({
4937
+ reason: AiError.ToolParameterValidationError.make({
4912
4938
  toolName: part.name,
4913
4939
  description: decoded.failure.message,
4914
4940
  }),
@@ -5142,7 +5168,9 @@ const decodeFinalOutput = Effect.fnUntraced(function* <AgentValue extends Agent.
5142
5168
 
5143
5169
  const candidateOutput: unknown = eventJson;
5144
5170
 
5145
- const decoded = yield* Schema.decodeUnknownEffect(agent.definition.output)(candidateOutput).pipe(
5171
+ const decoded = yield* Schema.decodeUnknownEffect<AgentValue["definition"]["output"]>(
5172
+ agent.definition.output,
5173
+ )(candidateOutput).pipe(
5146
5174
  Effect.mapError((cause) =>
5147
5175
  AgentOutputError.make({
5148
5176
  message: cause.message,
@@ -5157,7 +5185,9 @@ const encodeOutputCandidate = Effect.fnUntraced(function* <AgentValue extends Ag
5157
5185
  agent: AgentValue,
5158
5186
  candidate: unknown,
5159
5187
  ) {
5160
- const encoded = yield* Schema.encodeUnknownEffect(agent.definition.output)(candidate).pipe(
5188
+ const encoded = yield* Schema.encodeUnknownEffect<AgentValue["definition"]["output"]>(
5189
+ agent.definition.output,
5190
+ )(candidate).pipe(
5161
5191
  Effect.mapError((cause) =>
5162
5192
  AgentOutputError.make({
5163
5193
  message: `Completion Tool output failed Schema encoding: ${cause.message}`,
@@ -5165,7 +5195,9 @@ const encodeOutputCandidate = Effect.fnUntraced(function* <AgentValue extends Ag
5165
5195
  ),
5166
5196
  );
5167
5197
 
5168
- const decoded = yield* Schema.decodeUnknownEffect(agent.definition.output)(encoded).pipe(
5198
+ const decoded = yield* Schema.decodeUnknownEffect<AgentValue["definition"]["output"]>(
5199
+ agent.definition.output,
5200
+ )(encoded).pipe(
5169
5201
  Effect.mapError((cause) =>
5170
5202
  AgentOutputError.make({
5171
5203
  message: `Completion Tool output failed canonical decoding: ${cause.message}`,
@@ -5194,7 +5226,8 @@ const projectToolResult = Effect.fnUntraced(function* <AgentValue extends Agent.
5194
5226
  AgentOutputError | ModelProtocolError,
5195
5227
  AgentCompletionProjectionRequirements<AgentValue>
5196
5228
  > {
5197
- const tool = agent.definition.toolkit.tools[declaration.tool];
5229
+ const tools: AgentValue["definition"]["toolkit"]["tools"] = agent.definition.toolkit.tools;
5230
+ const tool = tools[declaration.tool];
5198
5231
 
5199
5232
  if (tool === undefined) {
5200
5233
  return yield* ModelProtocolError.make({
@@ -5202,9 +5235,9 @@ const projectToolResult = Effect.fnUntraced(function* <AgentValue extends Agent.
5202
5235
  });
5203
5236
  }
5204
5237
 
5205
- const decodedParameters = yield* Schema.decodeUnknownEffect(tool.parametersSchema)(
5206
- parameters,
5207
- ).pipe(
5238
+ const decodedParameters = yield* Schema.decodeUnknownEffect<
5239
+ AgentValue["definition"]["toolkit"]["tools"][string]["parametersSchema"]
5240
+ >(tool.parametersSchema)(parameters).pipe(
5208
5241
  Effect.mapError((cause) =>
5209
5242
  AgentOutputError.make({
5210
5243
  message: `Completion Tool parameters failed canonical decoding: ${cause.message}`,
@@ -5212,7 +5245,9 @@ const projectToolResult = Effect.fnUntraced(function* <AgentValue extends Agent.
5212
5245
  ),
5213
5246
  );
5214
5247
 
5215
- const decodedResult = yield* Schema.decodeUnknownEffect(tool.successSchema)(result).pipe(
5248
+ const decodedResult = yield* Schema.decodeUnknownEffect<
5249
+ AgentValue["definition"]["toolkit"]["tools"][string]["successSchema"]
5250
+ >(tool.successSchema)(result).pipe(
5216
5251
  Effect.mapError((cause) =>
5217
5252
  AgentOutputError.make({
5218
5253
  message: `Completion Tool result failed canonical decoding: ${cause.message}`,
@@ -5512,10 +5547,10 @@ const makeTurn = <
5512
5547
  : yield* prepareWithinDeadline(context, options.context.prepare(contextRequest));
5513
5548
 
5514
5549
  if (context.resolvedModel && modelContext.modelCall !== undefined) {
5515
- return yield* new AiError.AiError({
5550
+ return yield* AiError.AiError.make({
5516
5551
  module: "AgentRuntime",
5517
5552
  method: "resolveModel",
5518
- reason: new AiError.InvalidRequestError({
5553
+ reason: AiError.InvalidRequestError.make({
5519
5554
  description: "Context preparation cannot replace a thread-resolved model",
5520
5555
  }),
5521
5556
  });
@@ -6337,7 +6372,14 @@ const makeTurn = <
6337
6372
  if (Exit.isFailure(exit)) failedUsageCause = exit.cause;
6338
6373
  });
6339
6374
 
6340
- const attempt = (basis: Prompt.Prompt) =>
6375
+ const attempt = (
6376
+ basis: Prompt.Prompt,
6377
+ ): Effect.Effect<
6378
+ void,
6379
+ AgentRuntimeFailure<typeof agent, HookError, InstructionError>,
6380
+ | InterpreterRequirements<typeof agent, HookRequirements, InstructionRequirements>
6381
+ | Scope.Scope
6382
+ > =>
6341
6383
  Effect.flatten(
6342
6384
  outgoingModelPrompt(
6343
6385
  policy,
@@ -6430,6 +6472,8 @@ const makeTurn = <
6430
6472
 
6431
6473
  return yield* trackContext.pipe(
6432
6474
  Effect.as(
6475
+ // Transport-only tools retain existential schema services; the enclosing attempt keeps the Agent's declared requirements.
6476
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off
6433
6477
  guardBudgetStream(
6434
6478
  LanguageModel.streamText({
6435
6479
  // Every attempt, including an overflow retry, places the
@@ -6511,7 +6555,9 @@ const makeTurn = <
6511
6555
  if (events !== undefined) yield* publishEvents(context, events);
6512
6556
  }),
6513
6557
  ),
6514
- (effect) => prepareWithinDeadline(context, effect),
6558
+ <Value, Error, Requirements>(
6559
+ effect: Effect.Effect<Value, Error, Requirements>,
6560
+ ) => prepareWithinDeadline(context, effect),
6515
6561
  Effect.onExit((exit) => {
6516
6562
  if (Exit.isSuccess(exit)) return Effect.void;
6517
6563
  provisionalText?.discard();
@@ -6534,18 +6580,23 @@ const makeTurn = <
6534
6580
  Schedule.upTo({ times: policy.modelRetries ?? 0 }),
6535
6581
  Schedule.while(({ input: error }) => {
6536
6582
  const lastPart = trace.parts.at(-1);
6583
+ const providerError: unknown = error;
6537
6584
 
6538
6585
  return (
6539
6586
  canRepeatModelCall &&
6540
6587
  !hasStreamedContent(trace) &&
6541
6588
  ((lastPart?.type === "error" && isTransientErrorPayload(lastPart.error)) ||
6542
- (AiError.isAiError(error) && error.isRetryable))
6589
+ (AiError.isAiError(providerError) && providerError.isRetryable))
6543
6590
  );
6544
6591
  }),
6545
6592
  Schedule.modifyDelay(({ input: error, duration }) =>
6546
6593
  Effect.gen(function* () {
6547
6594
  const backoff = Duration.min(duration, MODEL_RETRY_MAX_DELAY);
6548
- const retryAfter = AiError.isAiError(error) ? error.retryAfter : undefined;
6595
+ const providerError: unknown = error;
6596
+
6597
+ const retryAfter = AiError.isAiError(providerError)
6598
+ ? providerError.retryAfter
6599
+ : undefined;
6549
6600
 
6550
6601
  const requested =
6551
6602
  retryAfter === undefined ? backoff : Duration.max(backoff, retryAfter);
@@ -7031,16 +7082,15 @@ const makeTurn = <
7031
7082
  * steering at the safe seam, and start the next Turn. The `maxTurns`
7032
7083
  * guard already ran for these Tool Call sites.
7033
7084
  */
7034
- const continueTurn = (history: Prompt.Prompt) =>
7035
- Effect.gen(function* () {
7036
- trace.historyAccepted = true;
7037
- yield* advanceHistory(context, history, options);
7038
- yield* settleTurn(context, trace, turn, turnId, options);
7039
- const steering = yield* drainInputs(context, options);
7040
- const nextPrompt = yield* appendInputs(context, history, steering, options);
7041
-
7042
- return nextTurn(nextPrompt, turn + 1, toolCalls);
7043
- });
7085
+ const continueTurn = Effect.fnUntraced(function* (history: Prompt.Prompt) {
7086
+ trace.historyAccepted = true;
7087
+ yield* advanceHistory(context, history, options);
7088
+ yield* settleTurn(context, trace, turn, turnId, options);
7089
+ const steering = yield* drainInputs(context, options);
7090
+ const nextPrompt = yield* appendInputs(context, history, steering, options);
7091
+
7092
+ return nextTurn(nextPrompt, turn + 1, toolCalls);
7093
+ });
7044
7094
 
7045
7095
  /**
7046
7096
  * The otherwise-stop seam: advance official history, drain steering,
@@ -7048,86 +7098,81 @@ const makeTurn = <
7048
7098
  * under the `maxTurns` and repeated-failure Stop Policy bounds or
7049
7099
  * complete the Run with the Turn's decoded final output.
7050
7100
  */
7051
- const settleOrFollowUp = (history: Prompt.Prompt) =>
7052
- Effect.gen(function* () {
7053
- trace.historyAccepted = true;
7054
- yield* advanceHistory(context, history, options);
7101
+ const settleOrFollowUp = Effect.fnUntraced(function* (history: Prompt.Prompt) {
7102
+ trace.historyAccepted = true;
7103
+ yield* advanceHistory(context, history, options);
7055
7104
 
7056
- // Do not consume durable receipts that this completed Turn cannot cover.
7057
- // Final-answer mode permits one grace Turn, but never a second finalization.
7058
- const turnsBlocked =
7059
- policy.onExhaustion === "fail" ? turn >= bounds.maxTurns : turn > bounds.maxTurns;
7105
+ // Do not consume durable receipts that this completed Turn cannot cover.
7106
+ // Final-answer mode permits one grace Turn, but never a second finalization.
7107
+ const turnsBlocked =
7108
+ policy.onExhaustion === "fail" ? turn >= bounds.maxTurns : turn > bounds.maxTurns;
7060
7109
 
7061
- const steering =
7062
- turnsBlocked || context.finalizationUsed
7063
- ? []
7064
- : yield* drainInputs(context, options);
7065
-
7066
- const queued =
7067
- steering.length > 0
7068
- ? steering
7069
- : takeFollowUps(context, options.commandDrainPolicy ?? "one");
7070
-
7071
- if (queued.length > 0) {
7072
- // Final-answer mode admits exactly one grace Turn past
7073
- // `maxTurns` (RUN-019): `turn > maxTurns` can only be
7074
- // `maxTurns + 1`, so a second grace is structurally
7075
- // impossible.
7076
- if (turnsBlocked) {
7077
- return failExecution(
7078
- AgentPolicyError.make({
7079
- limit: "turns",
7080
- message: `Agent exceeded its ${bounds.maxTurns} Turn limit`,
7081
- }),
7082
- );
7083
- }
7084
- yield* applyRepeatedFailurePolicy(
7085
- context,
7086
- trace,
7087
- agent.definition.policy.repeatedFailureLimit,
7088
- );
7089
- yield* settleTurn(context, trace, turn, turnId, options);
7090
- const nextPrompt = yield* appendInputs(context, history, queued, options);
7110
+ const steering =
7111
+ turnsBlocked || context.finalizationUsed ? [] : yield* drainInputs(context, options);
7091
7112
 
7092
- return nextTurn(nextPrompt, turn + 1, toolCalls);
7093
- }
7113
+ const queued =
7114
+ steering.length > 0
7115
+ ? steering
7116
+ : takeFollowUps(context, options.commandDrainPolicy ?? "one");
7094
7117
 
7095
- const output = yield* prepareWithinDeadline(
7118
+ if (queued.length > 0) {
7119
+ // Final-answer mode admits exactly one grace Turn past
7120
+ // `maxTurns` (RUN-019): `turn > maxTurns` can only be
7121
+ // `maxTurns + 1`, so a second grace is structurally
7122
+ // impossible.
7123
+ if (turnsBlocked) {
7124
+ return failExecution(
7125
+ AgentPolicyError.make({
7126
+ limit: "turns",
7127
+ message: `Agent exceeded its ${bounds.maxTurns} Turn limit`,
7128
+ }),
7129
+ );
7130
+ }
7131
+ yield* applyRepeatedFailurePolicy(
7096
7132
  context,
7097
- decodeFinalOutput(agent, trace.text.join("")),
7133
+ trace,
7134
+ agent.definition.policy.repeatedFailureLimit,
7098
7135
  );
7136
+ yield* settleTurn(context, trace, turn, turnId, options);
7137
+ const nextPrompt = yield* appendInputs(context, history, queued, options);
7099
7138
 
7100
- const declaration = agent.definition.runDisposition;
7139
+ return nextTurn(nextPrompt, turn + 1, toolCalls);
7140
+ }
7101
7141
 
7102
- const runDisposition =
7103
- finalAnswerOnly || declaration === undefined
7104
- ? undefined
7105
- : yield* prepareWithinDeadline(
7106
- context,
7107
- encodeRunDisposition(agent, output.decoded),
7108
- );
7142
+ const output = yield* prepareWithinDeadline(
7143
+ context,
7144
+ decodeFinalOutput(agent, trace.text.join("")),
7145
+ );
7109
7146
 
7110
- const completionUsage = yield* usageReportOf(context);
7111
-
7112
- return Effect.map(eventBase(context), (base) =>
7113
- RunCompleted.make({
7114
- ...base,
7115
- output: output.encoded,
7116
- ...(runDisposition === undefined ? {} : { runDisposition }),
7117
- turns: turn,
7118
- // A Run settled under the final-answer constraint reports
7119
- // the exhaustion honestly (RUN-011), never a plain model
7120
- // stop, and carries the dimension that bound.
7121
- finishReason: finalAnswerOnly ? "budget-exhausted" : "model-stop",
7122
- ...(finalAnswerOnly && context.exhaustedDimension !== undefined
7123
- ? { exhausted: context.exhaustedDimension }
7124
- : {}),
7125
- ...completionUsage,
7126
- }),
7127
- ).pipe(
7128
- Effect.tap((event) => settleTurn(context, trace, turn, turnId, options, event)),
7129
- );
7130
- });
7147
+ const declaration = agent.definition.runDisposition;
7148
+
7149
+ const runDisposition =
7150
+ finalAnswerOnly || declaration === undefined
7151
+ ? undefined
7152
+ : yield* prepareWithinDeadline(
7153
+ context,
7154
+ encodeRunDisposition(agent, output.decoded),
7155
+ );
7156
+
7157
+ const completionUsage = yield* usageReportOf(context);
7158
+
7159
+ return Effect.map(eventBase(context), (base) =>
7160
+ RunCompleted.make({
7161
+ ...base,
7162
+ output: output.encoded,
7163
+ ...(runDisposition === undefined ? {} : { runDisposition }),
7164
+ turns: turn,
7165
+ // A Run settled under the final-answer constraint reports
7166
+ // the exhaustion honestly (RUN-011), never a plain model
7167
+ // stop, and carries the dimension that bound.
7168
+ finishReason: finalAnswerOnly ? "budget-exhausted" : "model-stop",
7169
+ ...(finalAnswerOnly && context.exhaustedDimension !== undefined
7170
+ ? { exhausted: context.exhaustedDimension }
7171
+ : {}),
7172
+ ...completionUsage,
7173
+ }),
7174
+ ).pipe(Effect.tap((event) => settleTurn(context, trace, turn, turnId, options, event)));
7175
+ });
7131
7176
 
7132
7177
  if (providerOnly && trace.finishReason === "stop") {
7133
7178
  if (agent.definition.completion?.required === true) {
@@ -8601,12 +8646,12 @@ function executeWithCompletion<
8601
8646
 
8602
8647
  const validateUpdate =
8603
8648
  updateSchema === undefined
8604
- ? () => Effect.fail(new UpdateError({ reason: "unavailable" }))
8649
+ ? () => Effect.fail(UpdateError.make({ reason: "unavailable" }))
8605
8650
  : (value: Schema.Json) =>
8606
8651
  Schema.decodeEffect(updateSchema)(value).pipe(
8607
8652
  Effect.provide(updateContext),
8608
8653
  Effect.asVoid,
8609
- Effect.mapError(() => new UpdateError({ reason: "validation" })),
8654
+ Effect.mapError(() => UpdateError.make({ reason: "validation" })),
8610
8655
  );
8611
8656
 
8612
8657
  const languageModel = yield* LanguageModel.LanguageModel;
@@ -8983,15 +9028,14 @@ function executeWithCompletion<
8983
9028
  const selectionPrompt = yield* Schema.encodeEffect(
8984
9029
  Schema.fromJsonString(Prompt.Prompt),
8985
9030
  )(pending.prompt).pipe(
8986
- Effect.mapError(
8987
- (cause) =>
8988
- new AiError.AiError({
8989
- module: "AgentRuntime",
8990
- method: "resolveModel",
8991
- reason: new AiError.InvalidRequestError({
8992
- description: `Cannot encode model selection context: ${cause.message}`,
8993
- }),
9031
+ Effect.mapError((cause) =>
9032
+ AiError.AiError.make({
9033
+ module: "AgentRuntime",
9034
+ method: "resolveModel",
9035
+ reason: AiError.InvalidRequestError.make({
9036
+ description: `Cannot encode model selection context: ${cause.message}`,
8994
9037
  }),
9038
+ }),
8995
9039
  ),
8996
9040
  );
8997
9041
 
@@ -9030,34 +9074,36 @@ function executeWithCompletion<
9030
9074
  agent.definition.updates === undefined || nativeUpdateTool === undefined
9031
9075
  ? Context.empty()
9032
9076
  : yield* Toolkit.make(nativeUpdateTool).toHandlers({
9033
- emit_update: (parameters: unknown, call: Toolkit.HandlerContext<Tool.Any>) =>
9034
- Effect.gen(function* () {
9035
- if (call.toolCallId === undefined)
9036
- return yield* new UpdateError({ reason: "unavailable" });
9077
+ emit_update: Effect.fnUntraced(function* (
9078
+ parameters: unknown,
9079
+ call: Toolkit.HandlerContext<Tool.Any>,
9080
+ ) {
9081
+ if (call.toolCallId === undefined)
9082
+ return yield* UpdateError.make({ reason: "unavailable" });
9037
9083
 
9038
- const decoded = yield* Schema.decodeUnknownEffect(
9039
- Schema.Struct({ value: Schema.Unknown }),
9040
- )(parameters).pipe(
9041
- Effect.mapError(() => new UpdateError({ reason: "validation" })),
9042
- );
9084
+ const decoded = yield* Schema.decodeUnknownEffect(
9085
+ Schema.Struct({ value: Schema.Unknown }),
9086
+ )(parameters).pipe(
9087
+ Effect.mapError(() => UpdateError.make({ reason: "validation" })),
9088
+ );
9043
9089
 
9044
- const value = yield* Schema.encodeUnknownEffect(agent.definition.updates!)(
9045
- decoded.value,
9046
- ).pipe(
9047
- Effect.flatMap(Schema.decodeUnknownEffect(Schema.Json)),
9048
- Effect.mapError(() => new UpdateError({ reason: "validation" })),
9049
- );
9090
+ const value = yield* Schema.encodeUnknownEffect(agent.definition.updates!)(
9091
+ decoded.value,
9092
+ ).pipe(
9093
+ Effect.flatMap(Schema.decodeUnknownEffect(Schema.Json)),
9094
+ Effect.mapError(() => UpdateError.make({ reason: "validation" })),
9095
+ );
9050
9096
 
9051
- const updateId = yield* Schema.decodeEffect(IdempotencyKey)(
9052
- `tool:${context.runId}:${call.toolCallId}`,
9053
- ).pipe(Effect.mapError(() => new UpdateError({ reason: "validation" })));
9097
+ const updateId = yield* Schema.decodeEffect(IdempotencyKey)(
9098
+ `tool:${context.runId}:${call.toolCallId}`,
9099
+ ).pipe(Effect.mapError(() => UpdateError.make({ reason: "validation" })));
9054
9100
 
9055
- const emitter = yield* Emitter;
9101
+ const emitter = yield* Emitter;
9056
9102
 
9057
- yield* emitter.emit({ target: agent.definition, updateId, value });
9103
+ yield* emitter.emit({ target: agent.definition, updateId, value });
9058
9104
 
9059
- return { emitted: true };
9060
- }),
9105
+ return { emitted: true };
9106
+ }),
9061
9107
  });
9062
9108
 
9063
9109
  const engineToolServices = Context.make(
@@ -9114,7 +9160,7 @@ function executeWithCompletion<
9114
9160
  }),
9115
9161
  Context.merge(updateHandlers),
9116
9162
  Context.add(Emitter, {
9117
- emit: () => Effect.fail(new UpdateError({ reason: "unavailable" })),
9163
+ emit: () => Effect.fail(UpdateError.make({ reason: "unavailable" })),
9118
9164
  }),
9119
9165
  Context.add(CurrentToolCatalog, { entries: [] }),
9120
9166
  Context.add(RunToolVisibility, visibility),
@@ -9330,9 +9376,9 @@ const completeRun = <AgentValue extends Agent.Any, Error, Requirements>(
9330
9376
  Effect.gen(function* () {
9331
9377
  const candidateOutput: unknown = completed.output;
9332
9378
 
9333
- const output = yield* Schema.decodeUnknownEffect(agent.definition.output)(
9334
- candidateOutput,
9335
- ).pipe(
9379
+ const output = yield* Schema.decodeUnknownEffect<AgentValue["definition"]["output"]>(
9380
+ agent.definition.output,
9381
+ )(candidateOutput).pipe(
9336
9382
  Effect.mapError((cause) =>
9337
9383
  AgentOutputError.make({
9338
9384
  message: cause.message,
@@ -9577,9 +9623,11 @@ function runUnknown<H = never, R = never>(
9577
9623
  input: unknown,
9578
9624
  options: RunOptions<H, R> = {},
9579
9625
  ) {
9580
- const program = "definition" in agent ? agent : { definition: agent };
9626
+ const program = { definition: "definition" in agent ? agent.definition : agent };
9581
9627
 
9582
9628
  return runProgram(program, (onCompleted) =>
9629
+ // The overload retains each Agent's R; this implementation executes an existential model binding without narrowing its services.
9630
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off
9583
9631
  executeWithCompletion(agent, input, options, onCompleted).pipe(
9584
9632
  Effect.provide(
9585
9633
  Layer.mergeAll(ModelUsageAccounting.layerEphemeral, AgentUpdateAcceptance.layerEphemeral),
@@ -9614,11 +9662,13 @@ function startUnknown<H = never, R = never>(
9614
9662
  input: unknown,
9615
9663
  options: RunOptions<H, R> = {},
9616
9664
  ) {
9617
- const program = "definition" in agent ? agent : { definition: agent };
9665
+ const program = { definition: "definition" in agent ? agent.definition : agent };
9618
9666
 
9619
9667
  return startProgram(
9620
9668
  program,
9621
9669
  (executionOptions, onCompleted, onUsage, publish) =>
9670
+ // The overload retains each Agent's R; detached execution captures the same existential binding inside the caller's Scope.
9671
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off
9622
9672
  executeWithCompletion(agent, input, executionOptions, onCompleted, onUsage, publish).pipe(
9623
9673
  Effect.provide(
9624
9674
  Layer.mergeAll(ModelUsageAccounting.layerEphemeral, AgentUpdateAcceptance.layerEphemeral),
@@ -9888,588 +9938,588 @@ const makeToolBrokerService = Effect.fnUntraced(function* <HookError, HookRequir
9888
9938
  observer === undefined ? undefined : { observer, permits: Semaphore.makeUnsafe(1) };
9889
9939
 
9890
9940
  const service: ToolBrokerService = {
9891
- openPass: (toolkit, passOptions) =>
9892
- Effect.gen(function* () {
9893
- const inheritedAuthority = yield* CurrentSubagentAuthority;
9941
+ openPass: Effect.fnUntraced(function* <Tools extends Record<string, Tool.Any>>(
9942
+ toolkit: Toolkit.WithHandler<Tools>,
9943
+ passOptions: Parameters<ToolBrokerService["openPass"]>[1],
9944
+ ) {
9945
+ const inheritedAuthority = yield* CurrentSubagentAuthority;
9894
9946
 
9895
- if (lifecycle.closed) {
9896
- return yield* ToolBrokerUnavailableError.make({
9897
- message: "The outer Tool Call for this broker has already settled",
9898
- });
9899
- }
9900
- // A malformed result bound would fail open (`NaN` defeats every
9901
- // comparison), so it is rejected typed before the pass opens.
9902
- if (
9903
- passOptions === undefined ||
9904
- !Number.isSafeInteger(passOptions.maxResultBytes) ||
9905
- passOptions.maxResultBytes <= 0
9906
- ) {
9907
- return yield* ToolBrokerConfigurationError.make({
9908
- message: `maxResultBytes must be a positive safe integer; received ${String(passOptions?.maxResultBytes)}`,
9909
- });
9910
- }
9911
- // Capture the handler services present at the pass edge once; nothing
9912
- // inside business execution can substitute them per invocation. The
9913
- // same private-assertion contract as `provideHookServices` applies.
9914
- const handlerServices = (yield* Effect.context<never>()) as Context.Context<unknown>;
9915
- const concurrency = passOptions.concurrency ?? 4;
9916
-
9917
- if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 64) {
9918
- return yield* ToolBrokerConfigurationError.make({
9919
- message: "concurrency must be an integer between 1 and 64",
9920
- });
9921
- }
9922
- const permits = yield* Semaphore.make(concurrency);
9923
- const records: Array<ProgrammaticCallRecord> = [];
9924
-
9925
- const preflightFailure = (
9926
- input: ProgrammaticToolInput,
9927
- kind: "infrastructure" | "protocol",
9928
- tag: string,
9929
- message: string,
9930
- cause?: Cause.Cause<unknown>,
9931
- ): Effect.Effect<ProgrammaticCallOutcome> => {
9932
- const outcome = programmaticOutcomeError(undefined, tag, message);
9933
-
9934
- if (preflightObserver === undefined) return Effect.succeed(outcome);
9935
-
9936
- return deliverToolFailure(preflightObserver.observer, {
9937
- _tag: "ProgrammaticPreflightFailure",
9938
- agentId: binding.context.agentId,
9939
- threadId: binding.context.threadId,
9940
- runId: binding.context.runId,
9941
- turnId: binding.turnId,
9942
- parentToolCallId: binding.outerToolCallId,
9943
- toolName: input.toolName,
9944
- ...(hasTool(toolkit.tools, input.toolName)
9945
- ? { executionClass: getToolExecutionClass(toolkit.tools[input.toolName]) }
9946
- : {}),
9947
- kind,
9948
- tag,
9949
- message: toolFailureMessage(message),
9950
- ...(cause === undefined ? {} : { cause }),
9951
- }).pipe(preflightObserver.permits.withPermits(1), Effect.as(outcome));
9952
- };
9947
+ if (lifecycle.closed) {
9948
+ return yield* ToolBrokerUnavailableError.make({
9949
+ message: "The outer Tool Call for this broker has already settled",
9950
+ });
9951
+ }
9952
+ // A malformed result bound would fail open (`NaN` defeats every
9953
+ // comparison), so it is rejected typed before the pass opens.
9954
+ if (
9955
+ passOptions === undefined ||
9956
+ !Number.isSafeInteger(passOptions.maxResultBytes) ||
9957
+ passOptions.maxResultBytes <= 0
9958
+ ) {
9959
+ return yield* ToolBrokerConfigurationError.make({
9960
+ message: `maxResultBytes must be a positive safe integer; received ${String(passOptions?.maxResultBytes)}`,
9961
+ });
9962
+ }
9963
+ // Capture the declared handler services at the pass boundary once.
9964
+ const handlerServices = yield* Effect.context<Tool.HandlerServices<Tools[keyof Tools]>>();
9965
+
9966
+ // Toolkit.handle decodes unknown parameters itself. Widen only that input boundary;
9967
+ // retain its declared schema failures and handler services for this exact Toolkit.
9968
+ const handle = toolkit.handle as (
9969
+ name: keyof Tools,
9970
+ parameters: unknown,
9971
+ id: string,
9972
+ ) => ReturnType<Toolkit.WithHandler<Tools>["handle"]>;
9973
+
9974
+ const concurrency = passOptions.concurrency ?? 4;
9975
+
9976
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 64) {
9977
+ return yield* ToolBrokerConfigurationError.make({
9978
+ message: "concurrency must be an integer between 1 and 64",
9979
+ });
9980
+ }
9981
+ const permits = yield* Semaphore.make(concurrency);
9982
+ const records: Array<ProgrammaticCallRecord> = [];
9983
+
9984
+ const preflightFailure = (
9985
+ input: ProgrammaticToolInput,
9986
+ kind: "infrastructure" | "protocol",
9987
+ tag: string,
9988
+ message: string,
9989
+ cause?: Cause.Cause<unknown>,
9990
+ ): Effect.Effect<ProgrammaticCallOutcome> => {
9991
+ const outcome = programmaticOutcomeError(undefined, tag, message);
9992
+
9993
+ if (preflightObserver === undefined) return Effect.succeed(outcome);
9994
+
9995
+ return deliverToolFailure(preflightObserver.observer, {
9996
+ _tag: "ProgrammaticPreflightFailure",
9997
+ agentId: binding.context.agentId,
9998
+ threadId: binding.context.threadId,
9999
+ runId: binding.context.runId,
10000
+ turnId: binding.turnId,
10001
+ parentToolCallId: binding.outerToolCallId,
10002
+ toolName: input.toolName,
10003
+ ...(hasTool(toolkit.tools, input.toolName)
10004
+ ? { executionClass: getToolExecutionClass(toolkit.tools[input.toolName]) }
10005
+ : {}),
10006
+ kind,
10007
+ tag,
10008
+ message: toolFailureMessage(message),
10009
+ ...(cause === undefined ? {} : { cause }),
10010
+ }).pipe(preflightObserver.permits.withPermits(1), Effect.as(outcome));
10011
+ };
9953
10012
 
9954
- const body = (
9955
- input: ProgrammaticToolInput,
9956
- sequenceIndex: number,
9957
- ): Effect.Effect<ProgrammaticCallOutcome> =>
9958
- Effect.gen(function* () {
9959
- if (!hasTool(toolkit.tools, input.toolName)) {
9960
- return yield* preflightFailure(
9961
- input,
9962
- "infrastructure",
9963
- "ProgrammaticToolUnknownError",
9964
- `Tool ${input.toolName} is not part of this pass's allowlisted Toolkit`,
9965
- );
9966
- }
9967
- const tool = toolkit.tools[input.toolName] as Tool.Any;
10013
+ const body = (
10014
+ input: ProgrammaticToolInput,
10015
+ sequenceIndex: number,
10016
+ ): Effect.Effect<ProgrammaticCallOutcome> =>
10017
+ Effect.gen(function* () {
10018
+ if (!hasTool(toolkit.tools, input.toolName)) {
10019
+ return yield* preflightFailure(
10020
+ input,
10021
+ "infrastructure",
10022
+ "ProgrammaticToolUnknownError",
10023
+ `Tool ${input.toolName} is not part of this pass's allowlisted Toolkit`,
10024
+ );
10025
+ }
10026
+ const tool = toolkit.tools[input.toolName];
9968
10027
 
9969
- const advertised = Object.values(binding.context.definition.toolkit.tools).some(
9970
- (outer) =>
9971
- Context.get(outer.annotations, AdditionalToolCatalog).some(
9972
- (entry) => entry.tool.name === input.toolName,
9973
- ),
10028
+ const advertised = Object.values(binding.context.definition.toolkit.tools).some((outer) =>
10029
+ Context.get(outer.annotations, AdditionalToolCatalog).some(
10030
+ (entry) => entry.tool.name === input.toolName,
10031
+ ),
10032
+ );
10033
+
10034
+ if (
10035
+ advertised &&
10036
+ !binding.context.toolCatalog.some(
10037
+ (entry) => entry.kind === "code-mode" && entry.tool.name === input.toolName,
10038
+ )
10039
+ )
10040
+ return yield* preflightFailure(
10041
+ input,
10042
+ "infrastructure",
10043
+ "ProgrammaticToolAuthorizationDenied",
10044
+ "Tool is excluded by the current host visibility policy",
9974
10045
  );
9975
10046
 
9976
- if (
9977
- advertised &&
9978
- !binding.context.toolCatalog.some(
9979
- (entry) => entry.kind === "code-mode" && entry.tool.name === input.toolName,
9980
- )
10047
+ if (
10048
+ !isSubagentToolAllowed(
10049
+ inheritedAuthority?.grant,
10050
+ inheritedAuthority?.depth ?? 0,
10051
+ input.toolName,
10052
+ tool.annotations,
9981
10053
  )
9982
- return yield* preflightFailure(
9983
- input,
9984
- "infrastructure",
9985
- "ProgrammaticToolAuthorizationDenied",
9986
- "Tool is excluded by the current host visibility policy",
9987
- );
10054
+ ) {
10055
+ return yield* preflightFailure(
10056
+ input,
10057
+ "infrastructure",
10058
+ "ProgrammaticToolAuthorizationDenied",
10059
+ "Tool exceeds the inherited subagent grant",
10060
+ );
10061
+ }
9988
10062
 
9989
- if (
9990
- !isSubagentToolAllowed(
9991
- inheritedAuthority?.grant,
9992
- inheritedAuthority?.depth ?? 0,
9993
- input.toolName,
9994
- tool.annotations,
9995
- )
9996
- ) {
9997
- return yield* preflightFailure(
9998
- input,
9999
- "infrastructure",
10000
- "ProgrammaticToolAuthorizationDenied",
10001
- "Tool exceeds the inherited subagent grant",
10002
- );
10003
- }
10063
+ if (Context.get(tool.annotations, ContextRolloverTool)) {
10064
+ return yield* preflightFailure(
10065
+ input,
10066
+ "infrastructure",
10067
+ "ProgrammaticContextRolloverUnsupportedError",
10068
+ "Context rollover requires a direct, singleton model Tool Call",
10069
+ );
10070
+ }
10071
+ const approval = tool.needsApproval;
10072
+
10073
+ if (approval !== undefined && approval !== false) {
10074
+ return yield* preflightFailure(
10075
+ input,
10076
+ "infrastructure",
10077
+ "ProgrammaticApprovalUnsupportedError",
10078
+ `Tool ${input.toolName} requires approval; approval-requiring Tools never start programmatically in the ephemeral slice`,
10079
+ );
10080
+ }
10004
10081
 
10005
- if (Context.get(tool.annotations, ContextRolloverTool)) {
10006
- return yield* preflightFailure(
10007
- input,
10008
- "infrastructure",
10009
- "ProgrammaticContextRolloverUnsupportedError",
10010
- "Context rollover requires a direct, singleton model Tool Call",
10011
- );
10012
- }
10013
- const approval = tool.needsApproval;
10082
+ // Validate the encoded arguments against the owning parameter
10083
+ // Schema before the handler can start; the pinned `Toolkit.handle`
10084
+ // decodes internally, so the validated encoded form passes through
10085
+ // (the same inversion as `prepareToolCall`).
10086
+ const decodeParameters = Schema.decodeUnknownEffect(tool.parametersSchema) as (
10087
+ value: unknown,
10088
+ ) => Effect.Effect<unknown, Schema.SchemaError>;
10014
10089
 
10015
- if (approval !== undefined && approval !== false) {
10016
- return yield* preflightFailure(
10090
+ const invalidParameters = yield* decodeParameters(input.encodedArguments).pipe(
10091
+ Effect.map(() => undefined),
10092
+ Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10093
+ preflightFailure(
10017
10094
  input,
10018
- "infrastructure",
10019
- "ProgrammaticApprovalUnsupportedError",
10020
- `Tool ${input.toolName} requires approval; approval-requiring Tools never start programmatically in the ephemeral slice`,
10021
- );
10022
- }
10095
+ "protocol",
10096
+ "ModelProtocolError",
10097
+ `Invalid parameters for Tool ${input.toolName}: ${error.message}`,
10098
+ cause,
10099
+ ),
10100
+ ),
10101
+ );
10023
10102
 
10024
- // Validate the encoded arguments against the owning parameter
10025
- // Schema before the handler can start; the pinned `Toolkit.handle`
10026
- // decodes internally, so the validated encoded form passes through
10027
- // (the same inversion as `prepareToolCall`).
10028
- const decodeParameters = Schema.decodeUnknownEffect(tool.parametersSchema) as (
10029
- value: unknown,
10030
- ) => Effect.Effect<unknown, Schema.SchemaError>;
10031
-
10032
- const invalidParameters = yield* decodeParameters(input.encodedArguments).pipe(
10033
- Effect.map(() => undefined),
10034
- Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10103
+ if (invalidParameters !== undefined) {
10104
+ return invalidParameters;
10105
+ }
10106
+
10107
+ const handleId = ToolCallId.make(`${binding.outerToolCallId}#${sequenceIndex}`);
10108
+
10109
+ const denied = yield* authorization
10110
+ .authorize({
10111
+ threadId: binding.context.threadId,
10112
+ runId: binding.context.runId,
10113
+ turnId: binding.turnId,
10114
+ turn: binding.turn,
10115
+ input: binding.context.input,
10116
+ programmatic: { parentToolCallId: binding.outerToolCallId, sequenceIndex },
10117
+ call: {
10118
+ toolCallId: handleId,
10119
+ toolName: input.toolName,
10120
+ parameters: input.encodedArguments,
10121
+ executionClass: getToolExecutionClass(tool),
10122
+ executionKind: getToolExecutionKind(tool.annotations),
10123
+ },
10124
+ })
10125
+ .pipe(
10126
+ Effect.flatMap((decision) =>
10127
+ decision._tag === "allowed"
10128
+ ? Effect.succeed(undefined)
10129
+ : preflightFailure(
10130
+ input,
10131
+ "infrastructure",
10132
+ "ProgrammaticToolAuthorizationDenied",
10133
+ decision.reason,
10134
+ ),
10135
+ ),
10136
+ Effect.catch((failure) =>
10035
10137
  preflightFailure(
10036
10138
  input,
10037
- "protocol",
10038
- "ModelProtocolError",
10039
- `Invalid parameters for Tool ${input.toolName}: ${error.message}`,
10040
- cause,
10139
+ "infrastructure",
10140
+ errorTag(failure.error),
10141
+ errorMessage(failure.error),
10142
+ failure.cause,
10041
10143
  ),
10042
10144
  ),
10043
10145
  );
10044
10146
 
10045
- if (invalidParameters !== undefined) {
10046
- return invalidParameters;
10047
- }
10147
+ if (denied !== undefined) return denied;
10048
10148
 
10049
- const handleId = ToolCallId.make(`${binding.outerToolCallId}#${sequenceIndex}`);
10050
-
10051
- const denied = yield* authorization
10052
- .authorize({
10053
- threadId: binding.context.threadId,
10054
- runId: binding.context.runId,
10055
- turnId: binding.turnId,
10056
- turn: binding.turn,
10057
- input: binding.context.input,
10058
- programmatic: { parentToolCallId: binding.outerToolCallId, sequenceIndex },
10059
- call: {
10060
- toolCallId: handleId,
10061
- toolName: input.toolName,
10062
- parameters: input.encodedArguments,
10063
- executionClass: getToolExecutionClass(tool),
10064
- executionKind: getToolExecutionKind(tool.annotations),
10065
- },
10066
- })
10067
- .pipe(
10068
- Effect.flatMap((decision) =>
10069
- decision._tag === "allowed"
10070
- ? Effect.succeed(undefined)
10071
- : preflightFailure(
10072
- input,
10073
- "infrastructure",
10074
- "ProgrammaticToolAuthorizationDenied",
10075
- decision.reason,
10076
- ),
10077
- ),
10078
- Effect.catch((failure) =>
10079
- preflightFailure(
10080
- input,
10081
- "infrastructure",
10082
- errorTag(failure.error),
10083
- errorMessage(failure.error),
10084
- failure.cause,
10085
- ),
10086
- ),
10087
- );
10088
-
10089
- if (denied !== undefined) return denied;
10149
+ // Serialize admission and durable reservation across outer handlers.
10150
+ // Once reserved, a slot is never refunded: ownership may be lost
10151
+ // after the append but before the Handler starts.
10152
+ const rejected = yield* binding.context.policyReservations.withPermit(
10153
+ Effect.gen(function* () {
10154
+ const used = binding.declaredToolCalls + binding.context.programmaticToolCalls;
10090
10155
 
10091
- // Serialize admission and durable reservation across outer handlers.
10092
- // Once reserved, a slot is never refunded: ownership may be lost
10093
- // after the append but before the Handler starts.
10094
- const rejected = yield* binding.context.policyReservations.withPermit(
10095
- Effect.gen(function* () {
10096
- const used = binding.declaredToolCalls + binding.context.programmaticToolCalls;
10156
+ if (used + 1 > binding.maxToolCalls) {
10157
+ return yield* preflightFailure(
10158
+ input,
10159
+ "infrastructure",
10160
+ "AgentPolicyError",
10161
+ `Agent exceeded its ${binding.maxToolCalls} Tool Call limit`,
10162
+ );
10163
+ }
10164
+ if (binding.budget !== undefined) {
10165
+ const exhausted = yield* provideHookServices(
10166
+ binding.budget.consume({
10167
+ modelCalls: 0,
10168
+ inputTokens: 0,
10169
+ outputTokens: 0,
10170
+ totalTokens: 0,
10171
+ toolCalls: 1,
10172
+ costMicrousd: 0,
10173
+ usage: emptyProgrammaticUsage,
10174
+ }),
10175
+ binding.hookServices,
10176
+ ).pipe(
10177
+ Effect.map(() => undefined),
10178
+ Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10179
+ Effect.succeed({ error, cause }),
10180
+ ),
10181
+ );
10097
10182
 
10098
- if (used + 1 > binding.maxToolCalls) {
10183
+ if (exhausted !== undefined) {
10099
10184
  return yield* preflightFailure(
10100
10185
  input,
10101
10186
  "infrastructure",
10102
- "AgentPolicyError",
10103
- `Agent exceeded its ${binding.maxToolCalls} Tool Call limit`,
10187
+ errorTag(exhausted.error),
10188
+ errorMessage(exhausted.error),
10189
+ exhausted.cause,
10104
10190
  );
10105
10191
  }
10106
- if (binding.budget !== undefined) {
10107
- const exhausted = yield* provideHookServices(
10108
- binding.budget.consume({
10109
- modelCalls: 0,
10110
- inputTokens: 0,
10111
- outputTokens: 0,
10112
- totalTokens: 0,
10113
- toolCalls: 1,
10114
- costMicrousd: 0,
10115
- usage: emptyProgrammaticUsage,
10116
- }),
10117
- binding.hookServices,
10118
- ).pipe(
10119
- Effect.map(() => undefined),
10120
- Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10121
- Effect.succeed({ error, cause }),
10122
- ),
10123
- );
10124
-
10125
- if (exhausted !== undefined) {
10126
- return yield* preflightFailure(
10192
+ }
10193
+ binding.context.programmaticToolCalls += 1;
10194
+ if (binding.reservePolicyUsage !== undefined) {
10195
+ return yield* provideHookServices(
10196
+ binding.reservePolicyUsage({
10197
+ programmaticToolCalls: binding.context.programmaticToolCalls,
10198
+ finalizationUsed: binding.context.finalizationUsed,
10199
+ }),
10200
+ binding.hookServices,
10201
+ ).pipe(
10202
+ Effect.as(undefined),
10203
+ Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10204
+ preflightFailure(
10127
10205
  input,
10128
10206
  "infrastructure",
10129
- errorTag(exhausted.error),
10130
- errorMessage(exhausted.error),
10131
- exhausted.cause,
10132
- );
10133
- }
10134
- }
10135
- binding.context.programmaticToolCalls += 1;
10136
- if (binding.reservePolicyUsage !== undefined) {
10137
- return yield* provideHookServices(
10138
- binding.reservePolicyUsage({
10139
- programmaticToolCalls: binding.context.programmaticToolCalls,
10140
- finalizationUsed: binding.context.finalizationUsed,
10141
- }),
10142
- binding.hookServices,
10143
- ).pipe(
10144
- Effect.as(undefined),
10145
- Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10146
- preflightFailure(
10147
- input,
10148
- "infrastructure",
10149
- errorTag(error),
10150
- errorMessage(error),
10151
- cause,
10152
- ),
10207
+ errorTag(error),
10208
+ errorMessage(error),
10209
+ cause,
10153
10210
  ),
10154
- );
10155
- }
10156
-
10157
- return undefined;
10158
- }),
10159
- );
10211
+ ),
10212
+ );
10213
+ }
10160
10214
 
10161
- if (rejected !== undefined) return rejected;
10215
+ return undefined;
10216
+ }),
10217
+ );
10162
10218
 
10163
- const index = sequenceIndex;
10219
+ if (rejected !== undefined) return rejected;
10164
10220
 
10165
- records[sequenceIndex] = {
10166
- sequenceIndex,
10167
- toolName: input.toolName,
10168
- status: "uncertain",
10169
- };
10170
- const executionClass = getToolExecutionClass(tool);
10171
- let failureObservation: ProgrammaticToolFailure | undefined;
10172
-
10173
- const startedFailure = (
10174
- kind: "infrastructure" | "protocol",
10175
- tag: string,
10176
- message: string,
10177
- ): Effect.Effect<ProgrammaticCallOutcome> => {
10178
- const outcome = programmaticOutcomeError(index, tag, message);
10179
-
10180
- if (observer === undefined) return Effect.succeed(outcome);
10181
- failureObservation = {
10182
- _tag: "ProgrammaticToolFailure",
10183
- agentId: binding.context.agentId,
10184
- threadId: binding.context.threadId,
10185
- runId: binding.context.runId,
10186
- turnId: binding.turnId,
10187
- toolCallId: handleId,
10188
- parentToolCallId: binding.outerToolCallId,
10189
- sequenceIndex: index,
10190
- toolName: input.toolName,
10191
- executionClass,
10192
- kind,
10193
- tag,
10194
- message: toolFailureMessage(message),
10195
- };
10196
-
10197
- return Effect.succeed(outcome);
10198
- };
10221
+ const index = sequenceIndex;
10199
10222
 
10200
- const telemetryDescriptor: ToolTelemetryDescriptor = {
10201
- context: binding.context,
10223
+ records[sequenceIndex] = {
10224
+ sequenceIndex,
10225
+ toolName: input.toolName,
10226
+ status: "uncertain",
10227
+ };
10228
+ const executionClass = getToolExecutionClass(tool);
10229
+ let failureObservation: ProgrammaticToolFailure | undefined;
10230
+
10231
+ const startedFailure = (
10232
+ kind: "infrastructure" | "protocol",
10233
+ tag: string,
10234
+ message: string,
10235
+ ): Effect.Effect<ProgrammaticCallOutcome> => {
10236
+ const outcome = programmaticOutcomeError(index, tag, message);
10237
+
10238
+ if (observer === undefined) return Effect.succeed(outcome);
10239
+ failureObservation = {
10240
+ _tag: "ProgrammaticToolFailure",
10241
+ agentId: binding.context.agentId,
10242
+ threadId: binding.context.threadId,
10243
+ runId: binding.context.runId,
10202
10244
  turnId: binding.turnId,
10203
10245
  toolCallId: handleId,
10204
- toolName: input.toolName,
10205
- executionClass,
10206
- invocationKind: "programmatic",
10207
- failureMode: tool.failureMode,
10208
10246
  parentToolCallId: binding.outerToolCallId,
10209
10247
  sequenceIndex: index,
10248
+ toolName: input.toolName,
10249
+ executionClass,
10250
+ kind,
10251
+ tag,
10252
+ message: toolFailureMessage(message),
10210
10253
  };
10211
10254
 
10212
- const execution = measureProgrammaticToolCall(
10213
- toolSpanTelemetry,
10214
- telemetryDescriptor,
10215
- Effect.gen(function* () {
10216
- yield* Effect.logDebug("agent programmatic tool handler started").pipe(
10217
- Effect.annotateLogs({
10255
+ return Effect.succeed(outcome);
10256
+ };
10257
+
10258
+ const telemetryDescriptor: ToolTelemetryDescriptor = {
10259
+ context: binding.context,
10260
+ turnId: binding.turnId,
10261
+ toolCallId: handleId,
10262
+ toolName: input.toolName,
10263
+ executionClass,
10264
+ invocationKind: "programmatic",
10265
+ failureMode: tool.failureMode,
10266
+ parentToolCallId: binding.outerToolCallId,
10267
+ sequenceIndex: index,
10268
+ };
10269
+
10270
+ const execution = measureProgrammaticToolCall(
10271
+ toolSpanTelemetry,
10272
+ telemetryDescriptor,
10273
+ Effect.gen(function* () {
10274
+ yield* Effect.logDebug("agent programmatic tool handler started").pipe(
10275
+ Effect.annotateLogs({
10276
+ agentId: binding.context.agentId,
10277
+ runId: binding.context.runId,
10278
+ turnId: binding.turnId,
10279
+ toolCallId: handleId,
10280
+ parentToolCallId: binding.outerToolCallId,
10281
+ toolName: input.toolName,
10282
+ sequenceIndex: index,
10283
+ }),
10284
+ );
10285
+ yield* Metric.update(toolCounter, 1);
10286
+
10287
+ let terminal:
10288
+ | {
10289
+ readonly encodedResult: unknown;
10290
+ readonly isFailure: boolean;
10291
+ readonly tag: string | undefined;
10292
+ }
10293
+ | undefined;
10294
+
10295
+ let resultAfterTerminal = false;
10296
+
10297
+ const handlerFailed = yield* Stream.unwrap(
10298
+ toolSpanTelemetry.isolateToolkitHandle(
10299
+ handle(input.toolName, input.encodedArguments, handleId),
10300
+ ),
10301
+ ).pipe(
10302
+ Stream.runForEach((result) =>
10303
+ Effect.sync(() => {
10304
+ // The direct path rejects a second result after the terminal
10305
+ // one; the broker preserves that protocol violation instead
10306
+ // of silently keeping the last value.
10307
+ if (terminal !== undefined) {
10308
+ resultAfterTerminal = true;
10309
+
10310
+ return;
10311
+ }
10312
+ if (!result.preliminary) {
10313
+ terminal = {
10314
+ encodedResult: toolResultForJson(tool, result),
10315
+ isFailure: result.isFailure,
10316
+ tag:
10317
+ observer === undefined || !result.isFailure
10318
+ ? undefined
10319
+ : errorTag(result.result),
10320
+ };
10321
+ }
10322
+ }),
10323
+ ),
10324
+ Effect.map(() => undefined),
10325
+ Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10326
+ Effect.succeed(new BrokerCallFailure(error, cause)),
10327
+ ),
10328
+ );
10329
+
10330
+ if (handlerFailed instanceof BrokerCallFailure) {
10331
+ const tag = errorTag(handlerFailed.error);
10332
+
10333
+ if (observer !== undefined) {
10334
+ failureObservation = {
10335
+ _tag: "ProgrammaticToolFailure",
10218
10336
  agentId: binding.context.agentId,
10337
+ threadId: binding.context.threadId,
10219
10338
  runId: binding.context.runId,
10220
10339
  turnId: binding.turnId,
10221
10340
  toolCallId: handleId,
10222
10341
  parentToolCallId: binding.outerToolCallId,
10223
- toolName: input.toolName,
10224
10342
  sequenceIndex: index,
10225
- }),
10343
+ toolName: input.toolName,
10344
+ executionClass,
10345
+ kind: "handler-error",
10346
+ tag,
10347
+ cause: handlerFailed.cause,
10348
+ };
10349
+ }
10350
+
10351
+ return programmaticOutcomeError(index, tag, errorMessage(handlerFailed.error));
10352
+ }
10353
+ if (resultAfterTerminal) {
10354
+ return yield* startedFailure(
10355
+ "protocol",
10356
+ "ModelProtocolError",
10357
+ `Tool Call ${handleId} produced more than one terminal result`,
10358
+ );
10359
+ }
10360
+ if (terminal === undefined) {
10361
+ return yield* startedFailure(
10362
+ "protocol",
10363
+ "ModelProtocolError",
10364
+ `Tool Call ${handleId} completed without a terminal result`,
10226
10365
  );
10227
- yield* Metric.update(toolCounter, 1);
10366
+ }
10367
+ if (terminal.isFailure) {
10368
+ if (observer !== undefined) {
10369
+ failureObservation = {
10370
+ _tag: "ProgrammaticToolFailure",
10371
+ agentId: binding.context.agentId,
10372
+ threadId: binding.context.threadId,
10373
+ runId: binding.context.runId,
10374
+ turnId: binding.turnId,
10375
+ toolCallId: handleId,
10376
+ parentToolCallId: binding.outerToolCallId,
10377
+ sequenceIndex: index,
10378
+ toolName: input.toolName,
10379
+ executionClass,
10380
+ kind: "declared-failure",
10381
+ tag: terminal.tag ?? "UnknownError",
10382
+ };
10383
+ }
10228
10384
 
10229
- let terminal:
10230
- | {
10231
- readonly encodedResult: unknown;
10232
- readonly isFailure: boolean;
10233
- readonly tag: string | undefined;
10234
- }
10235
- | undefined;
10236
-
10237
- let resultAfterTerminal = false;
10238
-
10239
- const handlerFailed = yield* Stream.unwrap(
10240
- toolSpanTelemetry.isolateToolkitHandle(
10241
- (
10242
- toolkit.handle as (
10243
- name: string,
10244
- params: unknown,
10245
- id: string,
10246
- ) => Effect.Effect<
10247
- Stream.Stream<Tool.HandlerResult<Tool.Any>, unknown, unknown>,
10248
- unknown,
10249
- unknown
10250
- >
10251
- )(input.toolName, input.encodedArguments, handleId),
10252
- ),
10253
- ).pipe(
10254
- Stream.runForEach((result) =>
10255
- Effect.sync(() => {
10256
- // The direct path rejects a second result after the terminal
10257
- // one; the broker preserves that protocol violation instead
10258
- // of silently keeping the last value.
10259
- if (terminal !== undefined) {
10260
- resultAfterTerminal = true;
10261
-
10262
- return;
10263
- }
10264
- if (!result.preliminary) {
10265
- terminal = {
10266
- encodedResult: toolResultForJson(tool, result),
10267
- isFailure: result.isFailure,
10268
- tag:
10269
- observer === undefined || !result.isFailure
10270
- ? undefined
10271
- : errorTag(result.result),
10272
- };
10273
- }
10274
- }),
10275
- ),
10276
- Effect.map(() => undefined),
10277
- Effect.catchCauseFilter(Cause.findError, (error, cause) =>
10278
- Effect.succeed(new BrokerCallFailure(error, cause)),
10279
- ),
10385
+ return {
10386
+ _tag: "ProgrammaticCallFailure",
10387
+ index,
10388
+ encodedResult: terminal.encodedResult,
10389
+ } as const;
10390
+ }
10391
+ if (Option.isNone(brokerDecodeJson(terminal.encodedResult))) {
10392
+ return yield* startedFailure(
10393
+ "protocol",
10394
+ "ModelProtocolError",
10395
+ `Tool ${input.toolName} produced a success encoding outside JSON`,
10280
10396
  );
10397
+ }
10398
+ let encodedResult = terminal.encodedResult;
10281
10399
 
10282
- if (handlerFailed instanceof BrokerCallFailure) {
10283
- const tag = errorTag(handlerFailed.error);
10284
-
10285
- if (observer !== undefined) {
10286
- failureObservation = {
10287
- _tag: "ProgrammaticToolFailure",
10288
- agentId: binding.context.agentId,
10289
- threadId: binding.context.threadId,
10290
- runId: binding.context.runId,
10291
- turnId: binding.turnId,
10292
- toolCallId: handleId,
10293
- parentToolCallId: binding.outerToolCallId,
10294
- sequenceIndex: index,
10295
- toolName: input.toolName,
10296
- executionClass,
10297
- kind: "handler-error",
10298
- tag,
10299
- cause: handlerFailed.cause,
10300
- };
10301
- }
10400
+ if (passOptions.redactResult !== undefined) {
10401
+ // A redactor is a substitution point: its replacement re-crosses
10402
+ // the JSON boundary or the call fails closed.
10403
+ const redacted = brokerDecodeJson(yield* passOptions.redactResult(encodedResult));
10302
10404
 
10303
- return programmaticOutcomeError(index, tag, errorMessage(handlerFailed.error));
10304
- }
10305
- if (resultAfterTerminal) {
10306
- return yield* startedFailure(
10307
- "protocol",
10308
- "ModelProtocolError",
10309
- `Tool Call ${handleId} produced more than one terminal result`,
10310
- );
10311
- }
10312
- if (terminal === undefined) {
10405
+ if (Option.isNone(redacted)) {
10313
10406
  return yield* startedFailure(
10314
10407
  "protocol",
10315
10408
  "ModelProtocolError",
10316
- `Tool Call ${handleId} completed without a terminal result`,
10409
+ `The redacted result for Tool ${input.toolName} is outside the JSON surface`,
10317
10410
  );
10318
10411
  }
10319
- if (terminal.isFailure) {
10320
- if (observer !== undefined) {
10321
- failureObservation = {
10322
- _tag: "ProgrammaticToolFailure",
10323
- agentId: binding.context.agentId,
10324
- threadId: binding.context.threadId,
10325
- runId: binding.context.runId,
10326
- turnId: binding.turnId,
10327
- toolCallId: handleId,
10328
- parentToolCallId: binding.outerToolCallId,
10329
- sequenceIndex: index,
10330
- toolName: input.toolName,
10331
- executionClass,
10332
- kind: "declared-failure",
10333
- tag: terminal.tag ?? "UnknownError",
10334
- };
10335
- }
10412
+ encodedResult = redacted.value;
10413
+ }
10414
+ const snapshot = brokerSerializeJson(encodedResult);
10336
10415
 
10337
- return {
10338
- _tag: "ProgrammaticCallFailure",
10339
- index,
10340
- encodedResult: terminal.encodedResult,
10341
- } as const;
10342
- }
10343
- if (Option.isNone(brokerDecodeJson(terminal.encodedResult))) {
10344
- return yield* startedFailure(
10345
- "protocol",
10346
- "ModelProtocolError",
10347
- `Tool ${input.toolName} produced a success encoding outside JSON`,
10348
- );
10349
- }
10350
- let encodedResult = terminal.encodedResult;
10351
-
10352
- if (passOptions.redactResult !== undefined) {
10353
- // A redactor is a substitution point: its replacement re-crosses
10354
- // the JSON boundary or the call fails closed.
10355
- const redacted = brokerDecodeJson(yield* passOptions.redactResult(encodedResult));
10356
-
10357
- if (Option.isNone(redacted)) {
10358
- return yield* startedFailure(
10359
- "protocol",
10360
- "ModelProtocolError",
10361
- `The redacted result for Tool ${input.toolName} is outside the JSON surface`,
10362
- );
10363
- }
10364
- encodedResult = redacted.value;
10365
- }
10366
- const snapshot = brokerSerializeJson(encodedResult);
10416
+ if (snapshot === undefined || snapshot.bytes > passOptions.maxResultBytes) {
10417
+ return yield* startedFailure(
10418
+ "infrastructure",
10419
+ "ProgrammaticResultLimitError",
10420
+ `Tool ${input.toolName} result of ${snapshot?.bytes ?? "unencodable"} bytes exceeds the ${passOptions.maxResultBytes}-byte broker bound`,
10421
+ );
10422
+ }
10367
10423
 
10368
- if (snapshot === undefined || snapshot.bytes > passOptions.maxResultBytes) {
10369
- return yield* startedFailure(
10370
- "infrastructure",
10371
- "ProgrammaticResultLimitError",
10372
- `Tool ${input.toolName} result of ${snapshot?.bytes ?? "unencodable"} bytes exceeds the ${passOptions.maxResultBytes}-byte broker bound`,
10373
- );
10374
- }
10424
+ // Retain the exact representation admitted above. Reusing the handler or redactor
10425
+ // value would let later mutation or stateful getters escape the byte bound.
10426
+ const owned = Schema.decodeOption(Schema.fromJsonString(Schema.Json))(snapshot.text);
10375
10427
 
10376
- // Retain the exact representation admitted above. Reusing the handler or redactor
10377
- // value would let later mutation or stateful getters escape the byte bound.
10378
- const owned = Schema.decodeOption(Schema.fromJsonString(Schema.Json))(
10379
- snapshot.text,
10428
+ if (Option.isNone(owned)) {
10429
+ return yield* startedFailure(
10430
+ "protocol",
10431
+ "ModelProtocolError",
10432
+ `Tool ${input.toolName} produced a success encoding outside JSON`,
10380
10433
  );
10434
+ }
10381
10435
 
10382
- if (Option.isNone(owned)) {
10383
- return yield* startedFailure(
10384
- "protocol",
10385
- "ModelProtocolError",
10386
- `Tool ${input.toolName} produced a success encoding outside JSON`,
10387
- );
10388
- }
10436
+ return {
10437
+ _tag: "ProgrammaticCallSuccess",
10438
+ index,
10439
+ encodedResult: owned.value,
10440
+ } as const;
10441
+ }),
10442
+ );
10389
10443
 
10390
- return {
10391
- _tag: "ProgrammaticCallSuccess",
10392
- index,
10393
- encodedResult: owned.value,
10394
- } as const;
10444
+ // Fix the broker outcome and terminal telemetry before delivery. Interruption of an
10445
+ // observer cannot turn a settled inner failure into an interrupted Handler attempt.
10446
+ // This still runs inline under the outer call's permit, before pass.invoke returns.
10447
+ const recorded = execution.pipe(
10448
+ Effect.tap((outcome) =>
10449
+ Effect.sync(() => {
10450
+ records[sequenceIndex] = {
10451
+ sequenceIndex,
10452
+ toolName: input.toolName,
10453
+ status:
10454
+ outcome._tag === "ProgrammaticCallSuccess"
10455
+ ? "succeeded"
10456
+ : outcome._tag === "ProgrammaticCallFailure"
10457
+ ? "failed"
10458
+ : "uncertain",
10459
+ ...(outcome._tag === "ProgrammaticCallError"
10460
+ ? { errorTag: outcome.errorTag }
10461
+ : {}),
10462
+ };
10395
10463
  }),
10396
- );
10464
+ ),
10465
+ );
10397
10466
 
10398
- // Fix the broker outcome and terminal telemetry before delivery. Interruption of an
10399
- // observer cannot turn a settled inner failure into an interrupted Handler attempt.
10400
- // This still runs inline under the outer call's permit, before pass.invoke returns.
10401
- const recorded = execution.pipe(
10402
- Effect.tap((outcome) =>
10403
- Effect.sync(() => {
10404
- records[sequenceIndex] = {
10405
- sequenceIndex,
10406
- toolName: input.toolName,
10407
- status:
10408
- outcome._tag === "ProgrammaticCallSuccess"
10409
- ? "succeeded"
10410
- : outcome._tag === "ProgrammaticCallFailure"
10411
- ? "failed"
10412
- : "uncertain",
10413
- ...(outcome._tag === "ProgrammaticCallError"
10414
- ? { errorTag: outcome.errorTag }
10415
- : {}),
10416
- };
10417
- }),
10418
- ),
10419
- );
10467
+ return yield* observer === undefined
10468
+ ? recorded
10469
+ : recorded.pipe(
10470
+ Effect.tap(() =>
10471
+ failureObservation === undefined
10472
+ ? Effect.void
10473
+ : deliverToolFailure(observer, failureObservation),
10474
+ ),
10475
+ );
10476
+ }).pipe(
10477
+ Effect.updateContext((current: Context.Context<never>) =>
10478
+ Context.merge(current, handlerServices),
10479
+ ),
10480
+ );
10420
10481
 
10421
- return yield* observer === undefined
10422
- ? recorded
10423
- : recorded.pipe(
10424
- Effect.tap(() =>
10425
- failureObservation === undefined
10426
- ? Effect.void
10427
- : deliverToolFailure(observer, failureObservation),
10428
- ),
10429
- );
10430
- }).pipe(Effect.provideContext(handlerServices)) as Effect.Effect<ProgrammaticCallOutcome>;
10431
-
10432
- const pass: ToolBrokerPass = {
10433
- snapshot: Effect.sync(() => records.map((record) => ({ ...record }))),
10434
- invoke: (input) =>
10435
- Effect.suspend(() => {
10436
- const sequenceIndex = records.length;
10437
-
10438
- records.push({ sequenceIndex, toolName: input.toolName, status: "not-started" });
10439
-
10440
- // Check after acquiring the permit too: queued work cannot start after its owner exits.
10441
- return permits
10442
- .withPermit(
10443
- Effect.suspend(() =>
10444
- lifecycle.closed
10445
- ? preflightFailure(
10446
- input,
10447
- "infrastructure",
10448
- "ToolBrokerUnavailableError",
10449
- "The outer Tool Call for this pass has already settled",
10450
- )
10451
- : body(input, sequenceIndex),
10452
- ),
10453
- )
10454
- .pipe(
10455
- Effect.tap((outcome) =>
10456
- Effect.sync(() => {
10457
- if (outcome._tag === "ProgrammaticCallError" && outcome.index === undefined) {
10458
- records[sequenceIndex] = {
10459
- sequenceIndex,
10460
- toolName: input.toolName,
10461
- status: "not-started",
10462
- errorTag: outcome.errorTag,
10463
- };
10464
- }
10465
- }),
10466
- ),
10467
- );
10468
- }),
10469
- };
10482
+ const pass: ToolBrokerPass = {
10483
+ snapshot: Effect.sync(() => records.map((record) => ({ ...record }))),
10484
+ invoke: (input) =>
10485
+ Effect.suspend(() => {
10486
+ const sequenceIndex = records.length;
10470
10487
 
10471
- return pass;
10472
- }),
10488
+ records.push({ sequenceIndex, toolName: input.toolName, status: "not-started" });
10489
+
10490
+ // Check after acquiring the permit too: queued work cannot start after its owner exits.
10491
+ return permits
10492
+ .withPermit(
10493
+ Effect.suspend(() =>
10494
+ lifecycle.closed
10495
+ ? preflightFailure(
10496
+ input,
10497
+ "infrastructure",
10498
+ "ToolBrokerUnavailableError",
10499
+ "The outer Tool Call for this pass has already settled",
10500
+ )
10501
+ : body(input, sequenceIndex),
10502
+ ),
10503
+ )
10504
+ .pipe(
10505
+ Effect.tap((outcome) =>
10506
+ Effect.sync(() => {
10507
+ if (outcome._tag === "ProgrammaticCallError" && outcome.index === undefined) {
10508
+ records[sequenceIndex] = {
10509
+ sequenceIndex,
10510
+ toolName: input.toolName,
10511
+ status: "not-started",
10512
+ errorTag: outcome.errorTag,
10513
+ };
10514
+ }
10515
+ }),
10516
+ ),
10517
+ );
10518
+ }),
10519
+ };
10520
+
10521
+ return pass;
10522
+ }),
10473
10523
  };
10474
10524
 
10475
10525
  return {
@@ -10553,98 +10603,97 @@ const makeDurableStepService = <HookError, HookRequirements>(
10553
10603
  const usedNames = new Set<string>();
10554
10604
 
10555
10605
  return {
10556
- do: <Output extends Schema.Top, BodyError, BodyServices>(
10606
+ do: Effect.fnUntraced(function* <Output extends Schema.Top, BodyError, BodyServices>(
10557
10607
  name: string,
10558
10608
  output: Output,
10559
10609
  execute: Effect.Effect<Output["Type"], BodyError, BodyServices>,
10560
- ) =>
10561
- Effect.gen(function* () {
10562
- if (usedNames.has(name)) {
10563
- return yield* DurableStepError.make({
10610
+ ) {
10611
+ if (usedNames.has(name)) {
10612
+ return yield* DurableStepError.make({
10613
+ toolCallId,
10614
+ stepName: name,
10615
+ reason: "duplicate-step-name",
10616
+ message: "Durable Step name was reused within one Tool Call",
10617
+ });
10618
+ }
10619
+ usedNames.add(name);
10620
+ const key: RunStepKey = { toolCallId, stepName: name };
10621
+
10622
+ const recorded = yield* provideHookServices(hook.lookup(key), hookServices).pipe(
10623
+ Effect.mapError((cause) =>
10624
+ DurableStepError.make({
10564
10625
  toolCallId,
10565
10626
  stepName: name,
10566
- reason: "duplicate-step-name",
10567
- message: "Durable Step name was reused within one Tool Call",
10568
- });
10569
- }
10570
- usedNames.add(name);
10571
- const key: RunStepKey = { toolCallId, stepName: name };
10627
+ reason: "lookup-failed",
10628
+ message: "Durable Step lookup failed",
10629
+ cause,
10630
+ }),
10631
+ ),
10632
+ );
10572
10633
 
10573
- const recorded = yield* provideHookServices(hook.lookup(key), hookServices).pipe(
10574
- Effect.mapError((cause) =>
10634
+ if (Option.isSome(recorded)) {
10635
+ return yield* Schema.decodeUnknownEffect(output)(recorded.value.encodedOutput).pipe(
10636
+ Effect.mapError(() =>
10575
10637
  DurableStepError.make({
10576
10638
  toolCallId,
10577
10639
  stepName: name,
10578
- reason: "lookup-failed",
10579
- message: "Durable Step lookup failed",
10580
- cause,
10640
+ reason: "recorded-result-invalid",
10641
+ message: "Recorded Durable Step result failed the declared output Schema",
10581
10642
  }),
10582
10643
  ),
10583
10644
  );
10645
+ }
10584
10646
 
10585
- if (Option.isSome(recorded)) {
10586
- return yield* Schema.decodeUnknownEffect(output)(recorded.value.encodedOutput).pipe(
10587
- Effect.mapError(() =>
10588
- DurableStepError.make({
10589
- toolCallId,
10590
- stepName: name,
10591
- reason: "recorded-result-invalid",
10592
- message: "Recorded Durable Step result failed the declared output Schema",
10593
- }),
10594
- ),
10595
- );
10596
- }
10597
-
10598
- return yield* Effect.acquireUseRelease(
10599
- provideHookServices(hook.reserve(key), hookServices).pipe(
10600
- Effect.mapError((cause) =>
10601
- DurableStepError.make({
10602
- toolCallId,
10603
- stepName: name,
10604
- reason:
10605
- cause instanceof AgentPersistenceCapacityError
10606
- ? "capacity-exhausted"
10607
- : "reservation-failed",
10608
- message:
10609
- cause instanceof AgentPersistenceCapacityError
10610
- ? "Durable Step has no room for a valid saved result"
10611
- : "Durable Step capacity reservation failed",
10612
- cause,
10613
- }),
10614
- ),
10647
+ return yield* Effect.acquireUseRelease(
10648
+ provideHookServices(hook.reserve(key), hookServices).pipe(
10649
+ Effect.mapError((cause) =>
10650
+ DurableStepError.make({
10651
+ toolCallId,
10652
+ stepName: name,
10653
+ reason:
10654
+ cause instanceof AgentPersistenceCapacityError
10655
+ ? "capacity-exhausted"
10656
+ : "reservation-failed",
10657
+ message:
10658
+ cause instanceof AgentPersistenceCapacityError
10659
+ ? "Durable Step has no room for a valid saved result"
10660
+ : "Durable Step capacity reservation failed",
10661
+ cause,
10662
+ }),
10615
10663
  ),
10616
- () =>
10617
- Effect.gen(function* () {
10618
- const value = yield* execute;
10619
-
10620
- const encodedOutput = yield* Schema.encodeEffect(output)(value).pipe(
10621
- Effect.mapError(() =>
10622
- DurableStepError.make({
10623
- toolCallId,
10624
- stepName: name,
10625
- reason: "output-encoding-failed",
10626
- message: "Durable Step output failed the declared output Schema",
10627
- }),
10628
- ),
10629
- );
10664
+ ),
10665
+ () =>
10666
+ Effect.gen(function* () {
10667
+ const value = yield* execute;
10668
+
10669
+ const encodedOutput = yield* Schema.encodeEffect(output)(value).pipe(
10670
+ Effect.mapError(() =>
10671
+ DurableStepError.make({
10672
+ toolCallId,
10673
+ stepName: name,
10674
+ reason: "output-encoding-failed",
10675
+ message: "Durable Step output failed the declared output Schema",
10676
+ }),
10677
+ ),
10678
+ );
10630
10679
 
10631
- yield* provideHookServices(hook.commit(key, encodedOutput), hookServices).pipe(
10632
- Effect.mapError((cause) =>
10633
- DurableStepError.make({
10634
- toolCallId,
10635
- stepName: name,
10636
- reason: "commit-failed",
10637
- message: "Durable Step commit failed",
10638
- cause,
10639
- }),
10640
- ),
10641
- );
10680
+ yield* provideHookServices(hook.commit(key, encodedOutput), hookServices).pipe(
10681
+ Effect.mapError((cause) =>
10682
+ DurableStepError.make({
10683
+ toolCallId,
10684
+ stepName: name,
10685
+ reason: "commit-failed",
10686
+ message: "Durable Step commit failed",
10687
+ cause,
10688
+ }),
10689
+ ),
10690
+ );
10642
10691
 
10643
- return value;
10644
- }),
10645
- (release) => release,
10646
- );
10647
- }),
10692
+ return value;
10693
+ }),
10694
+ (release) => release,
10695
+ );
10696
+ }),
10648
10697
  };
10649
10698
  };
10650
10699