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

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-BHmp2V9p.d.mts → Admin-DTDihBra.d.mts} +50 -13
  2. package/dist/{AgentRuntime-BD3ZHAMq.d.mts → AgentRuntime-CHDyRbSf.d.mts} +3 -3
  3. package/dist/{AgentRuntime-B6qZlD3f.mjs → AgentRuntime-DaDKMasp.mjs} +123 -114
  4. package/dist/AgentRuntime-DaDKMasp.mjs.map +1 -0
  5. package/dist/{Digest-EpeqWEdv.mjs → Digest-C3EODOee.mjs} +2 -2
  6. package/dist/{Digest-EpeqWEdv.mjs.map → Digest-C3EODOee.mjs.map} +1 -1
  7. package/dist/{DurableAgentRuntime-CBBjRNJN.mjs → DurableAgentRuntime-BJbuIqQq.mjs} +237 -114
  8. package/dist/DurableAgentRuntime-BJbuIqQq.mjs.map +1 -0
  9. package/dist/{EventSource-C9aaJaXT.d.mts → EventSource-Cc58uEso.d.mts} +2 -2
  10. package/dist/InMemory.d.mts +2 -2
  11. package/dist/{MessageDelivery-BGDm-c46.d.mts → MessageDelivery-ClHg7c0O.d.mts} +42 -42
  12. package/dist/{MessagingHost-CRUK4JBi.mjs → MessagingHost-CZCyrnvJ.mjs} +27 -26
  13. package/dist/MessagingHost-CZCyrnvJ.mjs.map +1 -0
  14. package/dist/{PreparedInputAdmission-BvDqlgiP.d.mts → PreparedInputAdmission-CjcX1OP0.d.mts} +3 -3
  15. package/dist/{Records-BiuJDWNe.mjs → Records-DzL4Kbtw.mjs} +2 -2
  16. package/dist/{Records-BiuJDWNe.mjs.map → Records-DzL4Kbtw.mjs.map} +1 -1
  17. package/dist/{RunContinuation-4ZATcVLB.mjs → RunContinuation-CWHuciup.mjs} +74 -40
  18. package/dist/RunContinuation-CWHuciup.mjs.map +1 -0
  19. package/dist/{RunContinuation-CTJ1jGrO.d.mts → RunContinuation-DSnUTSSb.d.mts} +11 -5
  20. package/dist/{RunJournal-CNZqA3Ge.mjs → RunJournal-DGblbFas.mjs} +86 -81
  21. package/dist/RunJournal-DGblbFas.mjs.map +1 -0
  22. package/dist/{RunStorage-BHjuuERI.mjs → RunStorage-BF2YgWkg.mjs} +44 -33
  23. package/dist/RunStorage-BF2YgWkg.mjs.map +1 -0
  24. package/dist/{Scheduling-NG8slKdI.mjs → Scheduling-07_dsGED.mjs} +3 -3
  25. package/dist/{Scheduling-NG8slKdI.mjs.map → Scheduling-07_dsGED.mjs.map} +1 -1
  26. package/dist/{Subagent-CTIpnzlB.mjs → Subagent-CexndZSp.mjs} +2 -2
  27. package/dist/{Subagent-CTIpnzlB.mjs.map → Subagent-CexndZSp.mjs.map} +1 -1
  28. package/dist/{Subagent-DKhZA72u.d.mts → Subagent-xhN_aD1r.d.mts} +3 -3
  29. package/dist/{Subscription-BNg9VCRl.d.mts → Subscription-DuC_PcGx.d.mts} +2 -2
  30. package/dist/{SubscriptionInput-BUoXv-_K.d.mts → SubscriptionInput-B1IeAEDi.d.mts} +2 -2
  31. package/dist/{Thread-dk-QiNml.d.mts → Thread-DzdJAY4J.d.mts} +4 -3
  32. package/dist/{ThreadHistory-ByxS8HU1.d.mts → ThreadHistory-KPPBJLJb.d.mts} +12 -2
  33. package/dist/{ThreadImport-CMgzttvr.d.mts → ThreadImport-D_Biid6w.d.mts} +42 -42
  34. package/dist/{ThreadImport-CodYoJoa.mjs → ThreadImport-ZgWxf7bB.mjs} +8 -8
  35. package/dist/{ThreadImport-CodYoJoa.mjs.map → ThreadImport-ZgWxf7bB.mjs.map} +1 -1
  36. package/dist/{ThreadStore-59uDGXul.mjs → ThreadStore-BC4V-mY-.mjs} +42 -23
  37. package/dist/ThreadStore-BC4V-mY-.mjs.map +1 -0
  38. package/dist/{ThreadTransfer-3jigUl3g.mjs → ThreadTransfer-S7FnTRHt.mjs} +4 -4
  39. package/dist/{ThreadTransfer-3jigUl3g.mjs.map → ThreadTransfer-S7FnTRHt.mjs.map} +1 -1
  40. package/dist/{WorkerAdmission-DDYJdOsU.mjs → WorkerAdmission-C47Vy_cK.mjs} +5 -5
  41. package/dist/{WorkerAdmission-DDYJdOsU.mjs.map → WorkerAdmission-C47Vy_cK.mjs.map} +1 -1
  42. package/dist/{agent-registration-DRbyhAu-.mjs → agent-registration-a6QYaG3h.mjs} +4 -4
  43. package/dist/{agent-registration-DRbyhAu-.mjs.map → agent-registration-a6QYaG3h.mjs.map} +1 -1
  44. package/dist/capabilities/Messaging.mjs +1 -1
  45. package/dist/capabilities/ModelContext.d.mts +1 -1
  46. package/dist/capabilities/RunHooks.d.mts +1 -1
  47. package/dist/capabilities/Subagent.d.mts +1 -1
  48. package/dist/capabilities/Subagent.mjs +1 -1
  49. package/dist/core/Thread.d.mts +2 -2
  50. package/dist/core/Thread.mjs +5 -3
  51. package/dist/core/Thread.mjs.map +1 -1
  52. package/dist/durable/ActivityStore.mjs +1 -1
  53. package/dist/durable/Admin.d.mts +1 -1
  54. package/dist/durable/Admin.mjs +1 -1
  55. package/dist/durable/AgentRegistration.d.mts +1 -1
  56. package/dist/durable/AgentRegistration.mjs +1 -1
  57. package/dist/durable/Certification.d.mts +1 -1
  58. package/dist/durable/CommittedActivity.d.mts +1 -1
  59. package/dist/durable/CommittedActivity.mjs +3 -3
  60. package/dist/durable/Digest.mjs +1 -1
  61. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  62. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  63. package/dist/durable/EventSource.d.mts +1 -1
  64. package/dist/durable/EventSource.mjs +1 -1
  65. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  66. package/dist/durable/LifecyclePublication.d.mts +1 -1
  67. package/dist/durable/LifecyclePublication.mjs +2 -2
  68. package/dist/durable/MessageDelivery.d.mts +1 -1
  69. package/dist/durable/MessageDelivery.mjs +2 -2
  70. package/dist/durable/MessageDeliveryStoreConformance.d.mts +40 -40
  71. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  72. package/dist/durable/PersistentHistory.d.mts +2 -2
  73. package/dist/durable/PersistentHistory.mjs +4 -4
  74. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  75. package/dist/durable/RecordFormat.mjs +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 +2 -2
  80. package/dist/durable/RunContinuation.mjs +3 -3
  81. package/dist/durable/RunJournal.d.mts +2 -2
  82. package/dist/durable/RunJournal.mjs +2 -2
  83. package/dist/durable/RunStorage.d.mts +1 -1
  84. package/dist/durable/RunStorage.mjs +1 -1
  85. package/dist/durable/Schedule.mjs +1 -1
  86. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  87. package/dist/durable/ScheduleTransition.mjs +1 -1
  88. package/dist/durable/Scheduling.d.mts +1 -1
  89. package/dist/durable/Scheduling.mjs +1 -1
  90. package/dist/durable/SettlementPublisher.d.mts +1 -1
  91. package/dist/durable/SettlementPublisher.mjs +3 -3
  92. package/dist/durable/SqlMemoryStore.mjs +5 -3
  93. package/dist/durable/SqlMemoryStore.mjs.map +1 -1
  94. package/dist/durable/SubmissionLedger.d.mts +1 -1
  95. package/dist/durable/SubmissionLedger.mjs +7 -3
  96. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  97. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  98. package/dist/durable/SubmissionLedgerConformance.mjs +6 -6
  99. package/dist/durable/SubmissionStatus.d.mts +1 -1
  100. package/dist/durable/Subscription.d.mts +1 -1
  101. package/dist/durable/Subscription.mjs +2 -2
  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 +2 -2
  107. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  108. package/dist/durable/SubscriptionTransition.mjs +1 -1
  109. package/dist/durable/Subscriptions.d.mts +5 -5
  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.mjs +1 -1
  116. package/dist/durable/ThreadImport.d.mts +1 -1
  117. package/dist/durable/ThreadImport.mjs +1 -1
  118. package/dist/durable/ThreadInvariants.d.mts +3 -3
  119. package/dist/durable/ThreadInvariants.mjs +4 -4
  120. package/dist/durable/ThreadProjection.mjs +1 -1
  121. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  122. package/dist/durable/ThreadReader.d.mts +1 -1
  123. package/dist/durable/ThreadReader.mjs +1 -1
  124. package/dist/durable/ThreadStore.d.mts +1 -1
  125. package/dist/durable/ThreadStore.mjs +2 -2
  126. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  127. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  128. package/dist/durable/ThreadTransfer.d.mts +1 -1
  129. package/dist/durable/ThreadTransfer.mjs +1 -1
  130. package/dist/durable/ThreadWork.d.mts +2 -2
  131. package/dist/durable/ThreadWork.mjs +12 -6
  132. package/dist/durable/ThreadWork.mjs.map +1 -1
  133. package/dist/durable/ToolReconciler.mjs +1 -1
  134. package/dist/durable/WakeScheduler.mjs +23 -39
  135. package/dist/durable/WakeScheduler.mjs.map +1 -1
  136. package/dist/durable/WorkerAdmission.d.mts +68 -68
  137. package/dist/durable/WorkerAdmission.mjs +1 -1
  138. package/dist/durable/WorkerHost.d.mts +1 -1
  139. package/dist/engine/AgentRuntime.d.mts +1 -1
  140. package/dist/engine/AgentRuntime.mjs +1 -1
  141. package/dist/engine/MessagingHost.mjs +1 -1
  142. package/dist/engine/Output.mjs +1 -1
  143. package/dist/engine/ThreadHistory.d.mts +1 -1
  144. package/dist/engine/ThreadHistory.mjs +3 -2
  145. package/dist/engine/ThreadHistory.mjs.map +1 -1
  146. package/dist/index.d.mts +12 -12
  147. package/dist/index.mjs +13 -13
  148. package/dist/{output-contract-CMIhu4Cw.mjs → output-contract-DCnLYCdp.mjs} +12 -15
  149. package/dist/{output-contract-CMIhu4Cw.mjs.map → output-contract-DCnLYCdp.mjs.map} +1 -1
  150. package/dist/{run-context-reader-CC7ZD95F.d.mts → run-context-reader-DvTEq2RC.d.mts} +2 -2
  151. package/package.json +1 -1
  152. package/src/core/Thread.ts +143 -146
  153. package/src/durable/DurableAgentRuntime.ts +549 -260
  154. package/src/durable/RunContinuation.ts +115 -64
  155. package/src/durable/RunJournal.ts +107 -112
  156. package/src/durable/RunStorage.ts +90 -61
  157. package/src/durable/SqlMemoryStore.ts +8 -6
  158. package/src/durable/SubmissionLedger.ts +15 -0
  159. package/src/durable/ThreadStore.ts +48 -26
  160. package/src/durable/ThreadWork.ts +10 -0
  161. package/src/durable/WakeScheduler.ts +34 -66
  162. package/src/durable/internal/initial-context.ts +17 -2
  163. package/src/durable/internal/prompt-history-cache.ts +13 -8
  164. package/src/durable/internal/usage-totals.ts +28 -31
  165. package/src/engine/MessagingHost.ts +4 -2
  166. package/src/engine/ThreadHistory.ts +47 -48
  167. package/src/engine/internal/agent-runtime.ts +234 -202
  168. package/src/engine/internal/output-contract.ts +15 -15
  169. package/src/engine/internal/primitive-delta.ts +2 -3
  170. package/src/engine/internal/tool-telemetry.ts +3 -5
  171. package/dist/AgentRuntime-B6qZlD3f.mjs.map +0 -1
  172. package/dist/DurableAgentRuntime-CBBjRNJN.mjs.map +0 -1
  173. package/dist/MessagingHost-CRUK4JBi.mjs.map +0 -1
  174. package/dist/RunContinuation-4ZATcVLB.mjs.map +0 -1
  175. package/dist/RunJournal-CNZqA3Ge.mjs.map +0 -1
  176. package/dist/RunStorage-BHjuuERI.mjs.map +0 -1
  177. package/dist/ThreadStore-59uDGXul.mjs.map +0 -1
