@yielded/agent 0.1.0-beta.182 → 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 (162) hide show
  1. package/dist/{Admin-C4mPa0M2.d.mts → Admin-BgCHCGSl.d.mts} +5 -5
  2. package/dist/{AgentRuntime-CVsnQBPe.d.mts → AgentRuntime-DhRchFzv.d.mts} +4 -1
  3. package/dist/{AgentRuntime-1OjlOCa9.mjs → AgentRuntime-ILtyHuQG.mjs} +52 -40
  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-D2I_T2Yt.mjs → DurableAgentRuntime-Cj263rW9.mjs} +20 -55
  9. package/dist/DurableAgentRuntime-Cj263rW9.mjs.map +1 -0
  10. package/dist/{EventSource-gc82d-f6.d.mts → EventSource-CaA6RAtC.d.mts} +3 -3
  11. package/dist/{MessageDelivery-DrQKvh93.d.mts → MessageDelivery-rlLpakO8.d.mts} +43 -43
  12. package/dist/{PreparedInputAdmission-CrqdFUd8.d.mts → PreparedInputAdmission-DjbWz2lW.d.mts} +4 -4
  13. package/dist/{RecordFormat-BNiLpe43.d.mts → RecordFormat-CjMl0uu8.d.mts} +2 -2
  14. package/dist/{Records-BTnGRdcf.d.mts → Records-BP67Yz6n.d.mts} +20 -8
  15. package/dist/{Records-CZRedCE7.mjs → Records-bbGVHprv.mjs} +14 -10
  16. package/dist/Records-bbGVHprv.mjs.map +1 -0
  17. package/dist/{RunContinuation-DMbA81sZ.d.mts → RunContinuation-CIJRN_vC.d.mts} +4 -4
  18. package/dist/{RunContinuation-DWiO2K9D.mjs → RunContinuation-DJ6kkbQG.mjs} +17 -58
  19. package/dist/RunContinuation-DJ6kkbQG.mjs.map +1 -0
  20. package/dist/{RunJournal-oJLo-pjS.mjs → RunJournal-DQr2dUgg.mjs} +4 -7
  21. package/dist/RunJournal-DQr2dUgg.mjs.map +1 -0
  22. package/dist/{RunStorage-DlfvMfHM.mjs → RunStorage-DblN-cmM.mjs} +2 -2
  23. package/dist/{RunStorage-DlfvMfHM.mjs.map → RunStorage-DblN-cmM.mjs.map} +1 -1
  24. package/dist/{Schedule-CDgTz0vj.d.mts → Schedule-CVLtl5rP.d.mts} +2 -2
  25. package/dist/{Scheduling-BebkRVc2.mjs → Scheduling-B6l0lWdr.mjs} +3 -3
  26. package/dist/{Scheduling-BebkRVc2.mjs.map → Scheduling-B6l0lWdr.mjs.map} +1 -1
  27. package/dist/{Subagent-BRiDyUS4.mjs → Subagent-Bm842K7J.mjs} +43 -17
  28. package/dist/Subagent-Bm842K7J.mjs.map +1 -0
  29. package/dist/{Subagent-FypBXZP1.d.mts → Subagent-CWWmA1UQ.d.mts} +15 -3
  30. package/dist/{Subscription-Bx0LiX3x.d.mts → Subscription-BRsNy0TA.d.mts} +4 -4
  31. package/dist/{SubscriptionInput-CBFCfuOF.d.mts → SubscriptionInput-ClCaW_kX.d.mts} +3 -3
  32. package/dist/{ThreadImport-DlCLjRHK.mjs → ThreadImport-CkPAxXU5.mjs} +7 -7
  33. package/dist/{ThreadImport-DlCLjRHK.mjs.map → ThreadImport-CkPAxXU5.mjs.map} +1 -1
  34. package/dist/{ThreadImport-DTV_ario.d.mts → ThreadImport-DtRNoHIr.d.mts} +5 -5
  35. package/dist/{ThreadStore-D2rqtt7k.mjs → ThreadStore-C_ofHc7h.mjs} +4 -4
  36. package/dist/{ThreadStore-D2rqtt7k.mjs.map → ThreadStore-C_ofHc7h.mjs.map} +1 -1
  37. package/dist/{ThreadTransfer-BFolkpEp.mjs → ThreadTransfer-C_TRuo_9.mjs} +4 -4
  38. package/dist/{ThreadTransfer-BFolkpEp.mjs.map → ThreadTransfer-C_TRuo_9.mjs.map} +1 -1
  39. package/dist/{WorkerAdmission-Cxg_REhE.mjs → WorkerAdmission-9Ka3edXj.mjs} +5 -5
  40. package/dist/{WorkerAdmission-Cxg_REhE.mjs.map → WorkerAdmission-9Ka3edXj.mjs.map} +1 -1
  41. package/dist/{agent-registration-C6tkQz8r.mjs → agent-registration-7kp1dHT0.mjs} +4 -4
  42. package/dist/{agent-registration-C6tkQz8r.mjs.map → agent-registration-7kp1dHT0.mjs.map} +1 -1
  43. package/dist/capabilities/Subagent.d.mts +1 -1
  44. package/dist/capabilities/Subagent.mjs +1 -1
  45. package/dist/capabilities/SubagentReservations.d.mts +4 -1
  46. package/dist/capabilities/SubagentReservations.mjs +4 -1
  47. package/dist/capabilities/SubagentReservations.mjs.map +1 -1
  48. package/dist/durable/ActivityStore.d.mts +1 -1
  49. package/dist/durable/ActivityStore.mjs +1 -1
  50. package/dist/durable/Admin.d.mts +1 -1
  51. package/dist/durable/Admin.mjs +1 -1
  52. package/dist/durable/AgentRegistration.d.mts +1 -1
  53. package/dist/durable/AgentRegistration.mjs +1 -1
  54. package/dist/durable/Certification.d.mts +1 -1
  55. package/dist/durable/CommittedActivity.d.mts +3 -3
  56. package/dist/durable/CommittedActivity.mjs +3 -3
  57. package/dist/durable/Digest.d.mts +1 -1
  58. package/dist/durable/Digest.mjs +1 -1
  59. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  60. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  61. package/dist/durable/EventSource.d.mts +1 -1
  62. package/dist/durable/EventSource.mjs +1 -1
  63. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  64. package/dist/durable/LifecyclePublication.d.mts +1 -1
  65. package/dist/durable/LifecyclePublication.mjs +2 -2
  66. package/dist/durable/MessageDelivery.d.mts +1 -1
  67. package/dist/durable/MessageDelivery.mjs +2 -2
  68. package/dist/durable/MessageDeliveryStoreConformance.d.mts +4 -4
  69. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  70. package/dist/durable/PersistentHistory.d.mts +1 -1
  71. package/dist/durable/PersistentHistory.mjs +4 -4
  72. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  73. package/dist/durable/RecordFormat.d.mts +1 -1
  74. package/dist/durable/RecordFormat.mjs +1 -1
  75. package/dist/durable/Records.d.mts +1 -1
  76. package/dist/durable/Records.mjs +1 -1
  77. package/dist/durable/Recovery.d.mts +1 -1
  78. package/dist/durable/Recovery.mjs +1 -1
  79. package/dist/durable/RunContinuation.d.mts +3 -3
  80. package/dist/durable/RunContinuation.mjs +3 -3
  81. package/dist/durable/RunJournal.d.mts +1 -1
  82. package/dist/durable/RunJournal.mjs +1 -1
  83. package/dist/durable/RunStorage.d.mts +1 -1
  84. package/dist/durable/RunStorage.mjs +1 -1
  85. package/dist/durable/Schedule.d.mts +1 -1
  86. package/dist/durable/Schedule.mjs +1 -1
  87. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  88. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  89. package/dist/durable/ScheduleTransition.d.mts +1 -1
  90. package/dist/durable/ScheduleTransition.mjs +1 -1
  91. package/dist/durable/Scheduling.d.mts +3 -3
  92. package/dist/durable/Scheduling.mjs +1 -1
  93. package/dist/durable/SettlementPublisher.d.mts +1 -1
  94. package/dist/durable/SettlementPublisher.mjs +3 -3
  95. package/dist/durable/SubmissionLedger.d.mts +1 -1
  96. package/dist/durable/SubmissionLedger.mjs +2 -2
  97. package/dist/durable/SubmissionLedgerConformance.d.mts +2 -2
  98. package/dist/durable/SubmissionLedgerConformance.mjs +5 -5
  99. package/dist/durable/SubmissionStatus.d.mts +1 -1
  100. package/dist/durable/Subscription.d.mts +1 -1
  101. package/dist/durable/Subscription.mjs +1 -1
  102. package/dist/durable/SubscriptionInput.d.mts +1 -1
  103. package/dist/durable/SubscriptionInput.mjs +2 -2
  104. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  105. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  106. package/dist/durable/SubscriptionTools.d.mts +4 -4
  107. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  108. package/dist/durable/SubscriptionTransition.mjs +1 -1
  109. package/dist/durable/Subscriptions.d.mts +6 -6
  110. package/dist/durable/Subscriptions.mjs +2 -2
  111. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  112. package/dist/durable/ThreadArchiveRange.mjs +1 -1
  113. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  114. package/dist/durable/ThreadContextHistory.mjs +2 -2
  115. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  116. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  117. package/dist/durable/ThreadImport.d.mts +1 -1
  118. package/dist/durable/ThreadImport.mjs +1 -1
  119. package/dist/durable/ThreadInvariants.d.mts +3 -3
  120. package/dist/durable/ThreadInvariants.mjs +4 -4
  121. package/dist/durable/ThreadProjection.d.mts +1 -1
  122. package/dist/durable/ThreadProjection.mjs +1 -1
  123. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  124. package/dist/durable/ThreadReader.d.mts +1 -1
  125. package/dist/durable/ThreadReader.mjs +1 -1
  126. package/dist/durable/ThreadStore.d.mts +2 -2
  127. package/dist/durable/ThreadStore.mjs +2 -2
  128. package/dist/durable/ThreadStoreConformance.d.mts +2 -2
  129. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  130. package/dist/durable/ThreadTransfer.d.mts +2 -2
  131. package/dist/durable/ThreadTransfer.mjs +1 -1
  132. package/dist/durable/ThreadWork.d.mts +1 -1
  133. package/dist/durable/ThreadWork.mjs +5 -5
  134. package/dist/durable/ToolReconciler.mjs +1 -1
  135. package/dist/durable/WorkerAdmission.d.mts +2 -2
  136. package/dist/durable/WorkerAdmission.mjs +1 -1
  137. package/dist/durable/WorkerHost.d.mts +2 -2
  138. package/dist/engine/AgentRuntime.d.mts +1 -1
  139. package/dist/engine/AgentRuntime.mjs +1 -1
  140. package/dist/index.d.mts +14 -14
  141. package/dist/index.mjs +12 -12
  142. package/dist/{run-context-reader-CIdG5tdJ.d.mts → run-context-reader-DEU4REfD.d.mts} +3 -3
  143. package/package.json +1 -1
  144. package/src/capabilities/Subagent.ts +83 -28
  145. package/src/capabilities/SubagentReservations.ts +4 -1
  146. package/src/capabilities/internal/subagent-contract.ts +23 -3
  147. package/src/durable/DurableAgentRuntime.ts +24 -10
  148. package/src/durable/Records.ts +21 -15
  149. package/src/durable/RunContinuation.ts +54 -125
  150. package/src/durable/RunJournal.ts +4 -12
  151. package/src/durable/internal/history-prompt.ts +10 -4
  152. package/src/durable/internal/initial-context.ts +1 -1
  153. package/src/durable/internal/prompt-history-cache.ts +8 -63
  154. package/src/engine/internal/agent-runtime.ts +379 -381
  155. package/dist/AgentRuntime-1OjlOCa9.mjs.map +0 -1
  156. package/dist/DurableAgentRuntime-D2I_T2Yt.mjs.map +0 -1
  157. package/dist/Records-CZRedCE7.mjs.map +0 -1
  158. package/dist/RunContinuation-DWiO2K9D.mjs.map +0 -1
  159. package/dist/RunJournal-oJLo-pjS.mjs.map +0 -1
  160. package/dist/Subagent-BRiDyUS4.mjs.map +0 -1
  161. package/dist/json-BlJVQZxN.mjs +0 -15
  162. package/dist/json-BlJVQZxN.mjs.map +0 -1
