@yielded/agent 0.1.0-beta.181 → 0.1.0-beta.183

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 (177) hide show
  1. package/dist/{Admin-NvXFPDIM.d.mts → Admin-BgCHCGSl.d.mts} +8 -8
  2. package/dist/{AgentRuntime-Dx5u_FJD.d.mts → AgentRuntime-DhRchFzv.d.mts} +7 -4
  3. package/dist/{AgentRuntime-BAExRlgu.mjs → AgentRuntime-ILtyHuQG.mjs} +61 -45
  4. package/dist/AgentRuntime-ILtyHuQG.mjs.map +1 -0
  5. package/dist/{Digest-DjUgS5PG.d.mts → Digest-1iVcDLVm.d.mts} +2 -2
  6. package/dist/{Digest-ft4ku9pj.mjs → Digest-CzLJcsWv.mjs} +2 -2
  7. package/dist/{Digest-ft4ku9pj.mjs.map → Digest-CzLJcsWv.mjs.map} +1 -1
  8. package/dist/{DurableAgentRuntime-HH2muFrT.mjs → DurableAgentRuntime-Cj263rW9.mjs} +27 -57
  9. package/dist/DurableAgentRuntime-Cj263rW9.mjs.map +1 -0
  10. package/dist/{EventSource-DNyaWmyQ.d.mts → EventSource-CaA6RAtC.d.mts} +3 -3
  11. package/dist/InMemory.d.mts +1 -1
  12. package/dist/{MessageDelivery-CLzWJImC.d.mts → MessageDelivery-rlLpakO8.d.mts} +43 -43
  13. package/dist/{PreparedInputAdmission-BQ9UCLFS.d.mts → PreparedInputAdmission-DjbWz2lW.d.mts} +4 -4
  14. package/dist/{RecordFormat-BNiLpe43.d.mts → RecordFormat-CjMl0uu8.d.mts} +2 -2
  15. package/dist/{Records-BTnGRdcf.d.mts → Records-BP67Yz6n.d.mts} +20 -8
  16. package/dist/{Records-CZRedCE7.mjs → Records-bbGVHprv.mjs} +14 -10
  17. package/dist/Records-bbGVHprv.mjs.map +1 -0
  18. package/dist/{RunContinuation-CL0wBsQE.d.mts → RunContinuation-CIJRN_vC.d.mts} +5 -4
  19. package/dist/{RunContinuation-BF95Ncv2.mjs → RunContinuation-DJ6kkbQG.mjs} +22 -58
  20. package/dist/RunContinuation-DJ6kkbQG.mjs.map +1 -0
  21. package/dist/{RunEvent-C5jVQZ6K.d.mts → RunEvent-Bh1p4zAO.d.mts} +2 -2
  22. package/dist/{RunJournal-oJLo-pjS.mjs → RunJournal-DQr2dUgg.mjs} +4 -7
  23. package/dist/RunJournal-DQr2dUgg.mjs.map +1 -0
  24. package/dist/{RunOptions-BANtohGk.d.mts → RunOptions-aBHR-Eqm.d.mts} +6 -3
  25. package/dist/{RunStorage-DlfvMfHM.mjs → RunStorage-DblN-cmM.mjs} +2 -2
  26. package/dist/{RunStorage-DlfvMfHM.mjs.map → RunStorage-DblN-cmM.mjs.map} +1 -1
  27. package/dist/{Schedule-CDgTz0vj.d.mts → Schedule-CVLtl5rP.d.mts} +2 -2
  28. package/dist/{Scheduling-BebkRVc2.mjs → Scheduling-B6l0lWdr.mjs} +3 -3
  29. package/dist/{Scheduling-BebkRVc2.mjs.map → Scheduling-B6l0lWdr.mjs.map} +1 -1
  30. package/dist/{Subagent-D68fe7mr.mjs → Subagent-Bm842K7J.mjs} +43 -17
  31. package/dist/Subagent-Bm842K7J.mjs.map +1 -0
  32. package/dist/{Subagent-5ulnV6An.d.mts → Subagent-CWWmA1UQ.d.mts} +17 -5
  33. package/dist/{Subscription-D2yn6Eo3.d.mts → Subscription-BRsNy0TA.d.mts} +4 -4
  34. package/dist/{SubscriptionInput-Cog4x7Rd.d.mts → SubscriptionInput-ClCaW_kX.d.mts} +3 -3
  35. package/dist/{ThreadHistory-KPPBJLJb.d.mts → ThreadHistory-ajQhqZDY.d.mts} +2 -2
  36. package/dist/{ThreadImport-BoTfjjrz.mjs → ThreadImport-CkPAxXU5.mjs} +7 -7
  37. package/dist/{ThreadImport-BoTfjjrz.mjs.map → ThreadImport-CkPAxXU5.mjs.map} +1 -1
  38. package/dist/{ThreadImport-em89zmM-.d.mts → ThreadImport-DtRNoHIr.d.mts} +5 -5
  39. package/dist/{ThreadStore-D2rqtt7k.mjs → ThreadStore-C_ofHc7h.mjs} +4 -4
  40. package/dist/{ThreadStore-D2rqtt7k.mjs.map → ThreadStore-C_ofHc7h.mjs.map} +1 -1
  41. package/dist/{ThreadTransfer-BFolkpEp.mjs → ThreadTransfer-C_TRuo_9.mjs} +4 -4
  42. package/dist/{ThreadTransfer-BFolkpEp.mjs.map → ThreadTransfer-C_TRuo_9.mjs.map} +1 -1
  43. package/dist/{WorkerAdmission-DrxDrLII.mjs → WorkerAdmission-9Ka3edXj.mjs} +5 -5
  44. package/dist/{WorkerAdmission-DrxDrLII.mjs.map → WorkerAdmission-9Ka3edXj.mjs.map} +1 -1
  45. package/dist/{agent-registration-BovXT8jc.mjs → agent-registration-7kp1dHT0.mjs} +4 -4
  46. package/dist/{agent-registration-BovXT8jc.mjs.map → agent-registration-7kp1dHT0.mjs.map} +1 -1
  47. package/dist/capabilities/Commands.d.mts +1 -1
  48. package/dist/capabilities/Redaction.d.mts +1 -1
  49. package/dist/capabilities/RunHooks.d.mts +1 -1
  50. package/dist/capabilities/Subagent.d.mts +1 -1
  51. package/dist/capabilities/Subagent.mjs +1 -1
  52. package/dist/capabilities/SubagentReservations.d.mts +4 -1
  53. package/dist/capabilities/SubagentReservations.mjs +4 -1
  54. package/dist/capabilities/SubagentReservations.mjs.map +1 -1
  55. package/dist/core/RunEvent.d.mts +1 -1
  56. package/dist/core/RunEvent.mjs +1 -1
  57. package/dist/core/RunEvent.mjs.map +1 -1
  58. package/dist/durable/ActivityStore.d.mts +1 -1
  59. package/dist/durable/ActivityStore.mjs +1 -1
  60. package/dist/durable/Admin.d.mts +1 -1
  61. package/dist/durable/Admin.mjs +1 -1
  62. package/dist/durable/AgentRegistration.d.mts +1 -1
  63. package/dist/durable/AgentRegistration.mjs +1 -1
  64. package/dist/durable/Certification.d.mts +1 -1
  65. package/dist/durable/CommittedActivity.d.mts +3 -3
  66. package/dist/durable/CommittedActivity.mjs +3 -3
  67. package/dist/durable/Digest.d.mts +1 -1
  68. package/dist/durable/Digest.mjs +1 -1
  69. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  70. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  71. package/dist/durable/EventSource.d.mts +1 -1
  72. package/dist/durable/EventSource.mjs +1 -1
  73. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  74. package/dist/durable/LifecyclePublication.d.mts +1 -1
  75. package/dist/durable/LifecyclePublication.mjs +2 -2
  76. package/dist/durable/MessageDelivery.d.mts +1 -1
  77. package/dist/durable/MessageDelivery.mjs +2 -2
  78. package/dist/durable/MessageDeliveryStoreConformance.d.mts +42 -42
  79. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  80. package/dist/durable/PersistentHistory.d.mts +2 -2
  81. package/dist/durable/PersistentHistory.mjs +4 -4
  82. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  83. package/dist/durable/RecordFormat.d.mts +1 -1
  84. package/dist/durable/RecordFormat.mjs +1 -1
  85. package/dist/durable/Records.d.mts +1 -1
  86. package/dist/durable/Records.mjs +1 -1
  87. package/dist/durable/Recovery.d.mts +1 -1
  88. package/dist/durable/Recovery.mjs +1 -1
  89. package/dist/durable/RunContinuation.d.mts +3 -3
  90. package/dist/durable/RunContinuation.mjs +3 -3
  91. package/dist/durable/RunJournal.d.mts +1 -1
  92. package/dist/durable/RunJournal.mjs +1 -1
  93. package/dist/durable/RunStorage.d.mts +1 -1
  94. package/dist/durable/RunStorage.mjs +1 -1
  95. package/dist/durable/Schedule.d.mts +1 -1
  96. package/dist/durable/Schedule.mjs +1 -1
  97. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  98. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  99. package/dist/durable/ScheduleTransition.d.mts +1 -1
  100. package/dist/durable/ScheduleTransition.mjs +1 -1
  101. package/dist/durable/Scheduling.d.mts +3 -3
  102. package/dist/durable/Scheduling.mjs +1 -1
  103. package/dist/durable/SettlementPublisher.d.mts +1 -1
  104. package/dist/durable/SettlementPublisher.mjs +3 -3
  105. package/dist/durable/SubmissionLedger.d.mts +1 -1
  106. package/dist/durable/SubmissionLedger.mjs +2 -2
  107. package/dist/durable/SubmissionLedgerConformance.d.mts +2 -2
  108. package/dist/durable/SubmissionLedgerConformance.mjs +5 -5
  109. package/dist/durable/SubmissionStatus.d.mts +1 -1
  110. package/dist/durable/Subscription.d.mts +1 -1
  111. package/dist/durable/Subscription.mjs +1 -1
  112. package/dist/durable/SubscriptionInput.d.mts +1 -1
  113. package/dist/durable/SubscriptionInput.mjs +2 -2
  114. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  115. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  116. package/dist/durable/SubscriptionTools.d.mts +4 -4
  117. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  118. package/dist/durable/SubscriptionTransition.mjs +1 -1
  119. package/dist/durable/Subscriptions.d.mts +6 -6
  120. package/dist/durable/Subscriptions.mjs +2 -2
  121. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  122. package/dist/durable/ThreadArchiveRange.mjs +1 -1
  123. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  124. package/dist/durable/ThreadContextHistory.mjs +2 -2
  125. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  126. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  127. package/dist/durable/ThreadImport.d.mts +1 -1
  128. package/dist/durable/ThreadImport.mjs +1 -1
  129. package/dist/durable/ThreadInvariants.d.mts +3 -3
  130. package/dist/durable/ThreadInvariants.mjs +4 -4
  131. package/dist/durable/ThreadProjection.d.mts +1 -1
  132. package/dist/durable/ThreadProjection.mjs +1 -1
  133. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  134. package/dist/durable/ThreadReader.d.mts +1 -1
  135. package/dist/durable/ThreadReader.mjs +1 -1
  136. package/dist/durable/ThreadStore.d.mts +2 -2
  137. package/dist/durable/ThreadStore.mjs +2 -2
  138. package/dist/durable/ThreadStoreConformance.d.mts +2 -2
  139. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  140. package/dist/durable/ThreadTransfer.d.mts +2 -2
  141. package/dist/durable/ThreadTransfer.mjs +1 -1
  142. package/dist/durable/ThreadWork.d.mts +1 -1
  143. package/dist/durable/ThreadWork.mjs +5 -5
  144. package/dist/durable/ToolReconciler.mjs +1 -1
  145. package/dist/durable/WorkerAdmission.d.mts +2 -2
  146. package/dist/durable/WorkerAdmission.mjs +1 -1
  147. package/dist/durable/WorkerHost.d.mts +2 -2
  148. package/dist/engine/AgentRuntime.d.mts +1 -1
  149. package/dist/engine/AgentRuntime.mjs +1 -1
  150. package/dist/engine/RunOptions.d.mts +1 -1
  151. package/dist/engine/RunOptions.mjs.map +1 -1
  152. package/dist/engine/ThreadHistory.d.mts +1 -1
  153. package/dist/index.d.mts +17 -17
  154. package/dist/index.mjs +12 -12
  155. package/dist/{run-context-reader-kpO9xhP2.d.mts → run-context-reader-DEU4REfD.d.mts} +3 -3
  156. package/package.json +1 -1
  157. package/src/capabilities/Subagent.ts +83 -28
  158. package/src/capabilities/SubagentReservations.ts +4 -1
  159. package/src/capabilities/internal/subagent-contract.ts +23 -3
  160. package/src/core/RunEvent.ts +1 -1
  161. package/src/durable/DurableAgentRuntime.ts +64 -14
  162. package/src/durable/Records.ts +21 -15
  163. package/src/durable/RunContinuation.ts +64 -125
  164. package/src/durable/RunJournal.ts +4 -12
  165. package/src/durable/internal/history-prompt.ts +10 -4
  166. package/src/durable/internal/initial-context.ts +1 -1
  167. package/src/durable/internal/prompt-history-cache.ts +8 -63
  168. package/src/engine/RunOptions.ts +4 -1
  169. package/src/engine/internal/agent-runtime.ts +422 -387
  170. package/dist/AgentRuntime-BAExRlgu.mjs.map +0 -1
  171. package/dist/DurableAgentRuntime-HH2muFrT.mjs.map +0 -1
  172. package/dist/Records-CZRedCE7.mjs.map +0 -1
  173. package/dist/RunContinuation-BF95Ncv2.mjs.map +0 -1
  174. package/dist/RunJournal-oJLo-pjS.mjs.map +0 -1
  175. package/dist/Subagent-D68fe7mr.mjs.map +0 -1
  176. package/dist/json-BlJVQZxN.mjs +0 -15
  177. package/dist/json-BlJVQZxN.mjs.map +0 -1