@@ -763,8 +763,8 @@ const acceptTurnCommit = <E, R>(
763
763
  Effect.andThen(() => durability?.commitTurn(commit) ?? Effect.succeed("committed" as const)),
764
764
  );
765
765
 
766
- return accepted.pipe(
767
- Effect.flatMap((receipt) => {
766
+ return Effect.onExitPrimitive(
767
+ Effect.flatMap(accepted, (receipt) => {
768
768
  if (receipt === "deferred")
769
769
  return commit._tag === "Response" && commit.defer === true
770
770
  ? Effect.void
@@ -774,21 +774,19 @@ const acceptTurnCommit = <E, R>(
774
774
  }),
775
775
  );
776
776
 
777
- return Effect.sync(() => {
778
- if (commit._tag !== "Partial") {
779
- if (commit.response !== undefined) {
780
- context.pendingCommitInputs.splice(0, trace.commitInputCount ?? 0);
781
- trace.responseCommitted = true;
782
- }
783
- if (commit._tag === "Settled") trace.resultsCommitted = true;
777
+ if (commit._tag !== "Partial") {
778
+ if (commit.response !== undefined) {
779
+ context.pendingCommitInputs.splice(0, trace.commitInputCount ?? 0);
780
+ trace.responseCommitted = true;
784
781
  }
785
- });
782
+ if (commit._tag === "Settled") trace.resultsCommitted = true;
783
+ }
784
+
785
+ return Effect.void;
786
786
  }),
787
- Effect.onExit((exit) =>
788
- Effect.sync(() => {
789
- if (Exit.isFailure(exit)) trace.commitFailed = true;
790
- }),
791
- ),
787
+ (exit) => {
788
+ if (Exit.isFailure(exit)) trace.commitFailed = true;
789
+ },
792
790
  );
793
791
  });