@@ -1029,22 +1029,20 @@ const inspectModelResponsePartCapacity = (
1029
1029
  part: unknown,
1030
1030
  limits: EffectiveRunBufferLimits,
1031
1031
  knownSafePrototypes: ReadonlySet<object> = knownSafeModelResponsePrototypes,
1032
- ): Effect.Effect<number, ModelProtocolError> =>
1033
- Effect.suspend(() => {
1034
- const bytes = boundedValueFootprint(
1035
- part,
1036
- limits.maxModelResponseBytes - usage.responsePartBytes,
1037
- knownSafePrototypes,
1038
- );
1032
+ ): number | ModelProtocolError => {
1033
+ const bytes = boundedValueFootprint(
1034
+ part,
1035
+ limits.maxModelResponseBytes - usage.responsePartBytes,
1036
+ knownSafePrototypes,
1037
+ );
1039
1038
 
1040
- return bytes === undefined
1041
- ? Effect.fail(
1042
- ModelProtocolError.make({
1043
- message: `Model response exceeded the ${limits.maxModelResponseBytes}-byte retained response limit`,
1044
- }),
1045
- )
1046
- : Effect.succeed(bytes);
1047
- });
1039
+ return (
1040
+ bytes ??
1041
+ ModelProtocolError.make({
1042
+ message: `Model response exceeded the ${limits.maxModelResponseBytes}-byte retained response limit`,
1043
+ })
1044
+ );
1045
+ };
1048
1046
 