@@ -757,7 +757,7 @@ const acceptTurnCommit = <E, R>(
757
757
  trace: TurnTrace,
758
758
  durability: RunDurabilityHook<E, R> | undefined,
759
759
  commit: RunTurnCommit,
760
- ): Effect.Effect<void, E | ModelProtocolError | AgentPolicyError, R> =>
760
+ ): Effect.Effect<void | "capacity-refused", E | ModelProtocolError | AgentPolicyError, R> =>
761
761
  Effect.suspend(() => {
762
762
  if (trace.commitFailed) return Effect.void;
763
763
 
@@ -767,6 +767,15 @@ const acceptTurnCommit = <E, R>(
767
767
 
768
768
  return Effect.onExitPrimitive(
769
769
  Effect.flatMap(accepted, (receipt) => {
770
+ if (receipt === "capacity-refused")
771
+ return commit._tag === "Response" && trace.responseCommitted !== true
772
+ ? Effect.succeed(receipt)
773
+ : Effect.fail(
774
+ ModelProtocolError.make({
775
+ message: "Durability refused capacity after response acceptance",
776
+ }),
777
+ );
778
+
770
779
  if (receipt === "deferred")
771
780
  return commit._tag === "Response" && commit.defer === true
772
781
  ? Effect.void
@@ -837,7 +846,7 @@ const settleTurn = <E, R>(
837
846
  ...(response === undefined ? {} : { response }),
838
847
  results,
839
848
  ...(completion === undefined ? {} : { completion }),
840
- }),
849
+ }).pipe(Effect.asVoid),
841
850
  );
842
851
  });
843
852
 