794
792
 
@@ -1190,53 +1188,67 @@ const captureModelResponsePartGeneral = Effect.fnUntraced(function* <
1190
1188
  return { encodedPart: ownedEncoded, retainedBytes };
1191
1189
  });
1192
1190
 
1193
- const captureModelResponsePart = <Tools extends Record<string, Tool.Any>>(
1194
- part: unknown,
1195
- toolkit: Toolkit.Toolkit<Tools>,
1196
- usage: ModelResponseBufferUsage,
1197
- limits: EffectiveRunBufferLimits,
1198
- ): ReturnType<typeof captureModelResponsePartGeneral> =>
1199
- Effect.suspend((): ReturnType<typeof captureModelResponsePartGeneral> => {
1200
- const primitive =
1201
- usage.responsePartCount < limits.maxModelResponseParts
1202
- ? capturePrimitiveTextPart(part, limits.maxModelResponseBytes - usage.responsePartBytes)
1203
- : undefined;
1204
-
1205
- return primitive === undefined
1206
- ? captureModelResponsePartGeneral(part, toolkit, usage, limits)
1207
- : Effect.succeed(primitive);
1208
- });
1209
-
1210
1191
  interface OwnedModelResponsePart {
1211
1192
  readonly ownedPart: Response.AnyPart;
1212
1193
  readonly retainedBytes: number;
1213
1194
  }
1214
1195
 
1196
+ const decodePrimitiveModelResponsePart = Schema.decodeUnknownEffect(
1197
+ Schema.toType(
1198
+ Schema.Union([
1199
+ Response.TextStartPart,
1200
+ Response.TextDeltaPart,
1201
+ Response.TextEndPart,
1202
+ Response.ReasoningStartPart,
1203
+ Response.ReasoningDeltaPart,
1204
+ Response.ReasoningEndPart,
1205
+ ]),
1206
+ ),
1207
+ );
1208
+
1215
1209
  /** Reject trailing content before its capture can run an application codec or suspend. */