1049
1047
  /**
1050
1048
  * LanguageModel validates encoded parameters even with automatic resolution disabled.
@@ -1137,7 +1135,7 @@ const modelResponseParsersFor = (toolkit: Toolkit.Any) => {
1137
1135
  return parsers;
1138
1136
  };
1139
1137
 
1140
- const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1138
+ const captureModelResponsePartGeneral = Effect.fnUntracedEager(function* <
1141
1139
  Tools extends Record<string, Tool.Any>,
1142
1140
  >(
1143
1141
  part: unknown,
@@ -1164,19 +1162,23 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1164
1162
  if (directInputBytes === undefined) {
1165
1163
  const preflight = schemaClassToolPartPreflight(part, toolkit);
1166
1164
 
1167
- yield* inspectModelResponsePartCapacity(usage, preflight ?? part, limits);
1165
+ const capacity = inspectModelResponsePartCapacity(usage, preflight ?? part, limits);
1166
+
1167
+ if (typeof capacity !== "number") return yield* capacity;
1168
1168
  }
1169
1169
  const parsers = modelResponseParsersFor(toolkit);
1170
1170
 
1171
1171
  const encoded = yield* parsers
1172
1172
  .encode(part)
1173
1173
  .pipe(
1174
- Effect.mapError(() =>
1174
+ Effect.mapErrorEager(() =>
1175
1175
  ModelProtocolError.make({ message: "Model response part failed canonical encoding" }),
1176
1176
  ),
1177
1177
  );
1178
1178
 
1179
- const retainedBytes = yield* inspectModelResponsePartCapacity(usage, encoded, limits);
1179
+ const retainedBytes = inspectModelResponsePartCapacity(usage, encoded, limits);
1180
+
1181
+ if (typeof retainedBytes !== "number") return yield* retainedBytes;
1180
1182
 
1181
1183
  if (typeof structuredCloneFunction !== "function") {
1182
1184
  return yield* ModelProtocolError.make({
@@ -1184,17 +1186,15 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1184
1186
  });
1185
1187
  }
1186
1188
 
1187
- const ownedEncoded = yield* Effect.try({
1188
- try: () => {
1189
- const cloned: unknown = Reflect.apply(structuredCloneFunction, globalThis, [encoded]);
1189
+ let ownedEncoded: unknown;
1190
1190
 
1191
- return cloned;
1192
- },
1193
- catch: () =>
1194
- ModelProtocolError.make({
1195
- message: "Model response part could not be converted into engine-owned data",
1196
- }),
1197
- });
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
+ }
1198
1198
 
1199
1199
  return { encodedPart: ownedEncoded, retainedBytes };
1200
1200
  });
@@ -1217,49 +1217,51 @@ const decodePrimitiveModelResponsePart = Schema.decodeUnknownEffect(
1217
1217
  ),
1218
1218
  );
1219
1219
 
1220
- /** 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
+ */
1221
1224
  const ownModelResponsePart = <Tools extends Record<string, Tool.Any>>(
1222
1225
  part: unknown,
1223
1226
  toolkit: Toolkit.Toolkit<Tools>,
1224
1227
  usage: ModelResponseBufferUsage,
1225
1228
  limits: EffectiveRunBufferLimits,
1226
1229
  finished: boolean,
1227
- ): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> =>
1228
- Effect.suspend((): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> => {
1229
- if (finished) {
1230
- return Effect.fail(
1231
- ModelProtocolError.make({
1232
- message: "Model response emitted content after its finish part",
1233
- }),
1234
- );
1235
- }
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
+ }
1236
1238
 
1237
- const primitive =
1238
- usage.responsePartCount < limits.maxModelResponseParts
1239
- ? capturePrimitiveTextPart(part, limits.maxModelResponseBytes - usage.responsePartBytes)
1240
- : 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
+ }
1241
1253
 