@@ -1020,22 +1029,20 @@ const inspectModelResponsePartCapacity = (
1020
1029
  part: unknown,
1021
1030
  limits: EffectiveRunBufferLimits,
1022
1031
  knownSafePrototypes: ReadonlySet<object> = knownSafeModelResponsePrototypes,
1023
- ): Effect.Effect<number, ModelProtocolError> =>
1024
- Effect.suspend(() => {
1025
- const bytes = boundedValueFootprint(
1026
- part,
1027
- limits.maxModelResponseBytes - usage.responsePartBytes,
1028
- knownSafePrototypes,
1029
- );
1032
+ ): number | ModelProtocolError => {
1033
+ const bytes = boundedValueFootprint(
1034
+ part,
1035
+ limits.maxModelResponseBytes - usage.responsePartBytes,
1036
+ knownSafePrototypes,
1037
+ );
1030
1038
 
1031
- return bytes === undefined
1032
- ? Effect.fail(
1033
- ModelProtocolError.make({
1034
- message: `Model response exceeded the ${limits.maxModelResponseBytes}-byte retained response limit`,
1035
- }),
1036
- )
1037
- : Effect.succeed(bytes);
1038
- });
1039
+ return (
1040
+ bytes ??
1041
+ ModelProtocolError.make({
1042
+ message: `Model response exceeded the ${limits.maxModelResponseBytes}-byte retained response limit`,
1043
+ })
1044
+ );
1045
+ };
1039
1046
 
1040
1047
  /**
1041
1048
  * LanguageModel validates encoded parameters even with automatic resolution disabled.
@@ -1128,7 +1135,7 @@ const modelResponseParsersFor = (toolkit: Toolkit.Any) => {
1128
1135
  return parsers;
1129
1136
  };
1130
1137
 
1131
- const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1138
+ const captureModelResponsePartGeneral = Effect.fnUntracedEager(function* <
1132
1139
  Tools extends Record<string, Tool.Any>,
1133
1140
  >(
1134
1141
  part: unknown,
@@ -1155,19 +1162,23 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1155
1162
  if (directInputBytes === undefined) {
1156
1163
  const preflight = schemaClassToolPartPreflight(part, toolkit);
1157
1164
 
1158
- yield* inspectModelResponsePartCapacity(usage, preflight ?? part, limits);
1165
+ const capacity = inspectModelResponsePartCapacity(usage, preflight ?? part, limits);
1166
+
1167
+ if (typeof capacity !== "number") return yield* capacity;
1159
1168
  }
1160
1169
  const parsers = modelResponseParsersFor(toolkit);
1161
1170
 
1162
1171
  const encoded = yield* parsers
1163
1172
  .encode(part)
1164
1173
  .pipe(
1165
- Effect.mapError(() =>
1174
+ Effect.mapErrorEager(() =>
1166
1175
  ModelProtocolError.make({ message: "Model response part failed canonical encoding" }),
1167
1176
  ),
1168
1177
  );
1169
1178
 
1170
- const retainedBytes = yield* inspectModelResponsePartCapacity(usage, encoded, limits);
1179
+ const retainedBytes = inspectModelResponsePartCapacity(usage, encoded, limits);
1180
+
1181
+ if (typeof retainedBytes !== "number") return yield* retainedBytes;
1171
1182
 
1172
1183
  if (typeof structuredCloneFunction !== "function") {
1173
1184
  return yield* ModelProtocolError.make({
@@ -1175,17 +1186,15 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1175
1186
  });
1176
1187
  }
1177
1188
 
1178
- const ownedEncoded = yield* Effect.try({
1179
- try: () => {
1180
- const cloned: unknown = Reflect.apply(structuredCloneFunction, globalThis, [encoded]);
1189
+ let ownedEncoded: unknown;
1181
1190
 
1182
- return cloned;
1183
- },
1184
- catch: () =>
1185
- ModelProtocolError.make({
1186
- message: "Model response part could not be converted into engine-owned data",
1187
- }),
1188
- });
1191
+ try {
1192
+ ownedEncoded = Reflect.apply(structuredCloneFunction, globalThis, [encoded]);
1193
+ } catch {
1194
+ return yield* ModelProtocolError.make({
1195
+ message: "Model response part could not be converted into engine-owned data",
1196
+ });
1197
+ }
1189
1198
 
1190
1199
  return { encodedPart: ownedEncoded, retainedBytes };
1191
1200
  });
@@ -1208,49 +1217,51 @@ const decodePrimitiveModelResponsePart = Schema.decodeUnknownEffect(
1208
1217
  ),
1209
1218
  );
1210
1219
 
1211
- /** Reject trailing content before its capture can run an application codec or suspend. */
1220
+ /**
1221
+ * Called once by the executing consumer. Pure capture stays in that step; application
1222
+ * codecs may suspend in the same fiber. Reject trailing content before either path.
1223
+ */
1212
1224
  const ownModelResponsePart = <Tools extends Record<string, Tool.Any>>(
1213
1225
  part: unknown,
1214
1226
  toolkit: Toolkit.Toolkit<Tools>,
1215
1227
  usage: ModelResponseBufferUsage,
1216
1228
  limits: EffectiveRunBufferLimits,
1217
1229
  finished: boolean,
1218
- ): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> =>
1219
- Effect.suspend((): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> => {
1220
- if (finished) {
1221
- return Effect.fail(
1222
- ModelProtocolError.make({
1223
- message: "Model response emitted content after its finish part",
1224
- }),
1225
- );
1226
- }
1230
+ ): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> => {
1231
+ if (finished) {
1232
+ return Effect.fail(
1233
+ ModelProtocolError.make({
1234
+ message: "Model response emitted content after its finish part",
1235
+ }),
1236
+ );
1237
+ }
1227
1238
 
1228
- const primitive =
1229
- usage.responsePartCount < limits.maxModelResponseParts
1230
- ? capturePrimitiveTextPart(part, limits.maxModelResponseBytes - usage.responsePartBytes)
1231
- : undefined;
1239
+ const primitive =
1240
+ usage.responsePartCount < limits.maxModelResponseParts
1241
+ ? capturePrimitiveTextPart(part, limits.maxModelResponseBytes - usage.responsePartBytes)
1242
+ : undefined;
1243
+
1244
+ // Complete primitive snapshots need validation, but no defaults or transformation.
1245
+ // Schema.toType retains every constraint from the native Response part schemas.
1246
+ if (primitive !== undefined) {
1247
+ return Effect.mapBothEager(decodePrimitiveModelResponsePart(primitive.part), {
1248
+ onFailure: () =>
1249
+ ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1250
+ onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: primitive.retainedBytes }),
1251
+ });
1252
+ }
1232
1253
 
1233
- // Complete primitive snapshots need validation, but no defaults or transformation.
1234
- // Schema.toType retains every constraint from the native Response part schemas.
1235
- if (primitive !== undefined) {
1236
- return Effect.mapBothEager(decodePrimitiveModelResponsePart(primitive.part), {
1254
+ // Pending application codecs keep normal Effect execution in this same consumer fiber.
1255
+ return Effect.flatMapEager(
1256
+ captureModelResponsePartGeneral(part, toolkit, usage, limits),
1257
+ (captured) =>
1258
+ Effect.mapBothEager(modelResponseParsersFor(toolkit).decode(captured.encodedPart), {
1237
1259
  onFailure: () =>
1238
1260
  ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1239
- onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: primitive.retainedBytes }),
1240
- });
1241
- }
1242
-
1243
- // Pending application codecs keep normal Effect execution in this same consumer fiber.
1244
- return Effect.flatMapEager(
1245
- captureModelResponsePartGeneral(part, toolkit, usage, limits),
1246
- (captured) =>
1247
- Effect.mapBothEager(modelResponseParsersFor(toolkit).decode(captured.encodedPart), {
1248
- onFailure: () =>
1249
- ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1250
- onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: captured.retainedBytes }),
1251
- }),
1252
- );
1253
- });
1261
+ onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: captured.retainedBytes }),
1262
+ }),
1263
+ );
1264
+ };
1254
1265
 
1255
1266
  type PartLifecycle = "open" | "closed";