1216
- const ownModelResponsePart = Effect.fnUntraced(function* <Tools extends Record<string, Tool.Any>>(
1210
+ const ownModelResponsePart = <Tools extends Record<string, Tool.Any>>(
1217
1211
  part: unknown,
1218
1212
  toolkit: Toolkit.Toolkit<Tools>,
1219
1213
  usage: ModelResponseBufferUsage,
1220
1214
  limits: EffectiveRunBufferLimits,
1221
1215
  finished: boolean,
1222
- ): Effect.fn.Return<OwnedModelResponsePart, ModelProtocolError> {
1223
- if (finished) {
1224
- return yield* ModelProtocolError.make({
1225
- message: "Model response emitted content after its finish part",
1226
- });
1227
- }
1228
- const captured = yield* captureModelResponsePart(part, toolkit, usage, limits);
1216
+ ): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> =>
1217
+ Effect.suspend((): Effect.Effect<OwnedModelResponsePart, ModelProtocolError> => {
1218
+ if (finished) {
1219
+ return Effect.fail(
1220
+ ModelProtocolError.make({
1221
+ message: "Model response emitted content after its finish part",
1222
+ }),
1223
+ );
1224
+ }
1229
1225
 
1230
- const ownedPart = yield* modelResponseParsersFor(toolkit)
1231
- .decode(captured.encodedPart)
1232
- .pipe(
1233
- Effect.mapError(() =>
1234
- ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1235
- ),
1236
- );
1226
+ const primitive =
1227
+ usage.responsePartCount < limits.maxModelResponseParts
1228
+ ? capturePrimitiveTextPart(part, limits.maxModelResponseBytes - usage.responsePartBytes)
1229
+ : undefined;
1237
1230
 
1238
- return { ownedPart, retainedBytes: captured.retainedBytes };
1239
- });
1231
+ // Complete primitive snapshots need validation, but no defaults or transformation.
1232
+ // Schema.toType retains every constraint from the native Response part schemas.
1233
+ if (primitive !== undefined) {
1234
+ return Effect.mapBothEager(decodePrimitiveModelResponsePart(primitive.part), {
1235
+ onFailure: () =>
1236
+ ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1237
+ onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: primitive.retainedBytes }),
1238
+ });
1239
+ }
1240
+
1241
+ // Pending application codecs keep normal Effect execution in this same consumer fiber.
1242
+ return Effect.flatMapEager(
1243
+ captureModelResponsePartGeneral(part, toolkit, usage, limits),
1244
+ (captured) =>
1245
+ Effect.mapBothEager(modelResponseParsersFor(toolkit).decode(captured.encodedPart), {
1246
+ onFailure: () =>
1247
+ ModelProtocolError.make({ message: "Model response part failed canonical decoding" }),
1248
+ onSuccess: (ownedPart) => ({ ownedPart, retainedBytes: captured.retainedBytes }),
1249
+ }),
1250
+ );
1251
+ });
1240
1252
 
1241
1253
  type PartLifecycle = "open" | "closed";
1242
1254
 
@@ -2166,6 +2178,7 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2166
2178
  | AiError.AiError
2167
2179
  | Tool.HandlerError<ToolUnion<Tools>>;
2168
2180
  const call = prepared.call;
2181
+ const toolCallId = prepared.toolCallId;
2169
2182
  const observer = context.toolFailureObserver;
2170
2183
  const telemetryToolCallId = isTelemetryToolCallId(call.id) ? call.id : undefined;
2171
2184
  const executionClass = getToolExecutionClass(prepared.tool);
@@ -2205,8 +2218,6 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2205
2218
  const started = Result.isFailure(prepared.validation)
2206
2219
  ? Effect.void