1242
- // Complete primitive snapshots need validation, but no defaults or transformation.
1243
- // Schema.toType retains every constraint from the native Response part schemas.
1244
- if (primitive !== undefined) {
1245
- 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), {
1246
1259
  onFailure: () =>
1247
1260
  ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1248
- onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: primitive.retainedBytes }),
1249
- });
1250
- }
1251
-
1252
- // Pending application codecs keep normal Effect execution in this same consumer fiber.
1253
- return Effect.flatMapEager(
1254
- captureModelResponsePartGeneral(part, toolkit, usage, limits),
1255
- (captured) =>
1256
- Effect.mapBothEager(modelResponseParsersFor(toolkit).decode(captured.encodedPart), {
1257
- onFailure: () =>
1258
- ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1259
- onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: captured.retainedBytes }),
1260
- }),
1261
- );
1262
- });
1261
+ onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: captured.retainedBytes }),
1262
+ }),
1263
+ );
1264
+ };
1263
1265
 
1264
1266
  type PartLifecycle = "open" | "closed";
1265
1267
 
@@ -1316,10 +1318,12 @@ const endPart = (
1316
1318
  parts: Map<string, PartLifecycle>,
1317
1319
  id: string,
1318
1320
  description: string,
1319
- ): Effect.Effect<void, ModelProtocolError> =>
1320
- continuePart(parts, id, `${description} end`).pipe(
1321
- Effect.tap(() => Effect.sync(() => parts.set(id, "closed"))),
1322
- );
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
+ };
1323
1327
 