1256
1267
 
@@ -1307,10 +1318,12 @@ const endPart = (
1307
1318
  parts: Map<string, PartLifecycle>,
1308
1319
  id: string,
1309
1320
  description: string,
1310
- ): Effect.Effect<void, ModelProtocolError> =>
1311
- continuePart(parts, id, `${description} end`).pipe(
1312
- Effect.tap(() => Effect.sync(() => parts.set(id, "closed"))),
1313
- );
1321
+ ): Effect.Effect<void, ModelProtocolError> => {
1322
+ if (parts.get(id) !== "open") return continuePart(parts, id, `${description} end`);
1323
+ parts.set(id, "closed");
1324
+
1325
+ return Effect.void;
1326
+ };
1314
1327
 
1315
1328
  const firstOpenPart = (trace: TurnTrace): string | undefined => {
1316
1329
  for (const [id, state] of trace.textParts) {
@@ -1663,7 +1676,7 @@ const makeToolFailedEvent = Effect.fnUntraced(function* (
1663
1676
  /**
1664
1677
  * Settle a rejected Tool batch without starting any handler: every open
1665
1678
  * application call receives the encoded failure as its model-visible result.
1666
- * Only budget rejections (RUN-018) are exempt from repeated-failure folding;
1679
+ * Policy and persistence budget rejections are exempt from repeated-failure folding;
1667
1680
  * correctable model declarations count as ordinary failed calls. Emit one
1668
1681
  * `ToolCallFailed` per call with no `ToolCallStarted` (the
1669
1682
  * approval-denied precedent). The caller hands the trace to
@@ -1676,10 +1689,10 @@ const settleRejectedBatch = Effect.fnUntraced(function* (
1676
1689
  context: RunContext,
1677
1690
  turnId: TurnId,
1678
1691
  trace: TurnTrace,
1679
- error: AgentPolicyError | ModelProtocolError,
1692
+ error: AgentPolicyError | AgentPersistenceCapacityError | ModelProtocolError,
1680
1693
  alreadySettled?: ReadonlySet<string>,
1681
1694
  ): Effect.fn.Return<ReadonlyArray<RunEvent>, ModelProtocolError> {
1682
- const budgetRejected = error._tag === "AgentPolicyError" ? true : undefined;
1695
+ const budgetRejected = error._tag !== "ModelProtocolError" ? true : undefined;
1683
1696
 
1684
1697
  const encodedResult = {
1685
1698
  _tag: error._tag,
@@ -3349,9 +3362,13 @@ const decodeUsageTotal = Schema.decodeEffect(Schema.Natural);
3349
3362
  const decodeProviderUsage = Schema.decodeEffect(ProviderUsage);
3350
3363
 
3351
3364
  const decodeProviderUsageTotal = (value: number): Effect.Effect<number, ModelProtocolError> =>
3352
- decodeUsageTotal(value).pipe(Effect.mapError(() => invalidProviderUsage()));
3365
+ decodeUsageTotal(value).pipe(Effect.mapErrorEager(() => invalidProviderUsage()));
3353
3366
 
3354
- const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>(
3367
+ const consumeUsage = Effect.fnUntracedEager(function* <
3368
+ AgentValue extends Agent.Any,
3369
+ HookError,
3370
+ HookRequirements,
3371
+ >(
3355
3372
  agent: AgentValue,
3356
3373
  context: RunContext,
3357
3374
  usage: Response.Usage | undefined,
@@ -3362,337 +3379,332 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3362
3379
  RunCostEstimateRequest,
3363
3380
  "response" | "finishMetadata" | "purpose" | "webSearchCalls"
3364
3381
  > = {},
3365
- ): Effect.Effect<
3382
+ ): Effect.fn.Return<
3366
3383
  ConsumedUsage,
3367
3384
  AgentPolicyError | ModelProtocolError | HookError,
3368
3385
  HookRequirements | Model.ProviderName | Model.ModelName
3369
- > =>
3370
- Effect.gen(function* () {
3371
- if (usage === undefined) {
3372
- return yield* AgentPolicyError.make({
3373
- limit: "usage",
3374
- message: "A completed model response did not report usage",
3375
- });
3376
- }
3386
+ > {
3387
+ if (usage === undefined) {
3388
+ return yield* AgentPolicyError.make({
3389
+ limit: "usage",
3390
+ message: "A completed model response did not report usage",
3391
+ });
3392
+ }
3377
3393
 
3378
- const webSearchCalls = yield* decodeProviderUsageTotal(response.webSearchCalls ?? 0);
3394
+ const webSearchCalls = yield* decodeProviderUsageTotal(response.webSearchCalls ?? 0);
3379
3395
 
3380
- const providerUsage = yield* decodeProviderUsage(usage).pipe(
3381
- Effect.mapError(() => invalidProviderUsage()),
3382
- );
3396
+ const providerUsage = yield* decodeProviderUsage(usage).pipe(
3397
+ Effect.mapErrorEager(() => invalidProviderUsage()),
3398
+ );
3383
3399
 
3384
- const reportedUncached = providerUsage.inputTokens.uncached ?? 0;
3385
- const cacheRead = providerUsage.inputTokens.cacheRead ?? 0;
3386
- const cacheWrite = providerUsage.inputTokens.cacheWrite ?? 0;
3387
- const reportedText = providerUsage.outputTokens.text ?? 0;
3388
- const reasoning = providerUsage.outputTokens.reasoning ?? 0;
3389
- // Gross token accounting never discounts cache activity. An omitted
3390
- // aggregate is derived from present components; an omitted component may
3391
- // receive the aggregate remainder. Explicitly contradictory fields fail.
3392
- // Some providers include cache writes in `uncached` while also reporting
3393
- // them separately. Separate that overlap before constructing the canonical,
3394
- // additive input components, but retain the raw provider usage for pricing.
3395
- const reportedInputTotal = providerUsage.inputTokens.total;
3396
- const reportedInputWithoutWrite = reportedUncached + cacheRead;
3397
-
3398
- const cacheWriteOverlapsUncached =
3399
- reportedInputTotal !== undefined &&
3400
- providerUsage.inputTokens.uncached !== undefined &&
3401
- providerUsage.inputTokens.cacheWrite !== undefined &&
3402
- cacheWrite <= reportedUncached &&
3403
- reportedInputWithoutWrite <= reportedInputTotal &&
3404
- cacheWrite > reportedInputTotal - reportedInputWithoutWrite;
3405
-
3406
- const disjointReportedUncached =
3407
- reportedUncached - (cacheWriteOverlapsUncached ? cacheWrite : 0);
3408
-
3409
- const reportedInputComponents = disjointReportedUncached + cacheRead + cacheWrite;
3410
-
3411
- const allInputComponentsReported =
3412
- providerUsage.inputTokens.uncached !== undefined &&
3413
- providerUsage.inputTokens.cacheRead !== undefined &&
3414
- providerUsage.inputTokens.cacheWrite !== undefined;
3400
+ const reportedUncached = providerUsage.inputTokens.uncached ?? 0;
3401
+ const cacheRead = providerUsage.inputTokens.cacheRead ?? 0;
3402
+ const cacheWrite = providerUsage.inputTokens.cacheWrite ?? 0;
3403
+ const reportedText = providerUsage.outputTokens.text ?? 0;
3404
+ const reasoning = providerUsage.outputTokens.reasoning ?? 0;
3405
+ // Gross token accounting never discounts cache activity. An omitted
3406
+ // aggregate is derived from present components; an omitted component may
3407
+ // receive the aggregate remainder. Explicitly contradictory fields fail.
3408
+ // Some providers include cache writes in `uncached` while also reporting
3409
+ // them separately. Separate that overlap before constructing the canonical,
3410
+ // additive input components, but retain the raw provider usage for pricing.
3411
+ const reportedInputTotal = providerUsage.inputTokens.total;
3412
+ const reportedInputWithoutWrite = reportedUncached + cacheRead;
3413
+
3414
+ const cacheWriteOverlapsUncached =
3415
+ reportedInputTotal !== undefined &&
3416
+ providerUsage.inputTokens.uncached !== undefined &&
3417
+ providerUsage.inputTokens.cacheWrite !== undefined &&
3418
+ cacheWrite <= reportedUncached &&
3419
+ reportedInputWithoutWrite <= reportedInputTotal &&
3420
+ cacheWrite > reportedInputTotal - reportedInputWithoutWrite;
3421
+
3422
+ const disjointReportedUncached = reportedUncached - (cacheWriteOverlapsUncached ? cacheWrite : 0);
3423
+
3424
+ const reportedInputComponents = disjointReportedUncached + cacheRead + cacheWrite;
3425
+
3426
+ const allInputComponentsReported =
3427
+ providerUsage.inputTokens.uncached !== undefined &&
3428
+ providerUsage.inputTokens.cacheRead !== undefined &&
3429
+ providerUsage.inputTokens.cacheWrite !== undefined;
3415
3430
 
3416
- if (
3417
- reportedInputTotal !== undefined &&
3418
- (reportedInputTotal < reportedInputComponents ||
3419
- (allInputComponentsReported && reportedInputTotal !== reportedInputComponents))
3420
- ) {
3421
- return yield* invalidProviderUsage();
3422
- }
3423
- const inputTokens = reportedInputTotal ?? reportedInputComponents;
3424
- const reportedOutputComponents = reportedText + reasoning;
3425
- const reportedOutputTotal = providerUsage.outputTokens.total;
3431
+ if (
3432
+ reportedInputTotal !== undefined &&
3433
+ (reportedInputTotal < reportedInputComponents ||
3434
+ (allInputComponentsReported && reportedInputTotal !== reportedInputComponents))
3435
+ ) {
3436
+ return yield* invalidProviderUsage();
3437
+ }
3438
+ const inputTokens = reportedInputTotal ?? reportedInputComponents;
3439
+ const reportedOutputComponents = reportedText + reasoning;
3440
+ const reportedOutputTotal = providerUsage.outputTokens.total;
3426
3441
 
3427
- const allOutputComponentsReported =
3428
- providerUsage.outputTokens.text !== undefined &&
3429
- providerUsage.outputTokens.reasoning !== undefined;
3442
+ const allOutputComponentsReported =
3443
+ providerUsage.outputTokens.text !== undefined &&
3444
+ providerUsage.outputTokens.reasoning !== undefined;
3430
3445
 
3431
- if (
3432
- reportedOutputTotal !== undefined &&
3433
- (reportedOutputTotal < reportedOutputComponents ||
3434
- (allOutputComponentsReported && reportedOutputTotal !== reportedOutputComponents))
3435
- ) {
3436
- return yield* invalidProviderUsage();
3437
- }
3438
- const outputTokens = reportedOutputTotal ?? reportedOutputComponents;
3439
- // When a provider reports only aggregates, classify the unexplained input
3440
- // conservatively as uncached and non-reasoning output as text when those
3441
- // fields are absent. If either was explicit, assign the remainder to the
3442
- // first genuinely omitted component so no provider-supplied value changes.
3443
- const inputRemainder = inputTokens - reportedInputComponents;
3444
- const outputRemainder = outputTokens - reportedOutputComponents;
3445
-
3446
- const uncached =
3447
- disjointReportedUncached +
3448
- (providerUsage.inputTokens.uncached === undefined ? inputRemainder : 0);
3449
-
3450
- const normalizedCacheRead =
3451
- cacheRead +
3452
- (providerUsage.inputTokens.uncached !== undefined &&
3453
- providerUsage.inputTokens.cacheRead === undefined
3454
- ? inputRemainder
3455
- : 0);
3456
-
3457
- const normalizedCacheWrite =
3458
- cacheWrite +
3459
- (providerUsage.inputTokens.uncached !== undefined &&
3460
- providerUsage.inputTokens.cacheRead !== undefined &&
3461
- providerUsage.inputTokens.cacheWrite === undefined
3462
- ? inputRemainder
3463
- : 0);
3464
-
3465
- const text =
3466
- reportedText + (providerUsage.outputTokens.text === undefined ? outputRemainder : 0);
3467
-
3468
- const normalizedReasoning =
3469
- reasoning +
3470
- (providerUsage.outputTokens.text !== undefined &&
3471
- providerUsage.outputTokens.reasoning === undefined
3472
- ? outputRemainder
3473
- : 0);
3474
-
3475
- // Validate the complete additive components before pricing or staging usage.
3476
- const inputUsage = yield* InputTokenUsage.makeEffect({
3477
- total: inputTokens,
3478
- uncached,
3479
- cacheRead: normalizedCacheRead,
3480
- cacheWrite: normalizedCacheWrite,
3481
- }).pipe(Effect.mapError(() => invalidProviderUsage()));
3482
-
3483
- const outputUsage = yield* OutputTokenUsage.makeEffect({
3484
- total: outputTokens,
3485
- text,
3486
- reasoning: normalizedReasoning,
3487
- }).pipe(Effect.mapError(() => invalidProviderUsage()));
3488
-
3489
- const totalTokens = yield* decodeProviderUsageTotal(inputTokens + outputTokens);
3490
- const provider = yield* Model.ProviderName;
3491
- const model = yield* Model.ModelName;
3492
-
3493
- const estimate =
3494
- options.estimateCostMicrousd === undefined
3495
- ? 0
3496
- : yield* options.estimateCostMicrousd(usage, {
3497
- provider,
3498
- model,
3499
- usage,
3500
- ...response,
3501
- webSearchCalls,
3502
- });
3446
+ if (
3447
+ reportedOutputTotal !== undefined &&
3448
+ (reportedOutputTotal < reportedOutputComponents ||
3449
+ (allOutputComponentsReported && reportedOutputTotal !== reportedOutputComponents))
3450
+ ) {
3451
+ return yield* invalidProviderUsage();
3452
+ }
3453
+ const outputTokens = reportedOutputTotal ?? reportedOutputComponents;
3454
+ // When a provider reports only aggregates, classify the unexplained input
3455
+ // conservatively as uncached and non-reasoning output as text when those
3456
+ // fields are absent. If either was explicit, assign the remainder to the
3457
+ // first genuinely omitted component so no provider-supplied value changes.
3458
+ const inputRemainder = inputTokens - reportedInputComponents;
3459
+ const outputRemainder = outputTokens - reportedOutputComponents;
3460
+
3461
+ const uncached =
3462
+ disjointReportedUncached +
3463
+ (providerUsage.inputTokens.uncached === undefined ? inputRemainder : 0);
3464
+
3465
+ const normalizedCacheRead =
3466
+ cacheRead +
3467
+ (providerUsage.inputTokens.uncached !== undefined &&
3468
+ providerUsage.inputTokens.cacheRead === undefined
3469
+ ? inputRemainder
3470
+ : 0);
3471
+
3472
+ const normalizedCacheWrite =
3473
+ cacheWrite +
3474
+ (providerUsage.inputTokens.uncached !== undefined &&
3475
+ providerUsage.inputTokens.cacheRead !== undefined &&
3476
+ providerUsage.inputTokens.cacheWrite === undefined
3477
+ ? inputRemainder
3478
+ : 0);
3479
+
3480
+ const text = reportedText + (providerUsage.outputTokens.text === undefined ? outputRemainder : 0);
3481
+
3482
+ const normalizedReasoning =
3483
+ reasoning +
3484
+ (providerUsage.outputTokens.text !== undefined &&
3485
+ providerUsage.outputTokens.reasoning === undefined
3486
+ ? outputRemainder
3487
+ : 0);
3488
+
3489
+ // Validate the complete additive components before pricing or staging usage.
3490
+ const inputUsage = yield* InputTokenUsage.makeEffect({
3491
+ total: inputTokens,
3492
+ uncached,
3493
+ cacheRead: normalizedCacheRead,
3494
+ cacheWrite: normalizedCacheWrite,
3495
+ }).pipe(Effect.mapErrorEager(() => invalidProviderUsage()));
3496
+
3497
+ const outputUsage = yield* OutputTokenUsage.makeEffect({
3498
+ total: outputTokens,
3499
+ text,
3500
+ reasoning: normalizedReasoning,
3501
+ }).pipe(Effect.mapErrorEager(() => invalidProviderUsage()));
3502
+
3503
+ const totalTokens = yield* decodeProviderUsageTotal(inputTokens + outputTokens);
3504
+ const provider = yield* Model.ProviderName;
3505
+ const model = yield* Model.ModelName;
3503
3506
 
3504
- const costMicrousd = typeof estimate === "number" ? estimate : estimate.costMicrousd;
3507
+ const estimate =
3508
+ options.estimateCostMicrousd === undefined
3509
+ ? 0
3510
+ : yield* options.estimateCostMicrousd(usage, {
3511
+ provider,
3512
+ model,
3513
+ usage,
3514
+ ...response,
3515
+ webSearchCalls,
3516
+ });
3505
3517
 
3506
- if (!Number.isSafeInteger(costMicrousd) || costMicrousd < 0) {
3507
- return yield* AgentPolicyError.make({
3508
- limit: "cost",
3509
- message: "Model cost estimation must produce a non-negative integer number of microdollars",
3510
- });
3511
- }
3512
- const serviceTier = typeof estimate === "number" ? undefined : estimate.serviceTier;
3513
- const pricingVersion = typeof estimate === "number" ? undefined : estimate.pricingVersion;
3518
+ const costMicrousd = typeof estimate === "number" ? estimate : estimate.costMicrousd;
3514
3519
 
3515
- const pricingStatus =
3516
- options.estimateCostMicrousd === undefined
3517
- ? "unknown"
3518
- : typeof estimate === "number"
3519
- ? "estimated"
3520
- : (estimate.pricingStatus ?? "estimated");
3520
+ if (!Number.isSafeInteger(costMicrousd) || costMicrousd < 0) {
3521
+ return yield* AgentPolicyError.make({
3522
+ limit: "cost",
3523
+ message: "Model cost estimation must produce a non-negative integer number of microdollars",
3524
+ });
3525
+ }
3526
+ const serviceTier = typeof estimate === "number" ? undefined : estimate.serviceTier;
3527
+ const pricingVersion = typeof estimate === "number" ? undefined : estimate.pricingVersion;
3521
3528
 
3522
- const validPricingIdentity = (value: string | undefined): boolean =>
3523
- value === undefined || (value.length > 0 && value.length <= 256);
3529
+ const pricingStatus =
3530
+ options.estimateCostMicrousd === undefined
3531
+ ? "unknown"
3532
+ : typeof estimate === "number"
3533
+ ? "estimated"
3534
+ : (estimate.pricingStatus ?? "estimated");
3524
3535
 
3525
- if (!validPricingIdentity(serviceTier) || !validPricingIdentity(pricingVersion)) {
3526
- return yield* AgentPolicyError.make({
3527
- limit: "cost",
3528
- message: "Model cost estimation returned an invalid service tier or pricing version",
3529
- });
3530
- }
3536
+ const validPricingIdentity = (value: string | undefined): boolean =>
3537
+ value === undefined || (value.length > 0 && value.length <= 256);
3531
3538
 
3532
- const modelUsage = ModelCallUsage.make({
3533
- webSearchCalls,
3534
- provider,
3535
- model,
3536
- ...(response.response === undefined
3537
- ? {}
3538
- : { response: ModelResponseIdentity.make(response.response) }),
3539
- purpose: response.purpose ?? "turn",
3540
- usageStatus:
3541
- Object.values(providerUsage.inputTokens).every((value) => value === undefined) &&
3542
- Object.values(providerUsage.outputTokens).every((value) => value === undefined)
3543
- ? "unknown"
3544
- : [
3545
- providerUsage.inputTokens.uncached,
3546
- providerUsage.inputTokens.cacheRead,
3547
- providerUsage.inputTokens.cacheWrite,
3548
- providerUsage.outputTokens.text,
3549
- providerUsage.outputTokens.reasoning,
3550
- ].every((value) => value !== undefined)
3551
- ? "complete"
3552
- : "partial",
3553
- pricingStatus,
3554
- ...(serviceTier === undefined ? {} : { serviceTier }),
3555
- ...(pricingVersion === undefined ? {} : { pricingVersion }),
3556
- inputTokens: inputUsage,
3557
- outputTokens: outputUsage,
3558
- costMicrousd,
3539
+ if (!validPricingIdentity(serviceTier) || !validPricingIdentity(pricingVersion)) {
3540
+ return yield* AgentPolicyError.make({
3541
+ limit: "cost",
3542
+ message: "Model cost estimation returned an invalid service tier or pricing version",
3559
3543
  });
3544
+ }
3560
3545
 
3561
- const modelCalls = yield* decodeProviderUsageTotal(context.modelCalls + 1);
3546
+ const modelUsage = ModelCallUsage.make({
3547
+ webSearchCalls,
3548
+ provider,
3549
+ model,
3550
+ ...(response.response === undefined
3551
+ ? {}
3552
+ : { response: ModelResponseIdentity.make(response.response) }),
3553
+ purpose: response.purpose ?? "turn",
3554
+ usageStatus:
3555
+ Object.values(providerUsage.inputTokens).every((value) => value === undefined) &&
3556
+ Object.values(providerUsage.outputTokens).every((value) => value === undefined)
3557
+ ? "unknown"
3558
+ : [
3559
+ providerUsage.inputTokens.uncached,
3560
+ providerUsage.inputTokens.cacheRead,
3561
+ providerUsage.inputTokens.cacheWrite,
3562
+ providerUsage.outputTokens.text,
3563
+ providerUsage.outputTokens.reasoning,
3564
+ ].every((value) => value !== undefined)
3565
+ ? "complete"
3566
+ : "partial",
3567
+ pricingStatus,
3568
+ ...(serviceTier === undefined ? {} : { serviceTier }),
3569
+ ...(pricingVersion === undefined ? {} : { pricingVersion }),
3570
+ inputTokens: inputUsage,
3571
+ outputTokens: outputUsage,
3572
+ costMicrousd,
3573
+ });
3562
3574
 
3563
- const cumulativeWebSearchCalls =
3564
- context.webSearchCalls === undefined
3565
- ? undefined
3566
- : yield* decodeProviderUsageTotal(context.webSearchCalls + webSearchCalls);
3575
+ const modelCalls = yield* decodeProviderUsageTotal(context.modelCalls + 1);
3567
3576
 
3568
- const cumulativeInputTokens = yield* decodeProviderUsageTotal(
3569
- context.inputTokens + inputTokens,
3570
- );
3577
+ const cumulativeWebSearchCalls =
3578
+ context.webSearchCalls === undefined
3579
+ ? undefined
3580
+ : yield* decodeProviderUsageTotal(context.webSearchCalls + webSearchCalls);
3571
3581
 
3572
- const cumulativeOutputTokens = yield* decodeProviderUsageTotal(
3573
- context.outputTokens + outputTokens,
3574
- );
3582
+ const cumulativeInputTokens = yield* decodeProviderUsageTotal(context.inputTokens + inputTokens);
3575
3583
 
3576
- const cumulativeCostMicrousd = context.costMicrousd + costMicrousd;
3584
+ const cumulativeOutputTokens = yield* decodeProviderUsageTotal(
3585
+ context.outputTokens + outputTokens,
3586
+ );
3577
3587
 
3578
- if (!Number.isSafeInteger(cumulativeCostMicrousd)) {
3579
- return yield* AgentPolicyError.make({
3580
- limit: "cost",
3581
- message: "Cumulative model cost exceeds safe-integer accounting capacity",
3582
- });
3583
- }
3584
- const firstObserved = context.modelCalls === 0 && context.unobservedModelCalls === 0;
3588
+ const cumulativeCostMicrousd = context.costMicrousd + costMicrousd;
3585
3589
 
3586
- const combineStatus = (
3587
- prior: typeof UsageCompleteness.Type,
3588
- next: typeof UsageCompleteness.Type,
3589
- ) => (firstObserved && prior === "complete" ? next : prior === next ? prior : "partial");
3590
+ if (!Number.isSafeInteger(cumulativeCostMicrousd)) {
3591
+ return yield* AgentPolicyError.make({
3592
+ limit: "cost",
3593
+ message: "Cumulative model cost exceeds safe-integer accounting capacity",
3594
+ });
3595
+ }
3596
+ const firstObserved = context.modelCalls === 0 && context.unobservedModelCalls === 0;
3590
3597
 
3591
- context.usageStatus = combineStatus(context.usageStatus, modelUsage.usageStatus ?? "unknown");
3592
- context.pricingStatus = combineStatus(
3593
- context.pricingStatus,
3594
- pricingStatus === "estimated" ? "complete" : "unknown",
3595
- );
3596
- context.modelCalls = modelCalls;
3597
- context.webSearchCalls = cumulativeWebSearchCalls;
3598
- context.inputTokens = cumulativeInputTokens;
3599
- context.outputTokens = cumulativeOutputTokens;
3600
- context.lastInputTokens = inputTokens;
3601
- context.lastOutputTokens = outputTokens;
3602
- context.costMicrousd = cumulativeCostMicrousd;
3603
- context.lastCostMicrousd = costMicrousd;
3604
-
3605
- if (options.durability !== undefined) {
3606
- // Stage before enforcing hard rails: a response that spends past the
3607
- // cost/token budget still becomes auditable in its canonical Turn and
3608
- // terminal settlement.
3609
- yield* options.durability.noteTurnUsage({ turn, usage: modelUsage });
3610
- }
3598
+ const combineStatus = (
3599
+ prior: typeof UsageCompleteness.Type,
3600
+ next: typeof UsageCompleteness.Type,
3601
+ ) => (firstObserved && prior === "complete" ? next : prior === next ? prior : "partial");
3611
3602
 
3612
- const policy = agent.definition.policy;
3613
- const consumedTokens = context.inputTokens + context.outputTokens;
3614
- const tokenBudget = policy.tokenBudget;
3615
- let breach: AgentPolicyError | undefined;
3603
+ context.usageStatus = combineStatus(context.usageStatus, modelUsage.usageStatus ?? "unknown");
3604
+ context.pricingStatus = combineStatus(
3605
+ context.pricingStatus,
3606
+ pricingStatus === "estimated" ? "complete" : "unknown",
3607
+ );
3608
+ context.modelCalls = modelCalls;
3609
+ context.webSearchCalls = cumulativeWebSearchCalls;
3610
+ context.inputTokens = cumulativeInputTokens;
3611
+ context.outputTokens = cumulativeOutputTokens;
3612
+ context.lastInputTokens = inputTokens;
3613
+ context.lastOutputTokens = outputTokens;
3614
+ context.costMicrousd = cumulativeCostMicrousd;
3615
+ context.lastCostMicrousd = costMicrousd;
3616
+
3617
+ if (options.durability !== undefined) {
3618
+ // Stage before enforcing hard rails: a response that spends past the
3619
+ // cost/token budget still becomes auditable in its canonical Turn and
3620
+ // terminal settlement.
3621
+ yield* options.durability.noteTurnUsage({ turn, usage: modelUsage });
3622
+ }
3616
3623
 
3617
- if (
3618
- !context.finalizing &&
3619
- !context.tokenExhausted &&
3620
- tokenBudget !== undefined &&
3621
- consumedTokens > tokenBudget
3622
- ) {
3623
- breach = AgentPolicyError.make({
3624
- limit: "tokens",
3625
- message: `Agent exceeded its ${tokenBudget} token budget`,
3624
+ const policy = agent.definition.policy;
3625
+ const consumedTokens = context.inputTokens + context.outputTokens;
3626
+ const tokenBudget = policy.tokenBudget;
3627
+ let breach: AgentPolicyError | undefined;
3628
+
3629
+ if (
3630
+ !context.finalizing &&
3631
+ !context.tokenExhausted &&
3632
+ tokenBudget !== undefined &&
3633
+ consumedTokens > tokenBudget
3634
+ ) {
3635
+ breach = AgentPolicyError.make({
3636
+ limit: "tokens",
3637
+ message: `Agent exceeded its ${tokenBudget} token budget`,
3638
+ });
3639
+ // Fail mode rejects before any declared application Handler starts.
3640
+ if (policy.onExhaustion === "fail") {
3641
+ return yield* breach;
3642
+ }
3643
+ // One-shot (RUN-025): the flag joins the final-answer derivation so the
3644
+ // grace Turn's own usage accumulates and charges without re-breaching.
3645
+ context.tokenExhausted = true;
3646
+ context.exhaustedDimension ??= "tokens";
3647
+ }
3648
+ // Cost is an unconditional hard rail (runtime spec §3): it is enforced on
3649
+ // every response, including one that just soft-breached the token budget —
3650
+ // a simultaneous breach fails typed instead of soft-landing on overspend.
3651
+ const costBudget = policy.costBudgetMicrousd;
3652
+
3653
+ if (costBudget !== undefined) {
3654
+ if (options.estimateCostMicrousd === undefined) {
3655
+ return yield* AgentPolicyError.make({
3656
+ limit: "cost",
3657
+ message: "Agent cost budget requires a model cost estimator",
3626
3658
  });
3627
- // Fail mode rejects before any declared application Handler starts.
3628
- if (policy.onExhaustion === "fail") {
3629
- return yield* breach;
3630
- }
3631
- // One-shot (RUN-025): the flag joins the final-answer derivation so the
3632
- // grace Turn's own usage accumulates and charges without re-breaching.
3633
- context.tokenExhausted = true;
3634
- context.exhaustedDimension ??= "tokens";
3635
3659
  }
3636
- // Cost is an unconditional hard rail (runtime spec §3): it is enforced on
3637
- // every response, including one that just soft-breached the token budget —
3638
- // a simultaneous breach fails typed instead of soft-landing on overspend.
3639
- const costBudget = policy.costBudgetMicrousd;
3640
-
3641
- if (costBudget !== undefined) {
3642
- if (options.estimateCostMicrousd === undefined) {
3643
- return yield* AgentPolicyError.make({
3644
- limit: "cost",
3645
- message: "Agent cost budget requires a model cost estimator",
3646
- });
3647
- }
3648
- if (pricingStatus === "unknown") {
3649
- return yield* AgentPolicyError.make({
3650
- limit: "cost",
3651
- message: "Agent cost budget requires a known model cost estimate",
3652
- });
3653
- }
3654
- if (context.costMicrousd > costBudget) {
3655
- // Cost is spend, not context: it never earns a grace Turn.
3656
- return yield* AgentPolicyError.make({
3657
- limit: "cost",
3658
- message: `Agent exceeded its ${costBudget} microdollar cost budget`,
3659
- });
3660
- }
3660
+ if (pricingStatus === "unknown") {
3661
+ return yield* AgentPolicyError.make({
3662
+ limit: "cost",
3663
+ message: "Agent cost budget requires a known model cost estimate",
3664
+ });
3661
3665
  }
3662
- if (options.budget !== undefined) {
3663
- const delta: RunUsageDelta = {
3664
- modelCalls: 1,
3665
- inputTokens,
3666
- outputTokens,
3667
- totalTokens,
3668
- toolCalls: toolCallCount,
3669
- costMicrousd,
3670
- usage,
3671
- modelUsage,
3672
- };
3673
-
3674
- yield* options.budget.consume(delta);
3666
+ if (context.costMicrousd > costBudget) {
3667
+ // Cost is spend, not context: it never earns a grace Turn.
3668
+ return yield* AgentPolicyError.make({
3669
+ limit: "cost",
3670
+ message: `Agent exceeded its ${costBudget} microdollar cost budget`,
3671
+ });
3675
3672
  }
3676
- const warnings: Array<RunEvent> | undefined = context.publish === undefined ? undefined : [];
3673
+ }
3674
+ if (options.budget !== undefined) {
3675
+ const delta: RunUsageDelta = {
3676
+ modelCalls: 1,
3677
+ inputTokens,
3678
+ outputTokens,
3679
+ totalTokens,
3680
+ toolCalls: toolCallCount,
3681
+ costMicrousd,
3682
+ usage,
3683
+ modelUsage,
3684
+ };
3677
3685
 
3678
- if (
3679
- tokenBudget !== undefined &&
3680
- !context.warnedLimits.has("tokens") &&
3681
- nearingLimit(consumedTokens, tokenBudget)
3682
- ) {
3683
- context.warnedLimits.add("tokens");
3684
- warnings?.push(
3685
- BudgetWarning.make({
3686
- ...(yield* eventBase(context)),
3687
- limit: "tokens",
3688
- consumed: consumedTokens,
3689
- limitValue: tokenBudget,
3690
- }),
3691
- );
3692
- }
3686
+ yield* options.budget.consume(delta);
3687
+ }
3688
+ const warnings: Array<RunEvent> | undefined = context.publish === undefined ? undefined : [];
3693
3689
 
3694
- return { breach, warnings: warnings ?? noEvents, modelUsage };
3695
- });
3690
+ if (
3691
+ tokenBudget !== undefined &&
3692
+ !context.warnedLimits.has("tokens") &&
3693
+ nearingLimit(consumedTokens, tokenBudget)
3694
+ ) {
3695
+ context.warnedLimits.add("tokens");
3696
+ warnings?.push(
3697
+ BudgetWarning.make({
3698
+ ...(yield* eventBase(context)),
3699
+ limit: "tokens",
3700
+ consumed: consumedTokens,
3701
+ limitValue: tokenBudget,
3702
+ }),
3703
+ );
3704
+ }
3705
+
3706
+ return { breach, warnings: warnings ?? noEvents, modelUsage };
3707
+ });
3696
3708
 
3697
3709
  // `Effect.fnUntraced`: this helper runs for every streamed Response Part, so a
3698
3710
  // named span here would emit one span per TextDelta/ReasoningDelta. Spans stay
@@ -5765,13 +5777,6 @@ const makeTurn = <
5765
5777
 
5766
5778
  const started = Effect.gen(function* () {
5767
5779
  yield* Metric.update(modelCounter, 1);
5768
- yield* Effect.logDebug("agent model call started").pipe(
5769
- Effect.annotateLogs({
5770
- agentId: context.agentId,
5771
- runId: context.runId,
5772
- turnId,
5773
- }),
5774
- );
5775
5780
 
5776
5781
  if (context.publish === undefined) return;
5777
5782
 
@@ -5791,7 +5796,7 @@ const makeTurn = <
5791
5796
  ]);
5792
5797
 
5793
5798
  return;
5794
- }).pipe(Effect.withLogSpan("AgentRuntime.model"));
5799
+ });
5795
5800
 
5796
5801
  // Final-answer mode (RUN-018/RUN-019, extended to tokens by RUN-025):
5797
5802
  // once the Turn, Tool Call, or token budget is
@@ -6548,8 +6553,8 @@ const makeTurn = <
6548
6553
  activeProvisionalText = provisionalText;
6549
6554
  }),
6550
6555
  ),
6551
- Stream.runForEachArray((parts) =>
6552
- Effect.gen(function* () {
6556
+ Stream.runForEachArray(
6557
+ Effect.fnUntracedEager(function* (parts) {
6553
6558
  for (const part of parts) {
6554
6559
  const owned = yield* ownModelResponsePart(
6555
6560
  part,
@@ -7349,9 +7354,10 @@ const makeTurn = <
7349
7354
  return yield* ModelProtocolError.make({
7350
7355
  message: "Tool dispatch has no validated response facts",
7351
7356
  });
7357
+
7352
7358
  // Idempotency only permits replay of the original operation. Re-asking the
7353
7359
  // model could choose different arguments or a new key, so it keeps the barrier.
7354
- yield* acceptTurnCommit(context, trace, options.durability, {
7360
+ const admission = yield* acceptTurnCommit(context, trace, options.durability, {
7355
7361
  _tag: "Response",
7356
7362
  turn,
7357
7363
  turnId,
@@ -7370,6 +7376,33 @@ const makeTurn = <
7370
7376
  ? { defer: true as const }
7371
7377
  : {}),
7372
7378
  });
7379
+
7380
+ if (admission === "capacity-refused") {
7381
+ const rejection = yield* settleRejectedBatch(
7382
+ context,
7383
+ turnId,
7384
+ trace,
7385
+ AgentPersistenceCapacityError.make({
7386
+ message:
7387
+ "This application tool batch was not dispatched because its durable results exceed available capacity. Retry the needed application tools in smaller batches. Earlier tool results remain valid.",
7388
+ }),
7389
+ );
7390
+
7391
+ return publishEvents(context, rejection).pipe(
7392
+ Effect.andThen(
7393
+ toolBatchContinuation(
7394
+ agent,
7395
+ context,
7396
+ trace,
7397
+ prompt,
7398
+ turn,
7399
+ turnId,
7400
+ toolCalls,
7401
+ options,
7402
+ ),
7403
+ ),
7404
+ );
7405
+ }
7373
7406
  }
7374
7407
 
7375
7408
  const toolResults = executeToolBatch(
@@ -8839,13 +8872,10 @@ function executeWithCompletion<
8839
8872
 
8840
8873
  const started = Effect.gen(function* () {
8841
8874
  yield* Metric.update(runCounter, 1);
8842
- yield* Effect.logDebug("agent run started").pipe(
8843
- Effect.annotateLogs({ agentId: context.agentId, runId: context.runId }),
8844
- );
8845
8875
  yield* publishEvent(context, () =>
8846
8876
  eventBase(context).pipe(Effect.map((base) => RunStarted.make(base))),
8847
8877
  );
8848
- }).pipe(Effect.withLogSpan("AgentRuntime.run"));
8878
+ });
8849
8879
 
8850
8880
  const execution = Effect.flatten(
8851
8881
  Effect.gen(function* () {
@@ -9156,6 +9186,7 @@ function executeWithCompletion<
9156
9186
  history,
9157
9187
  preparation,
9158
9188
  agent.definition.policy,
9189
+ DateTime.makeUnsafe(context.durationDeadlineMillis),
9159
9190
  options.subagentGrant,
9160
9191
  options.subagentBudget,
9161
9192
  options.subagentBudgetScope,
@@ -11091,6 +11122,8 @@ const spawnWithParent = (
11091
11122
  export interface AgentSpawnerService {
11092
11123
  /** Resolved defaults for this parent Run; never includes its tools or handlers. */
11093
11124
  readonly policy: AgentPolicy;
11125
+ /** Effective deadline of this active parent execution, including RunOptions.durationDeadline. */
11126
+ readonly durationDeadline: DateTime.Utc;
11094
11127
  readonly depth: number;
11095
11128
  readonly grant?: SubagentGrant;
11096
11129
  readonly budget?: SubagentBudgetReservation;
@@ -11199,6 +11232,7 @@ const makeAgentSpawner = (
11199
11232
  history: ThreadHistory["Service"],
11200
11233
  preparation: RunContextPreparation["Service"],
11201
11234
  policy: AgentPolicy,
11235
+ durationDeadline: DateTime.Utc,
11202
11236
  grant?: SubagentGrant,
11203
11237
  budget?: SubagentBudgetReservation,
11204
11238
  budgetScope?: WorkerBudgetScope,
@@ -11208,6 +11242,7 @@ const makeAgentSpawner = (
11208
11242
  ...(budget === undefined ? {} : { budget }),
11209
11243
  ...(budgetScope === undefined ? {} : { budgetScope }),
11210
11244
  policy,
11245
+ durationDeadline,
11211
11246
  depth,
11212
11247
  parent,
11213
11248
  spawn: spawnWithParent(parent, depth, history, preparation, onChild),