2207
2220
  : Effect.gen(function* () {
2208
- const toolCallId = yield* decodeToolCallId(call.id);
2209
-
2210
2221
  yield* Effect.logDebug("agent tool handler started").pipe(
2211
2222
  Effect.annotateLogs({
2212
2223
  agentId: context.agentId,
@@ -2265,7 +2276,6 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2265
2276
  return yield* ModelProtocolError.make({
2266
2277
  message: `Tool Call ${call.id} produced more than one terminal result`,
2267
2278
  });
2268
- const toolCallId = yield* decodeToolCallId(call.id);
2269
2279
  const encodedResult = toolResultForJson(prepared.tool, result);
2270
2280
 
2271
2281
  if (result.preliminary) {
@@ -2302,7 +2312,6 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2302
2312
  message: `Tool Call ${call.id} completed without a terminal result`,
2303
2313
  });
2304
2314
  const result = terminalResult;
2305
- const toolCallId = yield* decodeToolCallId(call.id);
2306
2315
  let toolSelection: Selection | undefined;
2307
2316
 
2308
2317
  if (!result.isFailure && Context.get(prepared.tool.annotations, DiscoveryTool)) {
@@ -2318,7 +2327,7 @@ const executePreparedToolCall = Effect.fnUntraced(function* <
2318
2327
  context.toolSchemaTransformer,
2319
2328
  );
2320
2329
  }
2321
- const encodedResult = boundEncodedToolResult(result.encodedResult, resultBounds);
2330
+ const encodedResult = yield* boundEncodedToolResult(result.encodedResult, resultBounds);
2322
2331
 
2323
2332
  // The ordinary JSON boundary remains required; only the public Class projection is optional.
2324
2333
  const successResult = result.isFailure
@@ -2882,12 +2891,26 @@ const executeToolBatch = Effect.fnUntraced(function* <
2882
2891
  ),
2883
2892
  { concurrency: group.length === 1 ? 1 : "unbounded", discard: true },
2884
2893
  );
2885
- yield* checkpointExecution(context, durability);
2894
+ // Each following call checks before dispatch; the batch owner checks again
2895
+ // after the final group, its cleanup, and the optional budget guard.
2886
2896
  }
2887
2897
  }).pipe(
2888
- Effect.provideService(RunEventSink, batchSink),
2889
- Effect.provideService(Emitter, updateEmitter),
2890
- Effect.provideService(SubagentDurability, batchSubagentDurability),
2898
+ Effect.updateContext(
2899
+ (
2900
+ services: Context.Context<
2901
+ | HookRequirements
2902
+ | ToolSpanTelemetry
2903
+ | ProgrammaticToolAuthorization
2904
+ | Scope.Scope
2905
+ | Tool.HandlerServices<ToolUnion<Tools>>
2906
+ >,
2907
+ ) =>
2908
+ services.pipe(
2909
+ Context.add(SubagentDurability, batchSubagentDurability),
2910
+ Context.add(Emitter, updateEmitter),
2911
+ Context.add(RunEventSink, batchSink),
2912
+ ),
2913
+ ),
2891
2914
  Effect.ensuring(
2892
2915
  Effect.sync(() => {
2893
2916
  updatesOpen = false;
@@ -3144,13 +3167,17 @@ const toolResultForJson = (tool: Tool.Any, result: Tool.HandlerResult<Tool.Any>)
3144
3167
  return result.encodedResult;
3145
3168
  };
3146
3169
 
3170
+ const decodeBoundedToolResult = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown));
3171
+
3147
3172
  /**
3148
3173
  * RUN-022: bound one application Tool result at the settle seam. The bounded
3149
3174
  * value is what official history, the settled record, and the success event
3150
- * all carry, so every downstream surface agrees; a within-bounds result
3151
- * passes through with its identity preserved.
3175
+ * all carry, so every downstream surface agrees on the measured representation.
3152
3176
  */
3153
- const boundEncodedToolResult = (encodedResult: unknown, bounds: ToolResultBounds): unknown => {
3177
+ const boundEncodedToolResult = (
3178
+ encodedResult: unknown,
3179
+ bounds: ToolResultBounds,
3180
+ ): Effect.Effect<unknown> => {
3154
3181
  let text: string | undefined;
3155
3182
 
3156
3183
  try {
@@ -3158,10 +3185,10 @@ const boundEncodedToolResult = (encodedResult: unknown, bounds: ToolResultBounds
3158
3185
  } catch (cause) {
3159
3186
  // Fail closed (runtime spec §9): an unserializable result is unbounded by
3160
3187
  // construction, so the sentinel replaces it before history or records.
3161
- return unserializableToolResult(cause);
3188
+ return Effect.succeed(unserializableToolResult(cause));
3162
3189
  }
3163
3190
  if (text === undefined) {
3164
- return unserializableToolResult("the encoded result is not a JSON value");
3191
+ return Effect.succeed(unserializableToolResult("the encoded result is not a JSON value"));
3165
3192
  }
3166
3193
  const bounded = applyToolResultBounds(text, bounds);
3167
3194
 
@@ -3169,12 +3196,8 @@ const boundEncodedToolResult = (encodedResult: unknown, bounds: ToolResultBounds
3169
3196
  // truncated and unmodified cases: returning the original object would let a
3170
3197
  // stateful `toJSON` pass the byte check small and expand or throw on later
3171
3198
  // canonical serialization, carrying unchecked state past the boundary.
3172
- try {
3173
- return Schema.decodeSync(Schema.fromJsonString(Schema.Unknown))(bounded);
3174
- } catch (cause) {
3175
- // Defensive only: `applyToolResultBounds` always emits valid JSON.
3176
- return unserializableToolResult(cause);
3177
- }
3199
+ // Stay in the caller's Effect; a synchronous decoder would start another runtime fiber.
3200
+ return decodeBoundedToolResult(bounded).pipe(Effect.orElseSucceed(unserializableToolResult));
3178
3201
  };
3179
3202
 
3180
3203
  const durableResultBounds = <E, R>(
@@ -3363,7 +3386,7 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3363
3386
  // them separately. Separate that overlap before constructing the canonical,
3364
3387
  // additive input components, but retain the raw provider usage for pricing.
3365
3388
  const reportedInputTotal = providerUsage.inputTokens.total;
3366
- const reportedInputWithoutWrite = yield* decodeProviderUsageTotal(reportedUncached + cacheRead);
3389
+ const reportedInputWithoutWrite = reportedUncached + cacheRead;
3367
3390
 
3368
3391
  const cacheWriteOverlapsUncached =
3369
3392
  reportedInputTotal !== undefined &&
@@ -3376,9 +3399,7 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3376
3399
  const disjointReportedUncached =
3377
3400
  reportedUncached - (cacheWriteOverlapsUncached ? cacheWrite : 0);
3378
3401
 
3379
- const reportedInputComponents = yield* decodeProviderUsageTotal(
3380
- disjointReportedUncached + cacheRead + cacheWrite,
3381
- );
3402
+ const reportedInputComponents = disjointReportedUncached + cacheRead + cacheWrite;
3382
3403
 
3383
3404
  const allInputComponentsReported =
3384
3405
  providerUsage.inputTokens.uncached !== undefined &&
@@ -3393,7 +3414,7 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3393
3414
  return yield* invalidProviderUsage();
3394
3415
  }
3395
3416
  const inputTokens = reportedInputTotal ?? reportedInputComponents;
3396
- const reportedOutputComponents = yield* decodeProviderUsageTotal(reportedText + reasoning);
3417
+ const reportedOutputComponents = reportedText + reasoning;
3397
3418
  const reportedOutputTotal = providerUsage.outputTokens.total;
3398
3419
 
3399
3420
  const allOutputComponentsReported =
@@ -3444,6 +3465,20 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3444
3465
  ? outputRemainder
3445
3466
  : 0);
3446
3467
 
3468
+ // Validate the complete additive components before pricing or staging usage.
3469
+ const inputUsage = yield* InputTokenUsage.makeEffect({
3470
+ total: inputTokens,
3471
+ uncached,
3472
+ cacheRead: normalizedCacheRead,
3473
+ cacheWrite: normalizedCacheWrite,
3474
+ }).pipe(Effect.mapError(() => invalidProviderUsage()));
3475
+
3476
+ const outputUsage = yield* OutputTokenUsage.makeEffect({
3477
+ total: outputTokens,
3478
+ text,
3479
+ reasoning: normalizedReasoning,
3480
+ }).pipe(Effect.mapError(() => invalidProviderUsage()));
3481
+
3447
3482
  const totalTokens = yield* decodeProviderUsageTotal(inputTokens + outputTokens);
3448
3483
  const provider = yield* Model.ProviderName;
3449
3484
  const model = yield* Model.ModelName;
@@ -3511,17 +3546,8 @@ const consumeUsage = <AgentValue extends Agent.Any, HookError, HookRequirements>
3511
3546
  pricingStatus,
3512
3547
  ...(serviceTier === undefined ? {} : { serviceTier }),
3513
3548
  ...(pricingVersion === undefined ? {} : { pricingVersion }),
3514
- inputTokens: InputTokenUsage.make({
3515
- total: inputTokens,
3516
- uncached,
3517
- cacheRead: normalizedCacheRead,
3518
- cacheWrite: normalizedCacheWrite,
3519
- }),
3520
- outputTokens: OutputTokenUsage.make({
3521
- total: outputTokens,
3522
- text,
3523
- reasoning: normalizedReasoning,
3524
- }),
3549
+ inputTokens: inputUsage,
3550
+ outputTokens: outputUsage,
3525
3551
  costMicrousd,
3526
3552
  });
3527
3553
 
@@ -4605,30 +4631,26 @@ const decodeToolCallId = (id: string) =>
4605
4631
  );