1324
1328
  const firstOpenPart = (trace: TurnTrace): string | undefined => {
1325
1329
  for (const [id, state] of trace.textParts) {
@@ -3358,9 +3362,13 @@ const decodeUsageTotal = Schema.decodeEffect(Schema.Natural);
3358
3362
  const decodeProviderUsage = Schema.decodeEffect(ProviderUsage);
3359
3363
 
3360
3364
  const decodeProviderUsageTotal = (value: number): Effect.Effect<number, ModelProtocolError> =>
3361
- decodeUsageTotal(value).pipe(Effect.mapError(() => invalidProviderUsage()));
3365
+ decodeUsageTotal(value).pipe(Effect.mapErrorEager(() => invalidProviderUsage()));
3362
3366
 
3363
- const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>(
3367
+ const consumeUsage = Effect.fnUntracedEager(function* <
3368
+ AgentValue extends Agent.Any,
3369
+ HookError,
3370
+ HookRequirements,
3371
+ >(
3364
3372
  agent: AgentValue,
3365
3373
  context: RunContext,
3366
3374
  usage: Response.Usage | undefined,
@@ -3371,337 +3379,332 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3371
3379
  RunCostEstimateRequest,
3372
3380
  "response" | "finishMetadata" | "purpose" | "webSearchCalls"
3373
3381
  > = {},
3374
- ): Effect.Effect<
3382
+ ): Effect.fn.Return<
3375
3383
  ConsumedUsage,
3376
3384
  AgentPolicyError | ModelProtocolError | HookError,
3377
3385
  HookRequirements | Model.ProviderName | Model.ModelName
3378
- > =>
3379
- Effect.gen(function* () {
3380
- if (usage === undefined) {
3381
- return yield* AgentPolicyError.make({
3382
- limit: "usage",
3383
- message: "A completed model response did not report usage",
3384
- });
3385
- }
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
+ }
3386
3393
 
3387
- const webSearchCalls = yield* decodeProviderUsageTotal(response.webSearchCalls ?? 0);
3394
+ const webSearchCalls = yield* decodeProviderUsageTotal(response.webSearchCalls ?? 0);
3388
3395
 
3389
- const providerUsage = yield* decodeProviderUsage(usage).pipe(
3390
- Effect.mapError(() => invalidProviderUsage()),
3391
- );
3396
+ const providerUsage = yield* decodeProviderUsage(usage).pipe(
3397
+ Effect.mapErrorEager(() => invalidProviderUsage()),
3398
+ );
3392
3399
 
3393
- const reportedUncached = providerUsage.inputTokens.uncached ?? 0;
3394
- const cacheRead = providerUsage.inputTokens.cacheRead ?? 0;
3395
- const cacheWrite = providerUsage.inputTokens.cacheWrite ?? 0;
3396
- const reportedText = providerUsage.outputTokens.text ?? 0;
3397
- const reasoning = providerUsage.outputTokens.reasoning ?? 0;
3398
- // Gross token accounting never discounts cache activity. An omitted
3399
- // aggregate is derived from present components; an omitted component may
3400
- // receive the aggregate remainder. Explicitly contradictory fields fail.
3401
- // Some providers include cache writes in `uncached` while also reporting
3402
- // them separately. Separate that overlap before constructing the canonical,
3403
- // additive input components, but retain the raw provider usage for pricing.
3404
- const reportedInputTotal = providerUsage.inputTokens.total;
3405
- const reportedInputWithoutWrite = reportedUncached + cacheRead;
3406
-
3407
- const cacheWriteOverlapsUncached =
3408
- reportedInputTotal !== undefined &&
3409
- providerUsage.inputTokens.uncached !== undefined &&
3410
- providerUsage.inputTokens.cacheWrite !== undefined &&
3411
- cacheWrite <= reportedUncached &&
3412
- reportedInputWithoutWrite <= reportedInputTotal &&
3413
- cacheWrite > reportedInputTotal - reportedInputWithoutWrite;
3414
-
3415
- const disjointReportedUncached =
3416
- reportedUncached - (cacheWriteOverlapsUncached ? cacheWrite : 0);
3417
-
3418
- const reportedInputComponents = disjointReportedUncached + cacheRead + cacheWrite;
3419
-
3420
- const allInputComponentsReported =
3421
- providerUsage.inputTokens.uncached !== undefined &&
3422
- providerUsage.inputTokens.cacheRead !== undefined &&
3423
- 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;
3424
3430
 
3425
- if (
3426
- reportedInputTotal !== undefined &&
3427
- (reportedInputTotal < reportedInputComponents ||
3428
- (allInputComponentsReported && reportedInputTotal !== reportedInputComponents))
3429
- ) {
3430
- return yield* invalidProviderUsage();
3431
- }
3432
- const inputTokens = reportedInputTotal ?? reportedInputComponents;
3433
- const reportedOutputComponents = reportedText + reasoning;
3434
- 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;
3435
3441
 
3436
- const allOutputComponentsReported =
3437
- providerUsage.outputTokens.text !== undefined &&
3438
- providerUsage.outputTokens.reasoning !== undefined;
3442
+ const allOutputComponentsReported =
3443
+ providerUsage.outputTokens.text !== undefined &&
3444
+ providerUsage.outputTokens.reasoning !== undefined;
3439
3445
 
3440
- if (
3441
- reportedOutputTotal !== undefined &&
3442
- (reportedOutputTotal < reportedOutputComponents ||
3443
- (allOutputComponentsReported && reportedOutputTotal !== reportedOutputComponents))
3444
- ) {
3445
- return yield* invalidProviderUsage();
3446
- }
3447
- const outputTokens = reportedOutputTotal ?? reportedOutputComponents;
3448
- // When a provider reports only aggregates, classify the unexplained input
3449
- // conservatively as uncached and non-reasoning output as text when those
3450
- // fields are absent. If either was explicit, assign the remainder to the
3451
- // first genuinely omitted component so no provider-supplied value changes.
3452
- const inputRemainder = inputTokens - reportedInputComponents;
3453
- const outputRemainder = outputTokens - reportedOutputComponents;
3454
-
3455
- const uncached =
3456
- disjointReportedUncached +
3457
- (providerUsage.inputTokens.uncached === undefined ? inputRemainder : 0);
3458
-
3459
- const normalizedCacheRead =
3460
- cacheRead +
3461
- (providerUsage.inputTokens.uncached !== undefined &&
3462
- providerUsage.inputTokens.cacheRead === undefined
3463
- ? inputRemainder
3464
- : 0);
3465
-
3466
- const normalizedCacheWrite =
3467
- cacheWrite +
3468
- (providerUsage.inputTokens.uncached !== undefined &&
3469
- providerUsage.inputTokens.cacheRead !== undefined &&
3470
- providerUsage.inputTokens.cacheWrite === undefined
3471
- ? inputRemainder
3472
- : 0);
3473
-
3474
- const text =
3475
- reportedText + (providerUsage.outputTokens.text === undefined ? outputRemainder : 0);
3476
-
3477
- const normalizedReasoning =
3478
- reasoning +
3479
- (providerUsage.outputTokens.text !== undefined &&
3480
- providerUsage.outputTokens.reasoning === undefined
3481
- ? outputRemainder
3482
- : 0);
3483
-
3484
- // Validate the complete additive components before pricing or staging usage.
3485
- const inputUsage = yield* InputTokenUsage.makeEffect({
3486
- total: inputTokens,
3487
- uncached,
3488
- cacheRead: normalizedCacheRead,
3489
- cacheWrite: normalizedCacheWrite,
3490
- }).pipe(Effect.mapError(() => invalidProviderUsage()));
3491
-
3492
- const outputUsage = yield* OutputTokenUsage.makeEffect({
3493
- total: outputTokens,
3494
- text,
3495
- reasoning: normalizedReasoning,
3496
- }).pipe(Effect.mapError(() => invalidProviderUsage()));
3497
-
3498
- const totalTokens = yield* decodeProviderUsageTotal(inputTokens + outputTokens);
3499
- const provider = yield* Model.ProviderName;
3500
- const model = yield* Model.ModelName;
3501
-
3502
- const estimate =
3503
- options.estimateCostMicrousd === undefined
3504
- ? 0
3505
- : yield* options.estimateCostMicrousd(usage, {
3506
- provider,
3507
- model,
3508
- usage,
3509
- ...response,
3510
- webSearchCalls,
3511
- });
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;
3512
3506
 
3513
- 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
+ });
3514
3517
 
3515
- if (!Number.isSafeInteger(costMicrousd) || costMicrousd < 0) {
3516
- return yield* AgentPolicyError.make({
3517
- limit: "cost",
3518
- message: "Model cost estimation must produce a non-negative integer number of microdollars",
3519
- });
3520
- }
3521
- const serviceTier = typeof estimate === "number" ? undefined : estimate.serviceTier;
3522
- const pricingVersion = typeof estimate === "number" ? undefined : estimate.pricingVersion;
3518
+ const costMicrousd = typeof estimate === "number" ? estimate : estimate.costMicrousd;
3523
3519
 
3524
- const pricingStatus =
3525
- options.estimateCostMicrousd === undefined
3526
- ? "unknown"
3527
- : typeof estimate === "number"
3528
- ? "estimated"
3529
- : (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;
3530
3528
 
3531
- const validPricingIdentity = (value: string | undefined): boolean =>
3532
- 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");
3533
3535
 
3534
- if (!validPricingIdentity(serviceTier) || !validPricingIdentity(pricingVersion)) {
3535
- return yield* AgentPolicyError.make({
3536
- limit: "cost",
3537
- message: "Model cost estimation returned an invalid service tier or pricing version",
3538
- });
3539
- }
3536
+ const validPricingIdentity = (value: string | undefined): boolean =>
3537
+ value === undefined || (value.length > 0 && value.length <= 256);
3540
3538
 
3541
- const modelUsage = ModelCallUsage.make({
3542
- webSearchCalls,
3543
- provider,
3544
- model,
3545
- ...(response.response === undefined
3546
- ? {}
3547
- : { response: ModelResponseIdentity.make(response.response) }),
3548
- purpose: response.purpose ?? "turn",
3549
- usageStatus:
3550
- Object.values(providerUsage.inputTokens).every((value) => value === undefined) &&
3551
- Object.values(providerUsage.outputTokens).every((value) => value === undefined)
3552
- ? "unknown"
3553
- : [
3554
- providerUsage.inputTokens.uncached,
3555
- providerUsage.inputTokens.cacheRead,
3556
- providerUsage.inputTokens.cacheWrite,
3557
- providerUsage.outputTokens.text,
3558
- providerUsage.outputTokens.reasoning,
3559
- ].every((value) => value !== undefined)
3560
- ? "complete"
3561
- : "partial",
3562
- pricingStatus,
3563
- ...(serviceTier === undefined ? {} : { serviceTier }),
3564
- ...(pricingVersion === undefined ? {} : { pricingVersion }),
3565
- inputTokens: inputUsage,
3566
- outputTokens: outputUsage,
3567
- 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",
3568
3543
  });
3544
+ }
3569
3545
 
3570
- 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
+ });
3571
3574
 
3572
- const cumulativeWebSearchCalls =
3573
- context.webSearchCalls === undefined
3574
- ? undefined
3575
- : yield* decodeProviderUsageTotal(context.webSearchCalls + webSearchCalls);
3575
+ const modelCalls = yield* decodeProviderUsageTotal(context.modelCalls + 1);
3576
3576
 
3577
- const cumulativeInputTokens = yield* decodeProviderUsageTotal(
3578
- context.inputTokens + inputTokens,
3579
- );
3577
+ const cumulativeWebSearchCalls =
3578
+ context.webSearchCalls === undefined
3579
+ ? undefined
3580
+ : yield* decodeProviderUsageTotal(context.webSearchCalls + webSearchCalls);
3580
3581
 
3581
- const cumulativeOutputTokens = yield* decodeProviderUsageTotal(
3582
- context.outputTokens + outputTokens,
3583
- );
3582
+ const cumulativeInputTokens = yield* decodeProviderUsageTotal(context.inputTokens + inputTokens);
3584
3583
 
3585
- const cumulativeCostMicrousd = context.costMicrousd + costMicrousd;
3584
+ const cumulativeOutputTokens = yield* decodeProviderUsageTotal(
3585
+ context.outputTokens + outputTokens,
3586
+ );
3586
3587
 