4606
4632
 
4607
4633
  const decodeProviderToolCallId = (id: string) =>
4608
- Effect.suspend(() =>
4609
- decodeProviderToolCallIdValue(id).pipe(
4610
- Effect.mapError(() =>
4611
- ModelProtocolError.make({
4612
- message:
4613
- "Model supplied an invalid Tool Call ID; expected 1-128 ASCII letters, digits, dots, underscores, colons, or hyphens",
4614
- }),
4615
- ),
4634
+ decodeProviderToolCallIdValue(id).pipe(
4635
+ Effect.mapErrorEager(() =>
4636
+ ModelProtocolError.make({
4637
+ message:
4638
+ "Model supplied an invalid Tool Call ID; expected 1-128 ASCII letters, digits, dots, underscores, colons, or hyphens",
4639
+ }),
4616
4640
  ),
4617
4641
  );
4618
4642
 
4619
4643
  const decodeProviderResponsePartId = (id: string) =>
4620
- Effect.suspend(() =>
4621
- decodeProviderResponsePartIdValue(id).pipe(
4622
- Effect.mapError(() =>
4623
- ModelProtocolError.make({
4624
- message:
4625
- "Model supplied an invalid response part ID; expected 1-128 ASCII letters, digits, dots, underscores, colons, or hyphens",
4626
- }),
4627
- ),
4644
+ decodeProviderResponsePartIdValue(id).pipe(
4645
+ Effect.mapErrorEager(() =>
4646
+ ModelProtocolError.make({
4647
+ message:
4648
+ "Model supplied an invalid response part ID; expected 1-128 ASCII letters, digits, dots, underscores, colons, or hyphens",
4649
+ }),
4628
4650
  ),
4629
4651
  );
4630
4652
 
4631
- const validateProviderPartIdentifiers = Effect.fnUntraced(function* (part: Response.AnyPart) {
4653
+ const validateProviderPartIdentifiers = Effect.fnUntracedEager(function* (part: Response.AnyPart) {
4632
4654
  switch (part.type) {
4633
4655
  case "text-start":
4634
4656
  case "text-delta":
@@ -4761,10 +4783,10 @@ const promptFromTurnParts = (trace: TurnTrace): Prompt.Prompt => {
4761
4783
  return Prompt.fromMessages(messages);
4762
4784
  };
4763
4785
 
4764
- // `Effect.fnUntraced`: this dispatcher runs for every streamed Response Part,
4765
- // so a named span here would emit one span per TextDelta/ReasoningDelta. The
4766
- // enclosing model request keeps its `AgentRuntime.model` stream span.
4767
- const processModelPart = Effect.fnUntraced(function* <Tools extends Record<string, Tool.Any>>(
4786
+ // Yield the returned Effect immediately and exactly once from the executing stream
4787
+ // consumer: construction runs the synchronous prefix, mutating trace counters and maps.
4788
+ // Application codecs and events still suspend. The enclosing model request owns the span.
4789
+ const processModelPart = Effect.fnUntracedEager(function* <Tools extends Record<string, Tool.Any>>(
4768
4790
  context: RunContext,
4769
4791
  turnId: TurnId,
4770
4792
  turn: number,
@@ -6385,7 +6407,7 @@ const makeTurn = <
6385
6407
  policy,
6386
6408
  context,
6387
6409
  prepareModelPrompt(
6388
- Prompt.fromMessages([...basis.content, ...transientContext.content]),
6410
+ Prompt.concat(basis, transientContext),
6389
6411
  outputContract._tag === "rendered" ? outputContract.part : undefined,
6390
6412
  systemMessagesInHistory,
6391
6413
  staticInstructions,
@@ -6519,40 +6541,42 @@ const makeTurn = <
6519
6541
  activeProvisionalText = provisionalText;
6520
6542
  }),
6521
6543
  ),
6522
- Stream.runForEach((part) =>
6544
+ Stream.runForEachArray((parts) =>
6523
6545
  Effect.gen(function* () {
6524
- const owned = yield* ownModelResponsePart(
6525
- part,
6526
- agent.definition.toolkit,
6527
- trace,
6528
- context.bufferLimits,
6529
- trace.finished,
6530
- );
6531
-
6532
- const events = context.publish === undefined ? undefined : [];
6533
-
6534
- yield* processModelPart(
6535
- context,
6536
- turnId,
6537
- turn,
6538
- agent.definition.toolkit.tools,
6539
- trace,
6540
- owned.ownedPart,
6541
- owned.retainedBytes,
6542
- events,
6543
- );
6544
- if (provisionalText !== undefined) {
6545
- const part = owned.ownedPart;
6546
-
6547
- if (
6548
- part.type === "text-start" ||
6549
- part.type === "text-delta" ||
6550
- part.type === "text-end"
6551
- )
6552
- provisionalText.offerUnsafe(part);
6546
+ for (const part of parts) {
6547
+ const owned = yield* ownModelResponsePart(
6548
+ part,
6549
+ agent.definition.toolkit,
6550
+ trace,
6551
+ context.bufferLimits,
6552
+ trace.finished,
6553
+ );
6554
+
6555
+ const events = context.publish === undefined ? undefined : [];
6556
+
6557
+ yield* processModelPart(
6558
+ context,
6559
+ turnId,
6560
+ turn,
6561
+ agent.definition.toolkit.tools,
6562
+ trace,
6563
+ owned.ownedPart,
6564
+ owned.retainedBytes,
6565
+ events,
6566
+ );
6567
+ if (provisionalText !== undefined) {
6568
+ const part = owned.ownedPart;
6569
+
6570
+ if (
6571
+ part.type === "text-start" ||
6572
+ part.type === "text-delta" ||
6573
+ part.type === "text-end"
6574
+ )
6575
+ provisionalText.offerUnsafe(part);
6576
+ }
6577
+ // Publish before accepting the next part. Backpressure stays interruptible.
6578
+ if (events !== undefined) yield* publishEvents(context, events);
6553
6579
  }
6554
- // Publish before accepting the next part. Backpressure stays interruptible.
6555
- if (events !== undefined) yield* publishEvents(context, events);
6556
6580
  }),
6557
6581
  ),
6558
6582
  <Value, Error, Requirements>(
@@ -6575,64 +6599,67 @@ const makeTurn = <
6575
6599
  // Retry the closed native provider operation. Admitted content makes the response
6576
6600
  // ineligible for retry; canonical acceptance belongs to the continuation below.
6577
6601
  const attemptWithRetries = Effect.suspend(() => attempt(compactedOutgoing())).pipe(
6578
- Effect.retry(($) =>
6579
- $(Schedule.exponential(MODEL_RETRY_BASE)).pipe(
6580
- Schedule.upTo({ times: policy.modelRetries ?? 0 }),
6581
- Schedule.while(({ input: error }) => {
6582
- const lastPart = trace.parts.at(-1);
6583
- const providerError: unknown = error;
6584
-
6585
- return (
6586
- canRepeatModelCall &&
6587
- !hasStreamedContent(trace) &&
6588
- ((lastPart?.type === "error" && isTransientErrorPayload(lastPart.error)) ||
6589
- (AiError.isAiError(providerError) && providerError.isRetryable))
6590
- );
6591
- }),
6592
- Schedule.modifyDelay(({ input: error, duration }) =>
6593
- Effect.gen(function* () {
6594
- const backoff = Duration.min(duration, MODEL_RETRY_MAX_DELAY);
6595
- const providerError: unknown = error;
6602
+ (operation) =>
6603
+ (policy.modelRetries ?? 0) === 0
6604
+ ? operation
6605
+ : Effect.retry(operation, ($) =>
6606
+ $(Schedule.exponential(MODEL_RETRY_BASE)).pipe(
6607
+ Schedule.upTo({ times: policy.modelRetries ?? 0 }),
6608
+ Schedule.while(({ input: error }) => {
6609
+ const lastPart = trace.parts.at(-1);
6610
+ const providerError: unknown = error;
6611
+
6612
+ return (
6613
+ canRepeatModelCall &&
6614
+ !hasStreamedContent(trace) &&
6615
+ ((lastPart?.type === "error" && isTransientErrorPayload(lastPart.error)) ||
6616
+ (AiError.isAiError(providerError) && providerError.isRetryable))
6617
+ );
6618
+ }),
6619
+ Schedule.modifyDelay(({ input: error, duration }) =>
6620
+ Effect.gen(function* () {
6621
+ const backoff = Duration.min(duration, MODEL_RETRY_MAX_DELAY);
6622
+ const providerError: unknown = error;
6596
6623
 
6597
- const retryAfter = AiError.isAiError(providerError)
6598
- ? providerError.retryAfter
6599
- : undefined;
6624
+ const retryAfter = AiError.isAiError(providerError)
6625
+ ? providerError.retryAfter
6626
+ : undefined;
6600
6627
 
6601
- const requested =
6602
- retryAfter === undefined ? backoff : Duration.max(backoff, retryAfter);
6628
+ const requested =
6629
+ retryAfter === undefined ? backoff : Duration.max(backoff, retryAfter);
6603
6630
 
6604
- // Expiration is rejected by the next native attempt. Capping only this delay
6605
- // keeps failed-attempt accounting and canonical hooks outside interruption timers.
6606
- const remaining = Math.max(
6607
- 0,
6608
- context.durationDeadlineMillis - (yield* Clock.currentTimeMillis),
6609
- );
6631
+ // Expiration is rejected by the next native attempt. Capping only this delay
6632
+ // keeps failed-attempt accounting and canonical hooks outside interruption timers.
6633
+ const remaining = Math.max(
6634
+ 0,
6635
+ context.durationDeadlineMillis - (yield* Clock.currentTimeMillis),
6636
+ );
6610
6637
 
6611
- return Duration.min(requested, Duration.millis(remaining));
6612
- }),
6613
- ),
6614
- Schedule.tap(({ input: error, attempt: retry, duration }) =>
6615
- Effect.gen(function* () {
6616
- trace.parts.length = 0;
6617
- trace.responsePartCount = 0;
6618
- trace.responsePartBytes = 0;
6619
- // The failed attempt's usage was already retained on exit.
6620
- trace.usage = undefined;
6621
- delete trace.response;
6622
- delete trace.finishMetadata;
6623
- yield* Effect.logWarning("agent model call retrying").pipe(
6624
- Effect.annotateLogs({
6625
- runId: context.runId,
6626
- turnId,
6627
- retry,
6628
- delayMillis: Duration.toMillis(duration),
6629
- error: errorMessage(error),
6630
- }),
6631
- );
6632
- }),
6633
- ),
6634
- ),
6635
- ),
6638
+ return Duration.min(requested, Duration.millis(remaining));
6639
+ }),
6640
+ ),
6641
+ Schedule.tap(({ input: error, attempt: retry, duration }) =>
6642
+ Effect.gen(function* () {
6643
+ trace.parts.length = 0;
6644
+ trace.responsePartCount = 0;
6645
+ trace.responsePartBytes = 0;
6646
+ // The failed attempt's usage was already retained on exit.
6647
+ trace.usage = undefined;
6648
+ delete trace.response;
6649
+ delete trace.finishMetadata;
6650
+ yield* Effect.logWarning("agent model call retrying").pipe(
6651
+ Effect.annotateLogs({
6652
+ runId: context.runId,
6653
+ turnId,
6654
+ retry,
6655
+ delayMillis: Duration.toMillis(duration),
6656
+ error: errorMessage(error),
6657
+ }),
6658
+ );
6659
+ }),
6660
+ ),
6661
+ ),
6662
+ ),
6636
6663
  );
6637
6664
 
6638
6665
  // RUN-027: one summarize-and-retry for a classified provider context
@@ -7934,7 +7961,7 @@ const makeResumeTurn = <
7934
7961
  id: call.id,
7935
7962
  isFailure: true,
7936
7963
  result: yield* decodeEventJson(
7937
- boundEncodedToolResult(
7964
+ yield* boundEncodedToolResult(
7938
7965
  rejection.error,
7939
7966
  durableResultBounds(agent.definition.policy.toolResultBounds, options, turn),
7940
7967
  ),
@@ -8578,10 +8605,9 @@ function executeWithCompletion<
8578
8605
 
8579
8606
  // Normalize the selected host policy at the Run boundary. Inner calls project typed
8580
8607
  // override failures into broker outcomes; native calls retain their original E channel.
8581
- const programmaticAuthorization = Layer.effect(
8582
- ProgrammaticToolAuthorization,
8583
- Effect.gen(function* () {
8584
- const services = yield* Effect.context<HookRequirements>();
8608
+ const programmaticAuthorization = Effect.map(
8609
+ Effect.context<HookRequirements>(),
8610
+ (services) => {
8585
8611
  const selected = options.toolAuthorization;
8586
8612
 
8587
8613
  return ProgrammaticToolAuthorization.of({
@@ -8594,7 +8620,7 @@ function executeWithCompletion<
8594
8620
  ),
8595
8621
  ),
8596
8622
  });
8597
- }),
8623
+ },
8598
8624
  );
8599
8625
 
8600
8626
  const interpreted = Effect.flatten(
@@ -9006,8 +9032,13 @@ function executeWithCompletion<
9006
9032
  ),
9007
9033
  );
9008
9034
 
9009
- yield* checkpointExecution(context, options.durability);
9010
- if (output._tag !== "NextTurn") return output;
9035
+ if (output._tag !== "NextTurn") {
9036
+ yield* checkpointExecution(context, options.durability);
9037
+
9038
+ return output;
9039
+ }
9040
+ // Both Turn entry points check before any hook or dispatch. The
9041
+ // preceding Scope has closed, so that check also sees finalizer failures.
9011
9042
  request = output;
9012
9043
  }
9013
9044
 
@@ -9254,7 +9285,8 @@ function executeWithCompletion<
9254
9285
  const events = modeled.pipe(
9255
9286
  // The engine composition boundary owns span-lifecycle isolation while preserving the host's
9256
9287
  // ambient Tracer/Logger configuration. Individual Tool executions consume this capability.
9257
- Effect.provide(ToolSpanTelemetry.layer.pipe(Layer.provideMerge(programmaticAuthorization))),
9288
+ Effect.provideServiceEffect(ToolSpanTelemetry, ToolSpanTelemetry.make),
9289
+ Effect.provideServiceEffect(ProgrammaticToolAuthorization, programmaticAuthorization),
9258
9290
  );
9259
9291
 
9260
9292
  // Closing execution first joins every owned child and finalizer before validation,