3587
- if (!Number.isSafeInteger(cumulativeCostMicrousd)) {
3588
- return yield* AgentPolicyError.make({
3589
- limit: "cost",
3590
- message: "Cumulative model cost exceeds safe-integer accounting capacity",
3591
- });
3592
- }
3593
- const firstObserved = context.modelCalls === 0 && context.unobservedModelCalls === 0;
3588
+ const cumulativeCostMicrousd = context.costMicrousd + costMicrousd;
3594
3589
 
3595
- const combineStatus = (
3596
- prior: typeof UsageCompleteness.Type,
3597
- next: typeof UsageCompleteness.Type,
3598
- ) => (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;
3599
3597
 
3600
- context.usageStatus = combineStatus(context.usageStatus, modelUsage.usageStatus ?? "unknown");
3601
- context.pricingStatus = combineStatus(
3602
- context.pricingStatus,
3603
- pricingStatus === "estimated" ? "complete" : "unknown",
3604
- );
3605
- context.modelCalls = modelCalls;
3606
- context.webSearchCalls = cumulativeWebSearchCalls;
3607
- context.inputTokens = cumulativeInputTokens;
3608
- context.outputTokens = cumulativeOutputTokens;
3609
- context.lastInputTokens = inputTokens;
3610
- context.lastOutputTokens = outputTokens;
3611
- context.costMicrousd = cumulativeCostMicrousd;
3612
- context.lastCostMicrousd = costMicrousd;
3613
-
3614
- if (options.durability !== undefined) {
3615
- // Stage before enforcing hard rails: a response that spends past the
3616
- // cost/token budget still becomes auditable in its canonical Turn and
3617
- // terminal settlement.
3618
- yield* options.durability.noteTurnUsage({ turn, usage: modelUsage });
3619
- }
3598
+ const combineStatus = (
3599
+ prior: typeof UsageCompleteness.Type,
3600
+ next: typeof UsageCompleteness.Type,
3601
+ ) => (firstObserved && prior === "complete" ? next : prior === next ? prior : "partial");
3620
3602
 
3621
- const policy = agent.definition.policy;
3622
- const consumedTokens = context.inputTokens + context.outputTokens;
3623
- const tokenBudget = policy.tokenBudget;
3624
- 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
+ }
3625
3623
 
3626
- if (
3627
- !context.finalizing &&
3628
- !context.tokenExhausted &&
3629
- tokenBudget !== undefined &&
3630
- consumedTokens > tokenBudget
3631
- ) {
3632
- breach = AgentPolicyError.make({
3633
- limit: "tokens",
3634
- 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",
3635
3658
  });
3636
- // Fail mode rejects before any declared application Handler starts.
3637
- if (policy.onExhaustion === "fail") {
3638
- return yield* breach;
3639
- }
3640
- // One-shot (RUN-025): the flag joins the final-answer derivation so the
3641
- // grace Turn's own usage accumulates and charges without re-breaching.
3642
- context.tokenExhausted = true;
3643
- context.exhaustedDimension ??= "tokens";
3644
3659
  }
3645
- // Cost is an unconditional hard rail (runtime spec §3): it is enforced on
3646
- // every response, including one that just soft-breached the token budget —
3647
- // a simultaneous breach fails typed instead of soft-landing on overspend.
3648
- const costBudget = policy.costBudgetMicrousd;
3649
-
3650
- if (costBudget !== undefined) {
3651
- if (options.estimateCostMicrousd === undefined) {
3652
- return yield* AgentPolicyError.make({
3653
- limit: "cost",
3654
- message: "Agent cost budget requires a model cost estimator",
3655
- });
3656
- }
3657
- if (pricingStatus === "unknown") {
3658
- return yield* AgentPolicyError.make({
3659
- limit: "cost",
3660
- message: "Agent cost budget requires a known model cost estimate",
3661
- });
3662
- }
3663
- if (context.costMicrousd > costBudget) {
3664
- // Cost is spend, not context: it never earns a grace Turn.
3665
- return yield* AgentPolicyError.make({
3666
- limit: "cost",
3667
- message: `Agent exceeded its ${costBudget} microdollar cost budget`,
3668
- });
3669
- }
3660
+ if (pricingStatus === "unknown") {
3661
+ return yield* AgentPolicyError.make({
3662
+ limit: "cost",
3663
+ message: "Agent cost budget requires a known model cost estimate",
3664
+ });
3670
3665
  }
3671
- if (options.budget !== undefined) {
3672
- const delta: RunUsageDelta = {
3673
- modelCalls: 1,
3674
- inputTokens,
3675
- outputTokens,
3676
- totalTokens,
3677
- toolCalls: toolCallCount,
3678
- costMicrousd,
3679
- usage,
3680
- modelUsage,
3681
- };
3682
-
3683
- 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
+ });
3684
3672
  }
3685
- 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
+ };
3686
3685
 
3687
- if (
3688
- tokenBudget !== undefined &&
3689
- !context.warnedLimits.has("tokens") &&
3690
- nearingLimit(consumedTokens, tokenBudget)
3691
- ) {
3692
- context.warnedLimits.add("tokens");
3693
- warnings?.push(
3694
- BudgetWarning.make({
3695
- ...(yield* eventBase(context)),
3696
- limit: "tokens",
3697
- consumed: consumedTokens,
3698
- limitValue: tokenBudget,
3699
- }),
3700
- );
3701
- }
3686
+ yield* options.budget.consume(delta);
3687
+ }
3688
+ const warnings: Array<RunEvent> | undefined = context.publish === undefined ? undefined : [];
3702
3689
 
3703
- return { breach, warnings: warnings ?? noEvents, modelUsage };
3704
- });
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
+ });
3705
3708
 
3706
3709
  // `Effect.fnUntraced`: this helper runs for every streamed Response Part, so a
3707
3710
  // named span here would emit one span per TextDelta/ReasoningDelta. Spans stay
@@ -5774,13 +5777,6 @@ const makeTurn = <
5774
5777
 
5775
5778
  const started = Effect.gen(function* () {
5776
5779
  yield* Metric.update(modelCounter, 1);
5777
- yield* Effect.logDebug("agent model call started").pipe(
5778
- Effect.annotateLogs({
5779
- agentId: context.agentId,
5780
- runId: context.runId,
5781
- turnId,
5782
- }),
5783
- );
5784
5780
 
5785
5781
  if (context.publish === undefined) return;
5786
5782
 
@@ -5800,7 +5796,7 @@ const makeTurn = <
5800
5796
  ]);
5801
5797
 
5802
5798
  return;
5803
- }).pipe(Effect.withLogSpan("AgentRuntime.model"));
5799
+ });
5804
5800
 
5805
5801
  // Final-answer mode (RUN-018/RUN-019, extended to tokens by RUN-025):
5806
5802
  // once the Turn, Tool Call, or token budget is
@@ -6557,8 +6553,8 @@ const makeTurn = <
6557
6553
  activeProvisionalText = provisionalText;
6558
6554
  }),
6559
6555
  ),
6560
- Stream.runForEachArray((parts) =>
6561
- Effect.gen(function* () {
6556
+ Stream.runForEachArray(
6557
+ Effect.fnUntracedEager(function* (parts) {
6562
6558
  for (const part of parts) {
6563
6559
  const owned = yield* ownModelResponsePart(
6564
6560
  part,
@@ -8876,13 +8872,10 @@ function executeWithCompletion<
8876
8872
 
8877
8873
  const started = Effect.gen(function* () {
8878
8874
  yield* Metric.update(runCounter, 1);
8879
- yield* Effect.logDebug("agent run started").pipe(
8880
- Effect.annotateLogs({ agentId: context.agentId, runId: context.runId }),
8881
- );
8882
8875
  yield* publishEvent(context, () =>
8883
8876
  eventBase(context).pipe(Effect.map((base) => RunStarted.make(base))),
8884
8877
  );
8885
- }).pipe(Effect.withLogSpan("AgentRuntime.run"));
8878
+ });
8886
8879
 
8887
8880
  const execution = Effect.flatten(
8888
8881
  Effect.gen(function* () {
@@ -9193,6 +9186,7 @@ function executeWithCompletion<
9193
9186
  history,
9194
9187
  preparation,
9195
9188
  agent.definition.policy,
9189
+ DateTime.makeUnsafe(context.durationDeadlineMillis),
9196
9190
  options.subagentGrant,
9197
9191
  options.subagentBudget,
9198
9192
  options.subagentBudgetScope,
@@ -11128,6 +11122,8 @@ const spawnWithParent = (
11128
11122
  export interface AgentSpawnerService {
11129
11123
  /** Resolved defaults for this parent Run; never includes its tools or handlers. */
11130
11124
  readonly policy: AgentPolicy;
11125
+ /** Effective deadline of this active parent execution, including RunOptions.durationDeadline. */
11126
+ readonly durationDeadline: DateTime.Utc;
11131
11127
  readonly depth: number;
11132
11128
  readonly grant?: SubagentGrant;
11133
11129
  readonly budget?: SubagentBudgetReservation;
@@ -11236,6 +11232,7 @@ const makeAgentSpawner = (
11236
11232
  history: ThreadHistory["Service"],
11237
11233
  preparation: RunContextPreparation["Service"],
11238
11234
  policy: AgentPolicy,
11235
+ durationDeadline: DateTime.Utc,
11239
11236
  grant?: SubagentGrant,
11240
11237
  budget?: SubagentBudgetReservation,
11241
11238
  budgetScope?: WorkerBudgetScope,
@@ -11245,6 +11242,7 @@ const makeAgentSpawner = (
11245
11242
  ...(budget === undefined ? {} : { budget }),
11246
11243
  ...(budgetScope === undefined ? {} : { budgetScope }),
11247
11244
  policy,
11245
+ durationDeadline,
11248
11246
  depth,
11249
11247
  parent,
11250
11248
  spawn: spawnWithParent(parent, depth, history, preparation, onChild),