@yielded/agent 0.1.0-beta.173 → 0.1.0-beta.175

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 (266) hide show
  1. package/dist/{Admin-BX9ns28E.d.mts → Admin-Qa3I1oA5.d.mts} +61 -21
  2. package/dist/{Agent-9aBoIi74.d.mts → Agent-B39jRnFv.d.mts} +2 -2
  3. package/dist/{Agent-0WvBWWbM.mjs → Agent-BiRbr73L.mjs} +7 -6
  4. package/dist/Agent-BiRbr73L.mjs.map +1 -0
  5. package/dist/{AgentRuntime-BfP14534.d.mts → AgentRuntime-CHDyRbSf.d.mts} +12 -12
  6. package/dist/{AgentRuntime-CQ-qcETl.mjs → AgentRuntime-DaDKMasp.mjs} +290 -273
  7. package/dist/AgentRuntime-DaDKMasp.mjs.map +1 -0
  8. package/dist/{Digest-DCuPlhNk.mjs → Digest-C3EODOee.mjs} +42 -15
  9. package/dist/Digest-C3EODOee.mjs.map +1 -0
  10. package/dist/{DurableAgentRuntime-BAmSkWYr.mjs → DurableAgentRuntime-CPlw5_7e.mjs} +1557 -1458
  11. package/dist/DurableAgentRuntime-CPlw5_7e.mjs.map +1 -0
  12. package/dist/{EventSource-BwliwQz9.d.mts → EventSource-BTu68yDW.d.mts} +2 -2
  13. package/dist/InMemory.d.mts +2 -2
  14. package/dist/{MessageDelivery-B943ebI_.d.mts → MessageDelivery-DjAZJ4Fd.d.mts} +42 -42
  15. package/dist/{Messaging-C_6OOg_s.d.mts → Messaging-SVu6rWV8.d.mts} +12 -6
  16. package/dist/{MessagingHost-B8oKM7AC.d.mts → MessagingHost-C6SOAcoG.d.mts} +2 -2
  17. package/dist/{MessagingHost-CRUK4JBi.mjs → MessagingHost-CZCyrnvJ.mjs} +27 -26
  18. package/dist/MessagingHost-CZCyrnvJ.mjs.map +1 -0
  19. package/dist/{Output-Dsx6k52t.d.mts → Output-CRamN1Fy.d.mts} +2 -2
  20. package/dist/{PreparedInputAdmission-CxSJSiTl.d.mts → PreparedInputAdmission-wgY4JMZY.d.mts} +3 -3
  21. package/dist/{Records-BiuJDWNe.mjs → Records-DzL4Kbtw.mjs} +2 -2
  22. package/dist/{Records-BiuJDWNe.mjs.map → Records-DzL4Kbtw.mjs.map} +1 -1
  23. package/dist/{RunContinuation-FHM7XPjq.d.mts → RunContinuation-3jB5pULC.d.mts} +13 -7
  24. package/dist/{RunContinuation-DHYNAQFi.mjs → RunContinuation-BiyKJK32.mjs} +173 -82
  25. package/dist/RunContinuation-BiyKJK32.mjs.map +1 -0
  26. package/dist/{RunJournal-kfKVxJj7.mjs → RunJournal-CPMKcfd0.mjs} +106 -73
  27. package/dist/RunJournal-CPMKcfd0.mjs.map +1 -0
  28. package/dist/{RunOptions-D8zxFIrZ.d.mts → RunOptions-Be77lKVZ.d.mts} +2 -2
  29. package/dist/{RunStorage-uqPxps09.mjs → RunStorage-CzutHJBE.mjs} +10 -6
  30. package/dist/RunStorage-CzutHJBE.mjs.map +1 -0
  31. package/dist/{Scheduling-CVyfXlBR.mjs → Scheduling-07_dsGED.mjs} +12 -12
  32. package/dist/Scheduling-07_dsGED.mjs.map +1 -0
  33. package/dist/{Subagent-vpffemql.mjs → Subagent-CexndZSp.mjs} +53 -40
  34. package/dist/Subagent-CexndZSp.mjs.map +1 -0
  35. package/dist/{Subagent-E6tvd7-k.d.mts → Subagent-xhN_aD1r.d.mts} +20 -9
  36. package/dist/{SubagentHost-B6w2m-SI.d.mts → SubagentHost-D1xBgn9g.d.mts} +2 -2
  37. package/dist/{Subscription-DONig54Q.d.mts → Subscription-DmIHlOJK.d.mts} +2 -2
  38. package/dist/{SubscriptionInput-mJIRxWJM.d.mts → SubscriptionInput-DgcQhvbe.d.mts} +2 -2
  39. package/dist/{Thread-dk-QiNml.d.mts → Thread-DzdJAY4J.d.mts} +4 -3
  40. package/dist/{ThreadHistory-ByxS8HU1.d.mts → ThreadHistory-KPPBJLJb.d.mts} +12 -2
  41. package/dist/{ThreadImport-DckUMEqJ.mjs → ThreadImport-CjlG57vH.mjs} +9 -9
  42. package/dist/ThreadImport-CjlG57vH.mjs.map +1 -0
  43. package/dist/{ThreadImport-DpHxjBrA.d.mts → ThreadImport-DP-vOk2F.d.mts} +5 -5
  44. package/dist/{ThreadStore-CEekRa80.mjs → ThreadStore-EuUilthA.mjs} +162 -85
  45. package/dist/ThreadStore-EuUilthA.mjs.map +1 -0
  46. package/dist/{ThreadTransfer-B4gTQLIl.mjs → ThreadTransfer-DB_zsrtJ.mjs} +4 -4
  47. package/dist/{ThreadTransfer-B4gTQLIl.mjs.map → ThreadTransfer-DB_zsrtJ.mjs.map} +1 -1
  48. package/dist/{WorkerAdmission-DFVbbVh2.mjs → WorkerAdmission-eBeXt_mu.mjs} +5 -5
  49. package/dist/{WorkerAdmission-DFVbbVh2.mjs.map → WorkerAdmission-eBeXt_mu.mjs.map} +1 -1
  50. package/dist/{agent-registration-ByMZ_cK5.mjs → agent-registration-a6QYaG3h.mjs} +70 -57
  51. package/dist/agent-registration-a6QYaG3h.mjs.map +1 -0
  52. package/dist/capabilities/Approval.mjs +2 -2
  53. package/dist/capabilities/Approval.mjs.map +1 -1
  54. package/dist/capabilities/BrowserUse.d.mts +102 -1867
  55. package/dist/capabilities/BrowserUse.mjs +74 -798
  56. package/dist/capabilities/BrowserUse.mjs.map +1 -1
  57. package/dist/capabilities/Budget.mjs +3 -1
  58. package/dist/capabilities/Budget.mjs.map +1 -1
  59. package/dist/capabilities/CodeMode.d.mts +1 -1
  60. package/dist/capabilities/CodeMode.mjs +16 -9
  61. package/dist/capabilities/CodeMode.mjs.map +1 -1
  62. package/dist/capabilities/Commands.d.mts +1 -1
  63. package/dist/capabilities/McpClient.mjs +1 -1
  64. package/dist/capabilities/McpClient.mjs.map +1 -1
  65. package/dist/capabilities/Messaging.d.mts +1 -1
  66. package/dist/capabilities/Messaging.mjs +5 -5
  67. package/dist/capabilities/Messaging.mjs.map +1 -1
  68. package/dist/capabilities/ModelContext.d.mts +1 -1
  69. package/dist/capabilities/ModelContext.mjs +7 -5
  70. package/dist/capabilities/ModelContext.mjs.map +1 -1
  71. package/dist/capabilities/Redaction.mjs +49 -19
  72. package/dist/capabilities/Redaction.mjs.map +1 -1
  73. package/dist/capabilities/RunHooks.d.mts +2 -2
  74. package/dist/capabilities/RunHooks.mjs +1 -1
  75. package/dist/capabilities/RunHooks.mjs.map +1 -1
  76. package/dist/capabilities/Subagent.d.mts +1 -1
  77. package/dist/capabilities/Subagent.mjs +1 -1
  78. package/dist/capabilities/SubagentReservations.mjs +3 -1
  79. package/dist/capabilities/SubagentReservations.mjs.map +1 -1
  80. package/dist/capabilities/WebCapture.mjs.map +1 -1
  81. package/dist/capabilities/WebSearch.mjs +7 -7
  82. package/dist/capabilities/WebSearch.mjs.map +1 -1
  83. package/dist/core/Agent.d.mts +1 -1
  84. package/dist/core/Agent.mjs +1 -1
  85. package/dist/core/AgentUpdates.mjs +8 -8
  86. package/dist/core/AgentUpdates.mjs.map +1 -1
  87. package/dist/core/Thread.d.mts +2 -2
  88. package/dist/core/Thread.mjs +6 -4
  89. package/dist/core/Thread.mjs.map +1 -1
  90. package/dist/core/Usage.mjs +2 -2
  91. package/dist/core/Usage.mjs.map +1 -1
  92. package/dist/durable/ActivityStore.mjs +1 -1
  93. package/dist/durable/Admin.d.mts +1 -1
  94. package/dist/durable/Admin.mjs +1 -1
  95. package/dist/durable/AgentRegistration.d.mts +1 -1
  96. package/dist/durable/AgentRegistration.mjs +1 -1
  97. package/dist/durable/Certification.d.mts +1 -1
  98. package/dist/durable/CommittedActivity.d.mts +1 -1
  99. package/dist/durable/CommittedActivity.mjs +3 -3
  100. package/dist/durable/Digest.mjs +1 -1
  101. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  102. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  103. package/dist/durable/EventSource.d.mts +1 -1
  104. package/dist/durable/EventSource.mjs +7 -7
  105. package/dist/durable/EventSource.mjs.map +1 -1
  106. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  107. package/dist/durable/LifecyclePublication.d.mts +1 -1
  108. package/dist/durable/LifecyclePublication.mjs +2 -2
  109. package/dist/durable/MessageDelivery.d.mts +1 -1
  110. package/dist/durable/MessageDelivery.mjs +2 -2
  111. package/dist/durable/MessageDeliveryStoreConformance.d.mts +40 -40
  112. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  113. package/dist/durable/PersistentHistory.d.mts +2 -2
  114. package/dist/durable/PersistentHistory.mjs +4 -4
  115. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  116. package/dist/durable/RecordFormat.mjs +3 -3
  117. package/dist/durable/RecordFormat.mjs.map +1 -1
  118. package/dist/durable/Records.mjs +1 -1
  119. package/dist/durable/Recovery.d.mts +1 -1
  120. package/dist/durable/Recovery.mjs +1 -1
  121. package/dist/durable/RunContinuation.d.mts +2 -2
  122. package/dist/durable/RunContinuation.mjs +3 -3
  123. package/dist/durable/RunJournal.d.mts +2 -2
  124. package/dist/durable/RunJournal.mjs +2 -2
  125. package/dist/durable/RunStorage.d.mts +1 -1
  126. package/dist/durable/RunStorage.mjs +1 -1
  127. package/dist/durable/Schedule.mjs +1 -1
  128. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  129. package/dist/durable/ScheduleTransition.mjs +1 -1
  130. package/dist/durable/Scheduling.d.mts +1 -1
  131. package/dist/durable/Scheduling.mjs +1 -1
  132. package/dist/durable/SettlementPublisher.d.mts +1 -1
  133. package/dist/durable/SettlementPublisher.mjs +3 -3
  134. package/dist/durable/SqlMemoryStore.mjs +5 -3
  135. package/dist/durable/SqlMemoryStore.mjs.map +1 -1
  136. package/dist/durable/SubmissionLedger.d.mts +1 -1
  137. package/dist/durable/SubmissionLedger.mjs +11 -3
  138. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  139. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  140. package/dist/durable/SubmissionLedgerConformance.mjs +6 -6
  141. package/dist/durable/SubmissionStatus.d.mts +1 -1
  142. package/dist/durable/Subscription.d.mts +1 -1
  143. package/dist/durable/Subscription.mjs +2 -2
  144. package/dist/durable/SubscriptionInput.d.mts +1 -1
  145. package/dist/durable/SubscriptionInput.mjs +4 -4
  146. package/dist/durable/SubscriptionInput.mjs.map +1 -1
  147. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  148. package/dist/durable/SubscriptionStoreConformance.mjs +3 -3
  149. package/dist/durable/SubscriptionStoreConformance.mjs.map +1 -1
  150. package/dist/durable/SubscriptionTools.d.mts +2 -2
  151. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  152. package/dist/durable/SubscriptionTransition.mjs +1 -1
  153. package/dist/durable/Subscriptions.d.mts +5 -5
  154. package/dist/durable/Subscriptions.mjs +3 -3
  155. package/dist/durable/Subscriptions.mjs.map +1 -1
  156. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  157. package/dist/durable/ThreadArchiveRange.mjs +1 -1
  158. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  159. package/dist/durable/ThreadContextHistory.mjs +2 -2
  160. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  161. package/dist/durable/ThreadImport.d.mts +1 -1
  162. package/dist/durable/ThreadImport.mjs +1 -1
  163. package/dist/durable/ThreadInvariants.d.mts +3 -3
  164. package/dist/durable/ThreadInvariants.mjs +5 -5
  165. package/dist/durable/ThreadInvariants.mjs.map +1 -1
  166. package/dist/durable/ThreadProjection.mjs +1 -1
  167. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  168. package/dist/durable/ThreadReader.d.mts +1 -1
  169. package/dist/durable/ThreadReader.mjs +1 -1
  170. package/dist/durable/ThreadStore.d.mts +1 -1
  171. package/dist/durable/ThreadStore.mjs +2 -2
  172. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  173. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  174. package/dist/durable/ThreadTransfer.d.mts +1 -1
  175. package/dist/durable/ThreadTransfer.mjs +1 -1
  176. package/dist/durable/ThreadWork.d.mts +2 -2
  177. package/dist/durable/ThreadWork.mjs +12 -6
  178. package/dist/durable/ThreadWork.mjs.map +1 -1
  179. package/dist/durable/ToolReconciler.mjs +1 -1
  180. package/dist/durable/WakeScheduler.mjs +23 -39
  181. package/dist/durable/WakeScheduler.mjs.map +1 -1
  182. package/dist/durable/WorkerAdmission.d.mts +1 -1
  183. package/dist/durable/WorkerAdmission.mjs +1 -1
  184. package/dist/durable/WorkerHost.d.mts +2 -2
  185. package/dist/engine/AgentRuntime.d.mts +1 -1
  186. package/dist/engine/AgentRuntime.mjs +1 -1
  187. package/dist/engine/MessagingHost.d.mts +1 -1
  188. package/dist/engine/MessagingHost.mjs +1 -1
  189. package/dist/engine/Output.d.mts +1 -1
  190. package/dist/engine/Output.mjs +1 -1
  191. package/dist/engine/RunOptions.d.mts +1 -1
  192. package/dist/engine/SubagentHost.d.mts +1 -1
  193. package/dist/engine/ThreadHistory.d.mts +1 -1
  194. package/dist/engine/ThreadHistory.mjs +3 -2
  195. package/dist/engine/ThreadHistory.mjs.map +1 -1
  196. package/dist/index.d.mts +21 -21
  197. package/dist/index.mjs +16 -16
  198. package/dist/{output-contract-CMIhu4Cw.mjs → output-contract-DCnLYCdp.mjs} +12 -15
  199. package/dist/{output-contract-CMIhu4Cw.mjs.map → output-contract-DCnLYCdp.mjs.map} +1 -1
  200. package/dist/{run-context-reader-OcsZr4bk.d.mts → run-context-reader-DVhJZ1Qe.d.mts} +2 -2
  201. package/package.json +1 -1
  202. package/src/capabilities/Approval.ts +58 -60
  203. package/src/capabilities/BrowserUse.ts +89 -944
  204. package/src/capabilities/Budget.ts +8 -1
  205. package/src/capabilities/CodeMode.ts +57 -33
  206. package/src/capabilities/McpClient.ts +2 -2
  207. package/src/capabilities/Messaging.ts +51 -55
  208. package/src/capabilities/ModelContext.ts +5 -2
  209. package/src/capabilities/Redaction.ts +64 -34
  210. package/src/capabilities/RunHooks.ts +52 -55
  211. package/src/capabilities/Subagent.ts +33 -33
  212. package/src/capabilities/SubagentReservations.ts +9 -1
  213. package/src/capabilities/WebCapture.ts +6 -0
  214. package/src/capabilities/WebSearch.ts +10 -7
  215. package/src/capabilities/internal/subagent-background.ts +202 -158
  216. package/src/core/Agent.ts +11 -6
  217. package/src/core/AgentUpdates.ts +8 -8
  218. package/src/core/Thread.ts +142 -146
  219. package/src/core/Usage.ts +7 -8
  220. package/src/core/internal/agent-updates.ts +1 -0
  221. package/src/durable/DurableAgentRuntime.ts +1024 -829
  222. package/src/durable/EventSource.ts +15 -9
  223. package/src/durable/RecordFormat.ts +5 -3
  224. package/src/durable/RunContinuation.ts +316 -113
  225. package/src/durable/RunJournal.ts +106 -114
  226. package/src/durable/RunStorage.ts +129 -124
  227. package/src/durable/Scheduling.ts +639 -651
  228. package/src/durable/SqlMemoryStore.ts +8 -6
  229. package/src/durable/SubmissionLedger.ts +11 -0
  230. package/src/durable/SubscriptionInput.ts +5 -3
  231. package/src/durable/SubscriptionStoreConformance.ts +23 -23
  232. package/src/durable/Subscriptions.ts +18 -19
  233. package/src/durable/ThreadImport.ts +18 -19
  234. package/src/durable/ThreadInvariants.ts +2 -1
  235. package/src/durable/ThreadStore.ts +217 -111
  236. package/src/durable/ThreadWork.ts +11 -2
  237. package/src/durable/WakeScheduler.ts +34 -66
  238. package/src/durable/internal/StorageOwner.ts +10 -4
  239. package/src/durable/internal/agent-registration.ts +96 -89
  240. package/src/durable/internal/canonical-json.ts +61 -20
  241. package/src/durable/internal/prompt-history-cache.ts +13 -4
  242. package/src/durable/internal/record-encoding.ts +120 -64
  243. package/src/durable/internal/schedule-time.ts +6 -6
  244. package/src/durable/internal/usage-totals.ts +35 -0
  245. package/src/durable/internal/worker-host.ts +69 -74
  246. package/src/engine/MessagingHost.ts +4 -2
  247. package/src/engine/ThreadHistory.ts +47 -48
  248. package/src/engine/internal/agent-runtime.ts +1298 -1217
  249. package/src/engine/internal/output-contract.ts +15 -15
  250. package/src/engine/internal/primitive-delta.ts +2 -3
  251. package/src/engine/internal/provisional-text.ts +12 -10
  252. package/src/engine/internal/tool-exposure.ts +22 -18
  253. package/src/engine/internal/tool-telemetry.ts +15 -12
  254. package/dist/Agent-0WvBWWbM.mjs.map +0 -1
  255. package/dist/AgentRuntime-CQ-qcETl.mjs.map +0 -1
  256. package/dist/Digest-DCuPlhNk.mjs.map +0 -1
  257. package/dist/DurableAgentRuntime-BAmSkWYr.mjs.map +0 -1
  258. package/dist/MessagingHost-CRUK4JBi.mjs.map +0 -1
  259. package/dist/RunContinuation-DHYNAQFi.mjs.map +0 -1
  260. package/dist/RunJournal-kfKVxJj7.mjs.map +0 -1
  261. package/dist/RunStorage-uqPxps09.mjs.map +0 -1
  262. package/dist/Scheduling-CVyfXlBR.mjs.map +0 -1
  263. package/dist/Subagent-vpffemql.mjs.map +0 -1
  264. package/dist/ThreadImport-DckUMEqJ.mjs.map +0 -1
  265. package/dist/ThreadStore-CEekRa80.mjs.map +0 -1
  266. package/dist/agent-registration-ByMZ_cK5.mjs.map +0 -1
@@ -63,6 +63,7 @@ import {
63
63
  getToolExecutionKind,
64
64
  SubagentParentLink,
65
65
  } from "../core/SubagentContract.ts";
66
+ import { Store as ConversationStore, makeMemoryStore } from "../core/Thread.ts";
66
67
  import { Selection, Snapshot } from "../core/ToolExposure.ts";
67
68
  import type { ToolParameterRejection } from "../core/ToolResult.ts";
68
69
  import {
@@ -280,6 +281,7 @@ import {
280
281
  modelResponseInterruptedRecordId,
281
282
  modelResponseRecordId,
282
283
  projectRunJournalStream,
284
+ initialRunJournal,
283
285
  type JournalBoundary,
284
286
  type RunJournalProjection,
285
287
  type RunJournalContext,
@@ -846,6 +848,11 @@ export class RecoverySweepResult extends Schema.Class<RecoverySweepResult>("Reco
846
848
  /** Recover one bounded page of native work, optionally confined to one Thread. */
847
849
  export interface RecoverySweepOptions {
848
850
  readonly threadId?: ThreadId;
851
+ /** Leave this incarnation's untouched root admissions to their first actual claim.
852
+ * A verified sole admission needs no recovery inventory; other owners retain bounded
853
+ * discovery and deferred reports. Neither case supplies ownership or a recovery decision:
854
+ * the Attempt still verifies current controls, journal, fencing and historyDigest. */
855
+ readonly deferUnclaimedAdmissions?: boolean;
849
856
  /** Continue the previous pass with the same Thread selection until its result has no cursor. */
850
857
  readonly cursor?: string;
851
858
  }
@@ -1374,6 +1381,11 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1374
1381
  const reader = ThreadReader.fromStore(store);
1375
1382
  const promptHistory = yield* makePromptHistoryCache();
1376
1383
 
1384
+ // One incarnation remembers only newly accepted root input, never a recovery decision.
1385
+ // A claim consumes the hint before binding/application code can run. Cold owners,
1386
+ // replays, linked inputs, and any changed ledger state retain the recovery path.
1387
+ const unclaimedAdmissions = new Map<SubmissionId, ThreadId>();
1388
+
1377
1389
  const workDiscovery = yield* ThreadWorkDiscovery.pipe(
1378
1390
  Effect.provide(ThreadWorkDiscovery.layer),
1379
1391
  Effect.provideService(ThreadStore, store),
@@ -1444,6 +1456,17 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1444
1456
  // observer must never replace this host choice on a fresh or replacement Attempt.
1445
1457
  const toolFailureObserver = yield* CurrentToolFailureObserver;
1446
1458
 
1459
+ const runServices = Context.make(CurrentToolFailureObserver, toolFailureObserver).pipe(
1460
+ Context.add(RunToolVisibility, runToolVisibility),
1461
+ Context.add(RunToolScheduling, runToolScheduling),
1462
+ Context.add(ContextCompactor, compactor),
1463
+ Context.add(RunContextPreparation, runContextPreparation),
1464
+ );
1465
+
1466
+ const journalServices = Context.make(ThreadReader, reader).pipe(
1467
+ Context.add(Crypto.Crypto, crypto),
1468
+ );
1469
+
1447
1470
  const withCrypto = <A, E>(effect: Effect.Effect<A, E, Crypto.Crypto>): Effect.Effect<A, E> =>
1448
1471
  Effect.provideService(effect, Crypto.Crypto, crypto);
1449
1472
 
@@ -1453,8 +1476,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1453
1476
  suffix: ReadonlyArray<CanonicalRecordEnvelope>,
1454
1477
  ) =>
1455
1478
  initialContext(original).pipe(
1456
- Effect.provideService(ThreadReader, reader),
1457
- Effect.provideService(Crypto.Crypto, crypto),
1479
+ Effect.provideContext(journalServices),
1458
1480
  Effect.map((history) => history.concat(suffix)),
1459
1481
  );
1460
1482
 
@@ -1662,6 +1684,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1662
1684
  let context: RunJournalContext | undefined;
1663
1685
  let progress: RunContinuation | undefined;
1664
1686
  let progressThrough: CanonicalSequence | undefined;
1687
+ let operational: ReadonlyArray<RecordEnvelope> = [];
1665
1688
 
1666
1689
  const invalid = (message: string) =>
1667
1690
  ThreadStoreError.make({
@@ -1707,10 +1730,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1707
1730
 
1708
1731
  if (found === undefined) {
1709
1732
  // An exact lookup distinguishes missing evidence from an incomplete native index.
1710
- yield* resolveEvidence(threadId, ref).pipe(
1711
- Effect.provideService(ThreadReader, reader),
1712
- Effect.provideService(Crypto.Crypto, crypto),
1713
- );
1733
+ yield* resolveEvidence(threadId, ref).pipe(Effect.provideContext(journalServices));
1714
1734
 
1715
1735
  return yield* invalid(
1716
1736
  "Required Run evidence is absent from its locator; rebuild the index explicitly",
@@ -1777,8 +1797,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1777
1797
  } else {
1778
1798
  saved = yield* readRunContext(initial, input, cursor.savedContext.recordId).pipe(
1779
1799
  Effect.provide(RunContextReader.layer(threadId)),
1780
- Effect.provideService(ThreadReader, reader),
1781
- Effect.provideService(Crypto.Crypto, crypto),
1800
+ Effect.provideContext(journalServices),
1782
1801
  Effect.mapError(() =>
1783
1802
  invalid("Saved original context cannot be resolved from its evidence"),
1784
1803
  ),
@@ -1822,6 +1841,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1822
1841
  context = saved;
1823
1842
  progress = cursor;
1824
1843
  progressThrough = envelope.throughSequence;
1844
+ operational = envelope.operational;
1825
1845
  }
1826
1846
  }
1827
1847
 
@@ -1833,7 +1853,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1833
1853
  ).values(),
1834
1854
  ].sort((left, right) => left.sequence - right.sequence);
1835
1855
 
1836
- return { canonical: Stream.fromIterable(records), context, progress, progressThrough };
1856
+ return { records, context, progress, progressThrough, operational };
1837
1857
  });
1838
1858
 
1839
1859
  const readControl = Effect.fnUntraced(function* (
@@ -1843,9 +1863,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1843
1863
  const tail = yield* store.inspectTail(ThreadTailRequest.make({ threadId }));
1844
1864
  const view = yield* recoveryView(threadId, tail.tailSequence, submissionIds);
1845
1865
 
1846
- return yield* Stream.runCollect(
1847
- view.canonical.pipe(Stream.filter(controlRecords(submissionIds))),
1848
- );
1866
+ return view.records.filter(controlRecords(submissionIds));
1849
1867
  });
1850
1868
 
1851
1869
  const readAllTolerant = (
@@ -1888,9 +1906,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1888
1906
  return { records: [], materialized: false, throughSequence: ZERO_SEQUENCE, submissionIds };
1889
1907
 
1890
1908
  const records = yield* recoveryView(threadId, tail.value.tailSequence, submissionIds).pipe(
1891
- Effect.flatMap((view) =>
1892
- Stream.runCollect(view.canonical.pipe(Stream.filter(controlRecords(submissionIds)))),
1893
- ),
1909
+ Effect.map((view) => view.records.filter(controlRecords(submissionIds))),
1894
1910
  Effect.catchTag("ThreadNotMaterialized", (error) =>
1895
1911
  ThreadStoreError.make({
1896
1912
  operation: "read recovery history",
@@ -2575,8 +2591,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
2575
2591
  const progress =
2576
2592
  active?.writer ??
2577
2593
  (yield* makeProgressWriter(writer.threadId, config.deploymentId).pipe(
2578
- Effect.provideService(ThreadReader, reader),
2579
- Effect.provideService(Crypto.Crypto, crypto),
2594
+ Effect.provideContext(journalServices),
2580
2595
  ));
2581
2596
 
2582
2597
  const owned: RunWriter = {
@@ -2629,50 +2644,48 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
2629
2644
  // also covers a destination handoff written before dispatch or after Run settlement.
2630
2645
  const progressStore = ThreadStore.of({
2631
2646
  ...store,
2632
- append: (request) =>
2633
- Effect.gen(function* () {
2634
- const active = activeProgress.get(request.threadId);
2647
+ append: Effect.fnUntraced(function* (request) {
2648
+ const active = activeProgress.get(request.threadId);
2635
2649
 
2636
- const progress =
2637
- active?.writer ??
2638
- (yield* makeProgressWriter(request.threadId, config.deploymentId).pipe(
2639
- Effect.provideService(ThreadReader, reader),
2640
- Effect.provideService(Crypto.Crypto, crypto),
2641
- ));
2650
+ const progress =
2651
+ active?.writer ??
2652
+ (yield* makeProgressWriter(request.threadId, config.deploymentId).pipe(
2653
+ Effect.provideContext(journalServices),
2654
+ ));
2642
2655
 
2643
- let tail = {
2644
- sequence: request.expectedTailSequence,
2645
- };
2656
+ let tail = {
2657
+ sequence: request.expectedTailSequence,
2658
+ };
2646
2659
 
2647
- return yield* progress.commit(request.batch).pipe(
2648
- Effect.provideService(CurrentRunWriter, {
2649
- threadId: request.threadId,
2650
- tail: Effect.sync(() => tail),
2651
- withProgress: (effect) => storageOwner.withProgress(request.threadId, effect),
2652
- append: (batch, executions) =>
2653
- store
2654
- .append(
2655
- AppendRequest.make({
2656
- ...request,
2657
- expectedTailSequence: tail.sequence,
2658
-
2659
- batch,
2660
- executions,
2660
+ return yield* progress.commit(request.batch).pipe(
2661
+ Effect.provideService(CurrentRunWriter, {
2662
+ threadId: request.threadId,
2663
+ tail: Effect.sync(() => tail),
2664
+ withProgress: (effect) => storageOwner.withProgress(request.threadId, effect),
2665
+ append: (batch, executions) =>
2666
+ store
2667
+ .append(
2668
+ AppendRequest.make({
2669
+ ...request,
2670
+ expectedTailSequence: tail.sequence,
2671
+
2672
+ batch,
2673
+ executions,
2674
+ }),
2675
+ )
2676
+ .pipe(
2677
+ Effect.tapErrorTag("AppendConflict", (conflict) =>
2678
+ Effect.sync(() => {
2679
+ if (conflict.reason === "tail" && conflict.actualTailSequence !== undefined)
2680
+ tail = {
2681
+ sequence: conflict.actualTailSequence,
2682
+ };
2661
2683
  }),
2662
- )
2663
- .pipe(
2664
- Effect.tapErrorTag("AppendConflict", (conflict) =>
2665
- Effect.sync(() => {
2666
- if (conflict.reason === "tail" && conflict.actualTailSequence !== undefined)
2667
- tail = {
2668
- sequence: conflict.actualTailSequence,
2669
- };
2670
- }),
2671
- ),
2672
2684
  ),
2673
- }),
2674
- );
2675
- }),
2685
+ ),
2686
+ }),
2687
+ );
2688
+ }),
2676
2689
  });
2677
2690
 
2678
2691
  const appendBatch = (ctx: Pick<RunWriter, "threadId" | "append">, batch: CanonicalBatch) =>
@@ -2701,51 +2714,49 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
2701
2714
  threadId: ctx.threadId,
2702
2715
  tail: ctx.tail,
2703
2716
  withProgress: ctx.withProgress,
2704
- publishSettlement: (prepared, executions) =>
2705
- Effect.gen(function* () {
2706
- let tail = yield* ctx.tail;
2707
-
2708
- for (let retries = 0; ; retries++) {
2709
- const result = yield* runStorage
2710
- .publishSettlement(
2711
- SettlementPublication.make({
2712
- submissionId,
2713
- authority,
2714
- append: AppendRequest.make({
2715
- threadId: ctx.threadId,
2716
- expectedTailSequence: tail.sequence,
2717
-
2718
- batch: prepared,
2719
- executions,
2720
- }),
2721
- }),
2722
- )
2723
- .pipe(
2724
- (effect) =>
2725
- storageOwner.withMutation(Effect.andThen(ctx.checkActive, effect)),
2726
- Effect.catchTag("AppendConflict", (conflict) => {
2727
- if (
2728
- conflict.reason !== "tail" ||
2729
- conflict.actualTailSequence === undefined ||
2730
- retries >= 8
2731
- )
2732
- return Effect.fail(conflict);
2733
- tail = {
2734
- sequence: conflict.actualTailSequence,
2735
- };
2736
-
2737
- if (executions !== undefined && executions.length > 0)
2738
- return ctx.refresh.pipe(Effect.andThen(Effect.fail(conflict)));
2739
-
2740
- return Effect.succeed(undefined);
2717
+ publishSettlement: Effect.fnUntraced(function* (prepared, executions) {
2718
+ let tail = yield* ctx.tail;
2719
+
2720
+ for (let retries = 0; ; retries++) {
2721
+ const result = yield* runStorage
2722
+ .publishSettlement(
2723
+ SettlementPublication.make({
2724
+ submissionId,
2725
+ authority,
2726
+ append: AppendRequest.make({
2727
+ threadId: ctx.threadId,
2728
+ expectedTailSequence: tail.sequence,
2729
+
2730
+ batch: prepared,
2731
+ executions,
2741
2732
  }),
2742
- );
2733
+ }),
2734
+ )
2735
+ .pipe(
2736
+ (effect) => storageOwner.withMutation(Effect.andThen(ctx.checkActive, effect)),
2737
+ Effect.catchTag("AppendConflict", (conflict) => {
2738
+ if (
2739
+ conflict.reason !== "tail" ||
2740
+ conflict.actualTailSequence === undefined ||
2741
+ retries >= 8
2742
+ )
2743
+ return Effect.fail(conflict);
2744
+ tail = {
2745
+ sequence: conflict.actualTailSequence,
2746
+ };
2743
2747
 
2744
- if (result === undefined) continue;
2748
+ if (executions !== undefined && executions.length > 0)
2749
+ return ctx.refresh.pipe(Effect.andThen(Effect.fail(conflict)));
2745
2750
 
2746
- return result;
2747
- }
2748
- }),
2751
+ return Effect.succeed(undefined);
2752
+ }),
2753
+ );
2754
+
2755
+ if (result === undefined) continue;
2756
+
2757
+ return result;
2758
+ }
2759
+ }),
2749
2760
  }),
2750
2761
  );
2751
2762
  });
@@ -3207,15 +3218,23 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3207
3218
  yield* ownership.markInputApplied({ recordId, sequence: result.firstSequence });
3208
3219
  });
3209
3220
 
3210
- const readRunStart = Effect.fnUntraced(function* (threadId: ThreadId, runId: RunId) {
3211
- const loaded = yield* reader
3212
- .readExecution({ threadId, runId })
3213
- .pipe(Effect.catchTag("ThreadNotMaterialized", () => Effect.succeed(Option.none())));
3221
+ const readRunStart = Effect.fnUntraced(function* (
3222
+ threadId: ThreadId,
3223
+ runId: RunId,
3224
+ operational?: ReadonlyArray<RecordEnvelope>,
3225
+ ) {
3226
+ if (operational === undefined) {
3227
+ const loaded = yield* reader
3228
+ .readExecution({ threadId, runId })
3229
+ .pipe(Effect.catchTag("ThreadNotMaterialized", () => Effect.succeed(Option.none())));
3230
+
3231
+ if (Option.isNone(loaded)) return undefined;
3232
+ operational = loaded.value.operational;
3233
+ }
3214
3234
 
3215
- if (Option.isNone(loaded)) return undefined;
3216
3235
  const recordId = runStartedRecordId(runId);
3217
- const starts = loaded.value.operational.filter(({ payload }) => payload._tag === "RunStarted");
3218
- const existing = loaded.value.operational.find((record) => record.recordId === recordId);
3236
+ const starts = operational.filter(({ payload }) => payload._tag === "RunStarted");
3237
+ const existing = operational.find((record) => record.recordId === recordId);
3219
3238
 
3220
3239
  if (
3221
3240
  starts.length > 1 ||
@@ -3234,11 +3253,12 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3234
3253
  submission: SubmissionSnapshot,
3235
3254
  maxDurationMillis: number,
3236
3255
  records: ReadonlyArray<CanonicalRecordEnvelope>,
3256
+ operational: ReadonlyArray<RecordEnvelope>,
3237
3257
  ) {
3238
3258
  const writer = yield* CurrentRunWriter;
3239
3259
  const runId = runIdForSubmission(submission.submissionId);
3240
3260
  const recordId = runStartedRecordId(runId);
3241
- const existing = yield* readRunStart(submission.threadId, runId);
3261
+ const existing = yield* readRunStart(submission.threadId, runId, operational);
3242
3262
  let start: RecordEnvelope;
3243
3263
  let allowance = maxDurationMillis;
3244
3264
  let committed = existing !== undefined;
@@ -4762,70 +4782,71 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
4762
4782
  knownIds.add(recordId);
4763
4783
  });
4764
4784
 
4765
- /**
4766
- * Drive the shared interpreter Effect with history rebuilt by the run journal, commit
4767
- * each Turn under the local owner, and watch for durable abort intent. Engine Run failures settle `failed`; coordinator failures abort the
4768
- * Attempt cleanly with the obligation still owed.
4769
- *
4770
- * The interpreter supplies validated response, closed Tool-result, and completion facts.
4771
- * Responses precede approval and effectful dispatch; readonly responses may join their closed
4772
- * results. Tool results precede input drains and the next Turn. A no-tool response and Run
4773
- * completion share one batch. A completion Tool's result
4774
- * precedes its terminal-only completion batch, so recovery can repeat a pure output projection
4775
- * without replaying the handler. Pending batches resume from their canonical declarations.
4776
- */
4777
- const runModel = <
4778
- InputSchema extends Schema.Top,
4779
- OutputSchema extends Schema.Top,
4780
- Instructions,
4781
- Tools extends Record<string, Tool.Any>,
4782
- Provider,
4783
- ModelProvides,
4784
- ModelRequires,
4785
- InstructionError = InstructionErrorOf<Instructions, InputSchema["Type"]>,
4786
- InstructionRequirements = InstructionRequirementsOf<Instructions, InputSchema["Type"]>,
4787
- RunDispositionValue extends
4788
- | RunDispositionDeclaration<OutputSchema["Type"], Schema.Top>
4789
- | undefined = undefined,
4790
- InputPromptValue extends InputPromptSource<InputSchema["Type"], unknown, unknown> | undefined =
4791
- undefined,
4792
- UpdatesSchema extends Schema.Top | undefined = undefined,
4793
- >(
4794
- agent: RuntimeBinding<
4795
- InputSchema,
4796
- OutputSchema,
4785
+ const runModel = Effect.fnUntraced(
4786
+ /**
4787
+ * Drive the shared interpreter Effect with history rebuilt by the run journal, commit
4788
+ * each Turn under the local owner, and watch for durable abort intent. Engine Run failures settle `failed`; coordinator failures abort the
4789
+ * Attempt cleanly with the obligation still owed.
4790
+ *
4791
+ * The interpreter supplies validated response, closed Tool-result, and completion facts.
4792
+ * Responses precede approval and effectful dispatch; readonly responses may join their closed
4793
+ * results. Tool results precede input drains and the next Turn. A no-tool response and Run
4794
+ * completion share one batch. A completion Tool's result
4795
+ * precedes its terminal-only completion batch, so recovery can repeat a pure output projection
4796
+ * without replaying the handler. Pending batches resume from their canonical declarations.
4797
+ */
4798
+ function* <
4799
+ InputSchema extends Schema.Top,
4800
+ OutputSchema extends Schema.Top,
4797
4801
  Instructions,
4798
- Tools,
4802
+ Tools extends Record<string, Tool.Any>,
4799
4803
  Provider,
4800
4804
  ModelProvides,
4801
4805
  ModelRequires,
4802
- InstructionError,
4803
- InstructionRequirements,
4804
- RunDispositionValue,
4805
- InputPromptValue,
4806
- UpdatesSchema
4807
- >,
4808
- ctx: AttemptAppendContext,
4809
- submission: SubmissionSnapshot,
4810
- session: RunStorageSession,
4811
- records: ReadonlyArray<CanonicalRecordEnvelope>,
4812
- canonical: Stream.Stream<
4813
- JournalRecordEnvelope,
4814
- ThreadStoreError | ThreadNotMaterialized | RunJournalError
4815
- >,
4816
- initialJournal: InitialJournalSource | undefined,
4817
- canonicalThrough: CanonicalSequence,
4818
- progressThrough: CanonicalSequence | undefined,
4819
- continuation: RunContinuation | undefined,
4820
- priorContext: RunJournalContext | undefined,
4821
- journalMetadata: JournalMetadata | undefined,
4822
- lineage: AttemptLineage,
4823
- approvalDecisions: ReadonlyArray<ApprovalDecisionIntent>,
4824
- currentContracts: Readonly<Record<string, Digest>>,
4825
- runTiming: RunTiming,
4826
- yieldAfter?: DateTime.Utc,
4827
- ) =>
4828
- Effect.gen(function* () {
4806
+ InstructionError = InstructionErrorOf<Instructions, InputSchema["Type"]>,
4807
+ InstructionRequirements = InstructionRequirementsOf<Instructions, InputSchema["Type"]>,
4808
+ RunDispositionValue extends
4809
+ | RunDispositionDeclaration<OutputSchema["Type"], Schema.Top>
4810
+ | undefined = undefined,
4811
+ InputPromptValue extends
4812
+ | InputPromptSource<InputSchema["Type"], unknown, unknown>
4813
+ | undefined = undefined,
4814
+ UpdatesSchema extends Schema.Top | undefined = undefined,
4815
+ >(
4816
+ agent: RuntimeBinding<
4817
+ InputSchema,
4818
+ OutputSchema,
4819
+ Instructions,
4820
+ Tools,
4821
+ Provider,
4822
+ ModelProvides,
4823
+ ModelRequires,
4824
+ InstructionError,
4825
+ InstructionRequirements,
4826
+ RunDispositionValue,
4827
+ InputPromptValue,
4828
+ UpdatesSchema
4829
+ >,
4830
+ ctx: AttemptAppendContext,
4831
+ submission: SubmissionSnapshot,
4832
+ session: RunStorageSession,
4833
+ records: ReadonlyArray<CanonicalRecordEnvelope>,
4834
+ canonical: Stream.Stream<
4835
+ JournalRecordEnvelope,
4836
+ ThreadStoreError | ThreadNotMaterialized | RunJournalError
4837
+ >,
4838
+ initialJournal: InitialJournalSource | undefined,
4839
+ canonicalThrough: CanonicalSequence,
4840
+ progressThrough: CanonicalSequence | undefined,
4841
+ continuation: RunContinuation | undefined,
4842
+ priorContext: RunJournalContext | undefined,
4843
+ journalMetadata: JournalMetadata | undefined,
4844
+ lineage: AttemptLineage,
4845
+ approvalDecisions: ReadonlyArray<ApprovalDecisionIntent>,
4846
+ currentContracts: Readonly<Record<string, Digest>>,
4847
+ runTiming: RunTiming,
4848
+ yieldAfter?: DateTime.Utc,
4849
+ ) {
4829
4850
  const submissionId = submission.submissionId;
4830
4851
 
4831
4852
  const runId = runIdForSubmission(submissionId);
@@ -4851,23 +4872,35 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
4851
4872
  historyEvidence.set(entry.record.recordId, entry);
4852
4873
  };
4853
4874
 
4875
+ // An exact cache prefix followed only by this fresh input has no response,
4876
+ // policy reservation or context to replay. Keep recovery on the full projector.
4877
+ const freshPrefix =
4878
+ priorContext === undefined &&
4879
+ initialJournal?.prefix !== undefined &&
4880
+ initialJournal.records?.length === 1 &&
4881
+ initialJournal.records[0] === originalInput
4882
+ ? initialJournal.prefix.prompt
4883
+ : undefined;
4884
+
4854
4885
  let journal =
4855
- cached !== undefined &&
4856
- cached.threadId === ctx.threadId &&
4857
- cached.through === projectionThrough &&
4858
- cached.runId === runId &&
4859
- cached.contextRecordId === priorContext?.recordId
4860
- ? (boundaries.push(...cached.boundaries),
4861
- cached.contextEvidence.forEach(retainHistory),
4862
- cached.journal)
4863
- : yield* projectRunJournalStream(
4864
- canonical,
4865
- runId,
4866
- (boundary) => boundaries.push(boundary),
4867
- priorContext,
4868
- journalMetadata,
4869
- priorContext === undefined ? retainHistory : undefined,
4870
- );
4886
+ freshPrefix !== undefined
4887
+ ? initialRunJournal(freshPrefix)
4888
+ : cached !== undefined &&
4889
+ cached.threadId === ctx.threadId &&
4890
+ cached.through === projectionThrough &&
4891
+ cached.runId === runId &&
4892
+ cached.contextRecordId === priorContext?.recordId
4893
+ ? (boundaries.push(...cached.boundaries),
4894
+ cached.contextEvidence.forEach(retainHistory),
4895
+ cached.journal)
4896
+ : yield* projectRunJournalStream(
4897
+ canonical,
4898
+ runId,
4899
+ (boundary) => boundaries.push(boundary),
4900
+ priorContext,
4901
+ journalMetadata,
4902
+ priorContext === undefined ? retainHistory : undefined,
4903
+ );
4871
4904
 
4872
4905
  if (initialJournal?.prefix !== undefined) {
4873
4906
  const prompt = initialJournal.prefix.prompt;
@@ -5187,9 +5220,6 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5187
5220
  ),
5188
5221
  );
5189
5222
 
5190
- const addUsageTotal = (field: string, left: number, right: number) =>
5191
- checkedUsageTotal(field, left + right);
5192
-
5193
5223
  const subtractUsageTotal = (field: string, left: number, right: number) =>
5194
5224
  checkedUsageTotal(field, left - right);
5195
5225
 
@@ -5199,11 +5229,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5199
5229
  let unobservedModelCalls = journal.usage.unobservedModelCalls ?? 0;
5200
5230
 
5201
5231
  for (const count of stagedUnobservedCalls.values()) {
5202
- unobservedModelCalls = yield* addUsageTotal(
5203
- "unobservedModelCalls",
5204
- unobservedModelCalls,
5205
- count,
5206
- );
5232
+ unobservedModelCalls += count;
5207
5233
  }
5208
5234
  const detailedCalls = [...journal.usage.modelUsage, ...stagedCalls];
5209
5235
 
@@ -5224,17 +5250,15 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5224
5250
  let costMicrousd = journal.usage.costMicrousd;
5225
5251
 
5226
5252
  for (const usage of stagedUsage.values()) {
5227
- inputTokens = yield* addUsageTotal("inputTokens", inputTokens, usage.inputTokens);
5228
- outputTokens = yield* addUsageTotal("outputTokens", outputTokens, usage.outputTokens);
5229
- costMicrousd = yield* addUsageTotal("costMicrousd", costMicrousd, usage.costMicrousd);
5253
+ inputTokens += usage.inputTokens;
5254
+ outputTokens += usage.outputTokens;
5255
+ costMicrousd += usage.costMicrousd;
5230
5256
  }
5231
5257
 
5232
- const modelCalls = yield* addUsageTotal(
5233
- "modelCalls",
5234
- journal.usage.modelCalls,
5235
- stagedCalls.length,
5236
- );
5258
+ const modelCalls = journal.usage.modelCalls + stagedCalls.length;
5237
5259
 
5260
+ // Reconciliation must reject negative legacy deltas independently of
5261
+ // the final summary's complete safe-integer and additive checks.
5238
5262
  const legacyCalls = yield* subtractUsageTotal(
5239
5263
  "legacy.modelCalls",
5240
5264
  modelCalls,
@@ -5278,81 +5302,52 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5278
5302
 
5279
5303
  const existing = existingIndex < 0 ? undefined : byModel[existingIndex];
5280
5304
 
5281
- const legacyGroup = ModelUsageGroup.make({
5305
+ const legacyGroup = yield* ModelUsageGroup.makeEffect({
5282
5306
  provider: "unknown",
5283
5307
  model: "legacy-record",
5284
- modelCalls: yield* addUsageTotal(
5285
- "byModel.modelCalls",
5286
- existing?.modelCalls ?? 0,
5287
- legacyCalls,
5288
- ),
5289
- inputTokens: InputTokenUsage.make({
5290
- total: yield* addUsageTotal(
5291
- "byModel.inputTokens.total",
5292
- existing?.inputTokens.total ?? 0,
5293
- legacyInput,
5294
- ),
5295
- uncached: yield* addUsageTotal(
5296
- "byModel.inputTokens.uncached",
5297
- existing?.inputTokens.uncached ?? 0,
5298
- legacyInput,
5299
- ),
5308
+ modelCalls: (existing?.modelCalls ?? 0) + legacyCalls,
5309
+ inputTokens: {
5310
+ total: (existing?.inputTokens.total ?? 0) + legacyInput,
5311
+ uncached: (existing?.inputTokens.uncached ?? 0) + legacyInput,
5300
5312
  cacheRead: existing?.inputTokens.cacheRead ?? 0,
5301
5313
  cacheWrite: existing?.inputTokens.cacheWrite ?? 0,
5302
- }),
5303
- outputTokens: OutputTokenUsage.make({
5304
- total: yield* addUsageTotal(
5305
- "byModel.outputTokens.total",
5306
- existing?.outputTokens.total ?? 0,
5307
- legacyOutput,
5308
- ),
5309
- text: yield* addUsageTotal(
5310
- "byModel.outputTokens.text",
5311
- existing?.outputTokens.text ?? 0,
5312
- legacyOutput,
5313
- ),
5314
+ },
5315
+ outputTokens: {
5316
+ total: (existing?.outputTokens.total ?? 0) + legacyOutput,
5317
+ text: (existing?.outputTokens.text ?? 0) + legacyOutput,
5314
5318
  reasoning: existing?.outputTokens.reasoning ?? 0,
5315
- }),
5316
- costMicrousd: yield* addUsageTotal(
5317
- "byModel.costMicrousd",
5318
- existing?.costMicrousd ?? 0,
5319
- legacyCost,
5319
+ },
5320
+ costMicrousd: (existing?.costMicrousd ?? 0) + legacyCost,
5321
+ }).pipe(
5322
+ Effect.mapError((cause) =>
5323
+ RunJournalError.make({
5324
+ message: "Run legacy usage exceeds safe-integer accounting bounds",
5325
+ cause,
5326
+ }),
5320
5327
  ),
5321
- });
5328
+ );
5322
5329
 
5323
5330
  if (existingIndex < 0) byModel.push(legacyGroup);
5324
5331
  else byModel[existingIndex] = legacyGroup;
5325
5332
  }
5326
5333
 
5327
- const uncached = yield* addUsageTotal(
5328
- "inputTokens.uncached",
5329
- detailed.inputTokens.uncached,
5330
- legacyInput,
5331
- );
5332
-
5333
- const text = yield* addUsageTotal(
5334
- "outputTokens.text",
5335
- detailed.outputTokens.text,
5336
- legacyOutput,
5337
- );
5338
-
5339
- return RunUsageSummary.make({
5334
+ return yield* RunUsageSummary.makeEffect({
5340
5335
  ...(legacyCalls === 0 && detailed.webSearchCalls !== undefined
5341
5336
  ? { webSearchCalls: detailed.webSearchCalls }
5342
5337
  : {}),
5343
5338
  modelCalls,
5344
- inputTokens: InputTokenUsage.make({
5339
+ inputTokens: {
5345
5340
  total: inputTokens,
5346
5341
  // Legacy aggregate inputs are conservatively classified as uncached.
5347
- uncached,
5342
+ uncached: detailed.inputTokens.uncached + legacyInput,
5348
5343
  cacheRead: detailed.inputTokens.cacheRead,
5349
5344
  cacheWrite: detailed.inputTokens.cacheWrite,
5350
- }),
5351
- outputTokens: OutputTokenUsage.make({
5345
+ },
5346
+ outputTokens: {
5352
5347
  total: outputTokens,
5353
- text,
5348
+ text: detailed.outputTokens.text + legacyOutput,
5354
5349
  reasoning: detailed.outputTokens.reasoning,
5355
- }),
5350
+ },
5356
5351
  costMicrousd,
5357
5352
  byModel,
5358
5353
  unobservedModelCalls,
@@ -5368,7 +5363,14 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5368
5363
  ? "unknown"
5369
5364
  : "partial"
5370
5365
  : detailed.pricingStatus,
5371
- });
5366
+ }).pipe(
5367
+ Effect.mapError((cause) =>
5368
+ RunJournalError.make({
5369
+ message: "Run usage summary exceeds safe-integer accounting bounds",
5370
+ cause,
5371
+ }),
5372
+ ),
5373
+ );
5372
5374
  });
5373
5375
 
5374
5376
  const activeExecution = yield* reader.readExecution({ threadId: ctx.threadId, runId });
@@ -5544,37 +5546,38 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5544
5546
  const recordHalt = <A, R>(
5545
5547
  effect: Effect.Effect<A, DurableWorkerFailure, R>,
5546
5548
  ): Effect.Effect<A, CoordinatorHalt | AgentPersistenceCapacityError, R> =>
5547
- effect.pipe(
5548
- Effect.tapError((failure) =>
5549
- failure instanceof ThreadStoreError &&
5550
- failure.cause instanceof AgentPersistenceCapacityError
5551
- ? Effect.void
5552
- : Ref.set(haltRef, failure),
5553
- ),
5554
- Effect.catchCause((cause) =>
5555
- Effect.failCause(
5556
- Cause.map(cause, (failure) =>
5557
- failure instanceof ThreadStoreError &&
5558
- failure.cause instanceof AgentPersistenceCapacityError
5559
- ? failure.cause
5560
- : new CoordinatorHalt(failure),
5561
- ),
5549
+ Effect.catchCause(effect, (cause) => {
5550
+ const first = Cause.findErrorOption(cause);
5551
+
5552
+ const failure = Effect.failCause(
5553
+ Cause.map(cause, (error) =>
5554
+ error instanceof ThreadStoreError &&
5555
+ error.cause instanceof AgentPersistenceCapacityError
5556
+ ? error.cause
5557
+ : new CoordinatorHalt(error),
5562
5558
  ),
5563
- ),
5564
- );
5559
+ );
5560
+
5561
+ return Option.isNone(first) ||
5562
+ (first.value instanceof ThreadStoreError &&
5563
+ first.value.cause instanceof AgentPersistenceCapacityError)
5564
+ ? failure
5565
+ : Effect.andThen(Ref.set(haltRef, first.value), failure);
5566
+ });
5565
5567
 
5566
5568
  // Infrastructure errors can be wrapped by broker/Tool APIs. The interpreter checks this
5567
5569
  // authority before semantic work; progress delivery is not a coordinator checkpoint.
5568
- const checkpoint = recordHalt(
5569
- Effect.gen(function* () {
5570
- const failure = yield* Ref.get(haltRef);
5570
+ const progress = progressWriters.get(ctx);
5571
5571
 
5572
- if (failure !== undefined) return yield* failure;
5573
- const progress = progressWriters.get(ctx);
5572
+ const checkProgress =
5573
+ progress === undefined
5574
+ ? Effect.void
5575
+ : progress.check(runId).pipe(Effect.provideService(CurrentRunWriter, ctx));
5574
5576
 
5575
- if (progress !== undefined)
5576
- yield* progress.check(runId).pipe(Effect.provideService(CurrentRunWriter, ctx));
5577
- }),
5577
+ const checkpoint = recordHalt(
5578
+ Effect.flatMap(Ref.get(haltRef), (failure) =>
5579
+ failure === undefined ? checkProgress : Effect.fail(failure),
5580
+ ),
5578
5581
  );
5579
5582
 
5580
5583
  // One readonly Turn may own its response without publishing it. Promotion and settlement
@@ -6065,9 +6068,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6065
6068
  // The interpreter supplies only closed siblings, in declaration order.
6066
6069
  // Per-call identities retain each result before the child suspension.
6067
6070
  for (const result of toolResults) {
6068
- const batch = yield* withCrypto(
6069
- turnResultsBatch({ ...input, toolResults: [result] }),
6070
- );
6071
+ const batch = yield* turnResultsBatch({ ...input, toolResults: [result] });
6071
6072
 
6072
6073
  yield* appendBatch(
6073
6074
  ctx,
@@ -6100,47 +6101,45 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6100
6101
  turnId: commit.turnId,
6101
6102
  // The journal owns encoded messages. Detach the remaining small metadata
6102
6103
  // trees too: Schema validation alone is not an ownership transfer.
6103
- batch: yield* withCrypto(
6104
- turnCanonicalBatch({
6105
- ...input,
6106
- toolParameterRejections: input.toolParameterRejections?.map((rejection) => ({
6107
- ...rejection,
6108
- parameters: copyJson(rejection.parameters),
6109
- error: { ...rejection.error, reason: { ...rejection.error.reason } },
6110
- })),
6111
- toolExposure:
6112
- input.toolExposure === undefined
6113
- ? undefined
6114
- : Snapshot.make({
6115
- exposedToolNames: [...input.toolExposure.exposedToolNames],
6116
- ...(input.toolExposure.selection === undefined
6117
- ? {}
6118
- : {
6119
- selection: Selection.make({
6120
- toolNames: [...input.toolExposure.selection.toolNames],
6121
- }),
6104
+ batch: yield* turnCanonicalBatch({
6105
+ ...input,
6106
+ toolParameterRejections: input.toolParameterRejections?.map((rejection) => ({
6107
+ ...rejection,
6108
+ parameters: copyJson(rejection.parameters),
6109
+ error: { ...rejection.error, reason: { ...rejection.error.reason } },
6110
+ })),
6111
+ toolExposure:
6112
+ input.toolExposure === undefined
6113
+ ? undefined
6114
+ : Snapshot.make({
6115
+ exposedToolNames: [...input.toolExposure.exposedToolNames],
6116
+ ...(input.toolExposure.selection === undefined
6117
+ ? {}
6118
+ : {
6119
+ selection: Selection.make({
6120
+ toolNames: [...input.toolExposure.selection.toolNames],
6122
6121
  }),
6123
- }),
6124
- usage:
6125
- input.usage === undefined
6126
- ? undefined
6127
- : {
6128
- ...input.usage,
6129
- modelUsage: input.usage.modelUsage?.map((usage) =>
6130
- ModelCallUsage.make({
6131
- ...usage,
6132
- ...(usage.response === undefined
6133
- ? {}
6134
- : {
6135
- response: ModelResponseIdentity.make({ ...usage.response }),
6136
- }),
6137
- inputTokens: InputTokenUsage.make({ ...usage.inputTokens }),
6138
- outputTokens: OutputTokenUsage.make({ ...usage.outputTokens }),
6139
6122
  }),
6140
- ),
6141
- },
6142
- }),
6143
- ),
6123
+ }),
6124
+ usage:
6125
+ input.usage === undefined
6126
+ ? undefined
6127
+ : {
6128
+ ...input.usage,
6129
+ modelUsage: input.usage.modelUsage?.map((usage) =>
6130
+ ModelCallUsage.make({
6131
+ ...usage,
6132
+ ...(usage.response === undefined
6133
+ ? {}
6134
+ : {
6135
+ response: ModelResponseIdentity.make({ ...usage.response }),
6136
+ }),
6137
+ inputTokens: InputTokenUsage.make({ ...usage.inputTokens }),
6138
+ outputTokens: OutputTokenUsage.make({ ...usage.outputTokens }),
6139
+ }),
6140
+ ),
6141
+ },
6142
+ }),
6144
6143
  };
6145
6144
 
6146
6145
  const progress = progressWriters.get(ctx);
@@ -6163,17 +6162,15 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6163
6162
  let batch: CanonicalBatch;
6164
6163
 
6165
6164
  if (deferred === undefined) {
6166
- batch = yield* withCrypto(
6167
- commit._tag === "Response"
6168
- ? turnResponseBatch(input)
6169
- : response === undefined
6170
- ? turnResultsBatch(input)
6171
- : turnCanonicalBatch(input),
6172
- );
6165
+ batch = yield* commit._tag === "Response"
6166
+ ? turnResponseBatch(input)
6167
+ : response === undefined
6168
+ ? turnResultsBatch(input)
6169
+ : turnCanonicalBatch(input);
6173
6170
  } else if (toolResults.length === 0 && runCompletion === undefined) {
6174
6171
  batch = deferred.batch;
6175
6172
  } else {
6176
- const results = yield* withCrypto(turnResultsBatch(input));
6173
+ const results = yield* turnResultsBatch(input);
6177
6174
 
6178
6175
  batch = CanonicalBatch.make({
6179
6176
  ...deferred.batch,
@@ -6347,214 +6344,208 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6347
6344
  .get(ctx)
6348
6345
  ?.stageUsage(runId, terminalUsageCharge(uncommittedModelUsage()));
6349
6346
  }),
6350
- commitCompaction: (commit) =>
6351
- Effect.gen(function* () {
6352
- const canonicalTurn = commit.turn;
6353
- const recordId = compactionRecordId(runId, canonicalTurn, commit.kind);
6354
-
6355
- if (knownIds.has(recordId)) {
6356
- return yield* CompactionError.make({
6357
- message: "Compaction is already committed for this Turn and kind",
6358
- });
6359
- }
6347
+ commitCompaction: Effect.fnUntraced(function* (commit) {
6348
+ const canonicalTurn = commit.turn;
6349
+ const recordId = compactionRecordId(runId, canonicalTurn, commit.kind);
6360
6350
 
6361
- let sourceJournal = journal;
6362
- let sourceBoundaries = boundaries;
6351
+ if (knownIds.has(recordId)) {
6352
+ return yield* CompactionError.make({
6353
+ message: "Compaction is already committed for this Turn and kind",
6354
+ });
6355
+ }
6363
6356
 
6364
- if (initialJournal !== undefined) {
6365
- initialJournal.cacheable = false;
6366
- initialJournal.history = undefined;
6367
- }
6368
- if (commit.kind !== "summarize" || initialJournal?.prefix !== undefined) {
6369
- // Results must be canonical before pruning or rollover can cover them. Newly committed
6370
- // compactions remain overlays on this Attempt's append-only source, so omit those
6371
- // overlays while reconstructing the exact source-to-record mapping.
6372
- const tail = yield* ctx.tail;
6373
-
6374
- const source =
6375
- initialJournal === undefined
6376
- ? canonical
6377
- : Stream.fromIterable(
6378
- yield* recordHalt(
6379
- readInitialJournal(initialJournal.original, initialJournal.suffix),
6380
- ),
6381
- );
6357
+ let sourceJournal = journal;
6358
+ let sourceBoundaries = boundaries;
6382
6359
 
6383
- sourceBoundaries = [];
6384
- sourceJournal = yield* recordHalt(
6385
- projectRunJournalStream(
6386
- Stream.concat(
6387
- source,
6388
- Stream.fromIterable(
6389
- yield* recordHalt(
6390
- selectedRange(
6391
- ctx.threadId,
6392
- [submissionId],
6393
- tail.sequence,
6394
- canonicalThrough,
6395
- ),
6396
- ),
6360
+ if (initialJournal !== undefined) {
6361
+ initialJournal.cacheable = false;
6362
+ initialJournal.history = undefined;
6363
+ }
6364
+ if (commit.kind !== "summarize" || initialJournal?.prefix !== undefined) {
6365
+ // Results must be canonical before pruning or rollover can cover them. Newly committed
6366
+ // compactions remain overlays on this Attempt's append-only source, so omit those
6367
+ // overlays while reconstructing the exact source-to-record mapping.
6368
+ const tail = yield* ctx.tail;
6369
+
6370
+ const source =
6371
+ initialJournal === undefined
6372
+ ? canonical
6373
+ : Stream.fromIterable(
6374
+ yield* recordHalt(
6375
+ readInitialJournal(initialJournal.original, initialJournal.suffix),
6397
6376
  ),
6398
- ).pipe(
6399
- Stream.filter(
6400
- (envelope) =>
6401
- envelope.record.payload._tag !== "CompactionCreated" ||
6402
- envelope.sequence <= canonicalThrough,
6377
+ );
6378
+
6379
+ sourceBoundaries = [];
6380
+ sourceJournal = yield* recordHalt(
6381
+ projectRunJournalStream(
6382
+ Stream.concat(
6383
+ source,
6384
+ Stream.fromIterable(
6385
+ yield* recordHalt(
6386
+ selectedRange(ctx.threadId, [submissionId], tail.sequence, canonicalThrough),
6403
6387
  ),
6404
6388
  ),
6405
- runId,
6406
- (boundary) => sourceBoundaries.push(boundary),
6407
- priorContext,
6389
+ ).pipe(
6390
+ Stream.filter(
6391
+ (envelope) =>
6392
+ envelope.record.payload._tag !== "CompactionCreated" ||
6393
+ envelope.sequence <= canonicalThrough,
6394
+ ),
6408
6395
  ),
6409
- );
6410
- }
6411
-
6412
- // Summaries use the initial prior-Run boundaries; pruning and rollover also admit
6413
- // complete current-Run boundaries from the fresh canonical source above.
6414
- const coverable = sourceBoundaries.filter(
6415
- (boundary) =>
6416
- commit.kind !== "summarize" ||
6417
- ownerFirstSequence === undefined ||
6418
- boundary.sequence < ownerFirstSequence,
6419
- );
6420
-
6421
- if (coverable.length === 0) {
6422
- return yield* CompactionError.make({
6423
- message:
6424
- commit.kind === "rollover"
6425
- ? "Durable rollover requires complete canonical records"
6426
- : "Durable compaction requires eligible canonical records",
6427
- });
6428
- }
6429
-
6430
- const comparisonView = instructionView(
6431
- sourceJournal.prompt.content,
6432
- initialInstructions,
6433
- false,
6434
- sourceJournal.historyBefore.content.length,
6396
+ runId,
6397
+ (boundary) => sourceBoundaries.push(boundary),
6398
+ priorContext,
6399
+ ),
6435
6400
  );
6401
+ }
6436
6402
 
6437
- // Compare each visible message once. A transformed source can authorize only its
6438
- // exact canonical prefix; no per-candidate rereads or full encoded Prompt copies.
6439
- let matchingPrefix = 0;
6440
- let requiredThrough = 0;
6441
- const comparisonLimit = Math.min(comparisonView.messages.length, commit.through);
6403
+ // Summaries use the initial prior-Run boundaries; pruning and rollover also admit
6404
+ // complete current-Run boundaries from the fresh canonical source above.
6405
+ const coverable = sourceBoundaries.filter(
6406
+ (boundary) =>
6407
+ commit.kind !== "summarize" ||
6408
+ ownerFirstSequence === undefined ||
6409
+ boundary.sequence < ownerFirstSequence,
6410
+ );
6442
6411
 
6443
- for (let index = 0; index < commit.through; index += 1) {
6444
- const message = commit.source.content[index];
6412
+ if (coverable.length === 0) {
6413
+ return yield* CompactionError.make({
6414
+ message:
6415
+ commit.kind === "rollover"
6416
+ ? "Durable rollover requires complete canonical records"
6417
+ : "Durable compaction requires eligible canonical records",
6418
+ });
6419
+ }
6445
6420
 
6446
- if (
6447
- message !== undefined &&
6448
- (commit.kind === "clear-tool-results"
6449
- ? message.role === "tool"
6450
- : message.role !== "system")
6451
- )
6452
- requiredThrough = index + 1;
6453
- if (index >= comparisonLimit || index !== matchingPrefix || message === undefined)
6454
- continue;
6455
- const canonicalMessage = comparisonView.messages[index];
6456
-
6457
- if (canonicalMessage === undefined) continue;
6458
-
6459
- // Preserve Prompt's JSON projection while ignoring persisted object-key order.
6460
- const encode = (entry: Prompt.Message) =>
6461
- encodeCompactionMessageJson(entry).pipe(
6462
- Effect.flatMap(decodeCompactionMessageJson),
6463
- Effect.map(canonicalJson),
6464
- Effect.mapError((cause) =>
6465
- CompactionError.make({
6466
- message: "Could not encode the canonical compaction prefix",
6467
- cause,
6468
- }),
6469
- ),
6470
- );
6421
+ const comparisonView = instructionView(
6422
+ sourceJournal.prompt.content,
6423
+ initialInstructions,
6424
+ false,
6425
+ sourceJournal.historyBefore.content.length,
6426
+ );
6471
6427
 
6472
- const left = yield* encode(canonicalMessage);
6473
- const right = yield* encode(message);
6428
+ // Compare each visible message once. A transformed source can authorize only its
6429
+ // exact canonical prefix; no per-candidate rereads or full encoded Prompt copies.
6430
+ let matchingPrefix = 0;
6431
+ let requiredThrough = 0;
6432
+ const comparisonLimit = Math.min(comparisonView.messages.length, commit.through);
6474
6433
 
6475
- if (left === right) matchingPrefix += 1;
6476
- }
6434
+ for (let index = 0; index < commit.through; index += 1) {
6435
+ const message = commit.source.content[index];
6477
6436
 
6478
- let lastCovered: JournalBoundary | undefined;
6437
+ if (
6438
+ message !== undefined &&
6439
+ (commit.kind === "clear-tool-results"
6440
+ ? message.role === "tool"
6441
+ : message.role !== "system")
6442
+ )
6443
+ requiredThrough = index + 1;
6444
+ if (index >= comparisonLimit || index !== matchingPrefix || message === undefined)
6445
+ continue;
6446
+ const canonicalMessage = comparisonView.messages[index];
6447
+
6448
+ if (canonicalMessage === undefined) continue;
6449
+
6450
+ // Preserve Prompt's JSON projection while ignoring persisted object-key order.
6451
+ const encode = (entry: Prompt.Message) =>
6452
+ encodeCompactionMessageJson(entry).pipe(
6453
+ Effect.flatMap(decodeCompactionMessageJson),
6454
+ Effect.map(canonicalJson),
6455
+ Effect.mapError((cause) =>
6456
+ CompactionError.make({
6457
+ message: "Could not encode the canonical compaction prefix",
6458
+ cause,
6459
+ }),
6460
+ ),
6461
+ );
6479
6462
 
6480
- for (let index = 0; index < coverable.length; index += 1) {
6481
- const candidate = coverable[index];
6463
+ const left = yield* encode(canonicalMessage);
6464
+ const right = yield* encode(message);
6482
6465
 
6483
- if (candidate === undefined) continue;
6484
- const following = coverable[index + 1];
6466
+ if (left === right) matchingPrefix += 1;
6467
+ }
6485
6468
 
6486
- if (following?.tag === "ToolCallSettled") continue;
6469
+ let lastCovered: JournalBoundary | undefined;
6487
6470
 
6488
- const length = comparisonView.prefixLength(candidate.promptLength);
6471
+ for (let index = 0; index < coverable.length; index += 1) {
6472
+ const candidate = coverable[index];
6489
6473
 
6490
- if (length === 0 || length > commit.through) continue;
6491
- if (length < requiredThrough || length > matchingPrefix) continue;
6492
- lastCovered = candidate;
6493
- }
6494
- if (lastCovered === undefined) {
6495
- return yield* CompactionError.make({
6496
- message:
6497
- commit.kind === "rollover"
6498
- ? "Rollover coverage cannot be mapped to complete canonical records"
6499
- : "Compaction coverage cannot be mapped to complete canonical records",
6500
- });
6501
- }
6502
- const coveredSequence = lastCovered.sequence;
6474
+ if (candidate === undefined) continue;
6475
+ const following = coverable[index + 1];
6503
6476
 
6504
- if (
6505
- commit.kind !== "summarize" &&
6506
- sourceBoundaries.some(
6507
- (boundary) =>
6508
- boundary.incomplete === true &&
6509
- boundary.terminalPriorRun !== true &&
6510
- boundary.sequence <= coveredSequence,
6511
- )
6512
- ) {
6513
- return yield* CompactionError.make({
6514
- message: "Compaction cannot cover an incomplete Tool batch",
6515
- });
6516
- }
6517
- if (commit.kind === "summarize" && (commit.summary ?? "").trim().length === 0) {
6518
- return yield* CompactionError.make({
6519
- message: "A summarize compaction commit carried no summary",
6520
- });
6521
- }
6477
+ if (following?.tag === "ToolCallSettled") continue;
6522
6478
 
6523
- const payload = yield* Schema.decodeEffect(CompactionCreated)({
6524
- _tag: "CompactionCreated",
6525
- runId,
6526
- turn: canonicalTurn,
6527
- kind: commit.kind,
6528
- coversThrough: lastCovered.sequence,
6529
- ...(commit.kind === "summarize" ? { summary: commit.summary } : {}),
6530
- ...(commit.kind === "rollover" && commit.handoff !== undefined
6531
- ? { handoff: commit.handoff }
6532
- : {}),
6533
- }).pipe(
6534
- Effect.mapError((cause) =>
6535
- CompactionError.make({
6536
- message: "Compaction decision exceeds canonical persistence bounds",
6537
- cause,
6538
- }),
6539
- ),
6540
- );
6479
+ const length = comparisonView.prefixLength(candidate.promptLength);
6541
6480
 
6542
- const envelope = yield* recordHalt(makeEnvelope(recordId, payload));
6481
+ if (length === 0 || length > commit.through) continue;
6482
+ if (length < requiredThrough || length > matchingPrefix) continue;
6483
+ lastCovered = candidate;
6484
+ }
6485
+ if (lastCovered === undefined) {
6486
+ return yield* CompactionError.make({
6487
+ message:
6488
+ commit.kind === "rollover"
6489
+ ? "Rollover coverage cannot be mapped to complete canonical records"
6490
+ : "Compaction coverage cannot be mapped to complete canonical records",
6491
+ });
6492
+ }
6493
+ const coveredSequence = lastCovered.sequence;
6543
6494
 
6544
- yield* recordHalt(hit("compaction:before-canonical-append"));
6545
- yield* recordHalt(
6546
- appendBatch(
6547
- ctx,
6548
- CanonicalBatch.make({
6549
- batchId: compactionBatchId(runId, canonicalTurn, commit.kind),
6550
- producerId: config.producerId,
6551
- records: [envelope],
6552
- }),
6553
- ),
6554
- );
6555
- knownIds.add(recordId);
6556
- yield* recordHalt(hit("compaction:after-canonical-append"));
6557
- }),
6495
+ if (
6496
+ commit.kind !== "summarize" &&
6497
+ sourceBoundaries.some(
6498
+ (boundary) =>
6499
+ boundary.incomplete === true &&
6500
+ boundary.terminalPriorRun !== true &&
6501
+ boundary.sequence <= coveredSequence,
6502
+ )
6503
+ ) {
6504
+ return yield* CompactionError.make({
6505
+ message: "Compaction cannot cover an incomplete Tool batch",
6506
+ });
6507
+ }
6508
+ if (commit.kind === "summarize" && (commit.summary ?? "").trim().length === 0) {
6509
+ return yield* CompactionError.make({
6510
+ message: "A summarize compaction commit carried no summary",
6511
+ });
6512
+ }
6513
+
6514
+ const payload = yield* Schema.decodeEffect(CompactionCreated)({
6515
+ _tag: "CompactionCreated",
6516
+ runId,
6517
+ turn: canonicalTurn,
6518
+ kind: commit.kind,
6519
+ coversThrough: lastCovered.sequence,
6520
+ ...(commit.kind === "summarize" ? { summary: commit.summary } : {}),
6521
+ ...(commit.kind === "rollover" && commit.handoff !== undefined
6522
+ ? { handoff: commit.handoff }
6523
+ : {}),
6524
+ }).pipe(
6525
+ Effect.mapError((cause) =>
6526
+ CompactionError.make({
6527
+ message: "Compaction decision exceeds canonical persistence bounds",
6528
+ cause,
6529
+ }),
6530
+ ),
6531
+ );
6532
+
6533
+ const envelope = yield* recordHalt(makeEnvelope(recordId, payload));
6534
+
6535
+ yield* recordHalt(hit("compaction:before-canonical-append"));
6536
+ yield* recordHalt(
6537
+ appendBatch(
6538
+ ctx,
6539
+ CanonicalBatch.make({
6540
+ batchId: compactionBatchId(runId, canonicalTurn, commit.kind),
6541
+ producerId: config.producerId,
6542
+ records: [envelope],
6543
+ }),
6544
+ ),
6545
+ );
6546
+ knownIds.add(recordId);
6547
+ yield* recordHalt(hit("compaction:after-canonical-append"));
6548
+ }),
6558
6549
  };
6559
6550
 
6560
6551
  /** Digest of one declared call's canonical encoded parameters (the original normalized wire). */
@@ -6803,37 +6794,34 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6803
6794
  * so the next Turn's response commit makes it model-visible canonically — exactly the
6804
6795
  * prompt-coverage rule recovery relies on.
6805
6796
  */
6806
- const renderJoinedInput = ({
6807
- input: encodedInput,
6808
- messageAdmission,
6809
- }: Pick<UserInputRecorded, "input" | "messageAdmission">) =>
6810
- Effect.gen(function* () {
6811
- if (Schema.is(FrameworkMessage)(messageAdmission))
6812
- return yield* Schema.encodeEffect(Schema.fromJsonString(FrameworkMessage))(
6813
- messageAdmission,
6814
- ).pipe(
6815
- Effect.mapError(() =>
6816
- AgentInputError.make({ message: "Invalid worker completion message" }),
6817
- ),
6818
- );
6819
- const inputPrompt = agent.definition.inputPrompt;
6820
-
6821
- if (inputPrompt === undefined) {
6822
- return yield* renderInputPrompt(undefined, encodedInput, encodedInput);
6823
- }
6824
-
6825
- const decodedInput = yield* Schema.decodeEffect(agent.definition.input)(
6826
- encodedInput,
6797
+ const renderJoinedInput = Effect.fnUntraced(function* (
6798
+ encodedInput: UserInputRecorded["input"],
6799
+ messageAdmission: UserInputRecorded["messageAdmission"],
6800
+ ) {
6801
+ if (Schema.is(FrameworkMessage)(messageAdmission))
6802
+ return yield* Schema.encodeEffect(Schema.fromJsonString(FrameworkMessage))(
6803
+ messageAdmission,
6827
6804
  ).pipe(
6828
- Effect.mapError((cause) =>
6829
- AgentInputDecodeError.make({
6830
- message: cause.message,
6831
- }),
6805
+ Effect.mapError(() =>
6806
+ AgentInputError.make({ message: "Invalid worker completion message" }),
6832
6807
  ),
6833
6808
  );
6809
+ const inputPrompt = agent.definition.inputPrompt;
6834
6810
 
6835
- return yield* renderInputPrompt(inputPrompt, decodedInput, encodedInput);
6836
- });
6811
+ if (inputPrompt === undefined) {
6812
+ return yield* renderInputPrompt(undefined, encodedInput, encodedInput);
6813
+ }
6814
+
6815
+ const decodedInput = yield* Schema.decodeEffect(agent.definition.input)(encodedInput).pipe(
6816
+ Effect.mapError((cause) =>
6817
+ AgentInputDecodeError.make({
6818
+ message: cause.message,
6819
+ }),
6820
+ ),
6821
+ );
6822
+
6823
+ return yield* renderInputPrompt(inputPrompt, decodedInput, encodedInput);
6824
+ });
6837
6825
 
6838
6826
  // Claims are authority, wakes are hints. A cancelled waiter retains every claim
6839
6827
  // for the seam drain; it never appends input while an old response can still commit.
@@ -6892,7 +6880,9 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6892
6880
  : { messageAdmission: snapshot.submission.messageAdmission }),
6893
6881
  };
6894
6882
 
6895
- const rendered = yield* Effect.result(renderJoinedInput(input));
6883
+ const rendered = yield* Effect.result(
6884
+ renderJoinedInput(input.input, input.messageAdmission),
6885
+ );
6896
6886
 
6897
6887
  if (Result.isFailure(rendered)) {
6898
6888
  // A rejected prompt and its suffix retain their own Runs in queue order.
@@ -6932,118 +6922,117 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6932
6922
  Agent.DefinitionRequirements<(typeof agent)["definition"]>
6933
6923
  > = {
6934
6924
  awaitJoin,
6935
- drain: (policy) =>
6936
- Effect.gen(function* () {
6937
- const joinedInputs = yield* recordHalt(
6938
- Effect.gen(function* () {
6939
- const limit = policy === "one" ? 1 : MAX_JOIN_DRAIN;
6925
+ drain: Effect.fnUntraced(function* (policy) {
6926
+ const joinedInputs = yield* recordHalt(
6927
+ Effect.gen(function* () {
6928
+ const limit = policy === "one" ? 1 : MAX_JOIN_DRAIN;
6940
6929
 
6941
- const joinedInputs: Array<
6942
- Pick<UserInputRecorded, "input" | "messageAdmission"> & {
6943
- readonly rendered?: Prompt.RawInput;
6944
- }
6945
- > = [];
6930
+ const joinedInputs: Array<
6931
+ Pick<UserInputRecorded, "input" | "messageAdmission"> & {
6932
+ readonly rendered?: Prompt.RawInput;
6933
+ }
6934
+ > = [];
6946
6935
 
6947
- if (joinBacklog === undefined) {
6948
- const hostSnapshot = yield* ledger.loadRecoverySnapshot(
6949
- RecoverySnapshotRequest.make({ submissionId }),
6950
- );
6936
+ if (joinBacklog === undefined) {
6937
+ const hostSnapshot = yield* ledger.loadRecoverySnapshot(
6938
+ RecoverySnapshotRequest.make({ submissionId }),
6939
+ );
6951
6940
 
6952
- joinBacklog = hostSnapshot.joins;
6941
+ joinBacklog = hostSnapshot.joins;
6942
+ }
6943
+ // These inputs were already consumed by an earlier attempt. Restore the
6944
+ // entire uncovered prompt; only fresh claims are subject to the drain bound.
6945
+ for (const join of joinBacklog) {
6946
+ const joinId = join.submissionId;
6947
+
6948
+ if (deliveredJoinInputs.has(joinId)) continue;
6949
+ if (join.state !== "joining" && join.state !== "joined") continue;
6950
+ const existing = joinedInputEnvelopes.get(joinId);
6951
+
6952
+ if (existing === undefined) continue;
6953
+ if (join.state === "joining") {
6954
+ // Canonical input without its joined marker (crash between the append and
6955
+ // `markJoined`): repair the marker from history before reattaching.
6956
+ yield* session.markJoined(joinId, {
6957
+ recordId: existing.record.recordId,
6958
+ sequence: existing.sequence,
6959
+ });
6953
6960
  }
6954
- // These inputs were already consumed by an earlier attempt. Restore the
6955
- // entire uncovered prompt; only fresh claims are subject to the drain bound.
6956
- for (const join of joinBacklog) {
6957
- const joinId = join.submissionId;
6958
-
6959
- if (deliveredJoinInputs.has(joinId)) continue;
6960
- if (join.state !== "joining" && join.state !== "joined") continue;
6961
- const existing = joinedInputEnvelopes.get(joinId);
6962
-
6963
- if (existing === undefined) continue;
6964
- if (join.state === "joining") {
6965
- // Canonical input without its joined marker (crash between the append and
6966
- // `markJoined`): repair the marker from history before reattaching.
6967
- yield* session.markJoined(joinId, {
6968
- recordId: existing.record.recordId,
6969
- sequence: existing.sequence,
6970
- });
6971
- }
6972
- deliveredJoinInputs.add(joinId);
6973
- if (
6974
- lastHostResponseSequence !== undefined &&
6975
- lastHostResponseSequence > existing.sequence
6976
- ) {
6977
- continue;
6978
- }
6979
- const payload = existing.record.payload;
6980
-
6981
- if (payload._tag !== "UserInputRecorded") continue;
6982
- joinedInputs.push(payload);
6961
+ deliveredJoinInputs.add(joinId);
6962
+ if (
6963
+ lastHostResponseSequence !== undefined &&
6964
+ lastHostResponseSequence > existing.sequence
6965
+ ) {
6966
+ continue;
6983
6967
  }
6984
- const claims = yield* prepareInputs(limit);
6985
-
6986
- pendingJoinClaims.splice(0, claims.length);
6987
- for (const { claim, input: payload, rendered } of claims) {
6988
- const recordId = submissionInputRecordId(claim.submissionId);
6989
- let sequence: CanonicalSequence;
6990
- const existing = joinedInputEnvelopes.get(claim.submissionId);
6991
-
6992
- if (existing !== undefined) {
6993
- // Defensive reattach: the exact record is already canonical, so only the
6994
- // marker and the delivery remain (DUR-016 — never a duplicate append).
6995
- sequence = existing.sequence;
6996
- } else {
6997
- const envelope = yield* makeEnvelope(
6998
- recordId,
6999
- UserInputRecorded.make({
7000
- submissionId: claim.submissionId,
7001
- kind: "steering",
7002
- runId,
7003
- input: claim.inputPayload,
7004
- ...(payload.messageAdmission === undefined
7005
- ? {}
7006
- : { messageAdmission: payload.messageAdmission }),
7007
- }),
7008
- );
6968
+ const payload = existing.record.payload;
7009
6969
 
7010
- const result = yield* appendBatch(
7011
- ctx,
7012
- CanonicalBatch.make({
7013
- batchId: submissionInputBatchId(claim.submissionId),
7014
- producerId: config.producerId,
7015
- records: [envelope],
7016
- }),
7017
- );
6970
+ if (payload._tag !== "UserInputRecorded") continue;
6971
+ joinedInputs.push(payload);
6972
+ }
6973
+ const claims = yield* prepareInputs(limit);
6974
+
6975
+ pendingJoinClaims.splice(0, claims.length);
6976
+ for (const { claim, input: payload, rendered } of claims) {
6977
+ const recordId = submissionInputRecordId(claim.submissionId);
6978
+ let sequence: CanonicalSequence;
6979
+ const existing = joinedInputEnvelopes.get(claim.submissionId);
6980
+
6981
+ if (existing !== undefined) {
6982
+ // Defensive reattach: the exact record is already canonical, so only the
6983
+ // marker and the delivery remain (DUR-016 — never a duplicate append).
6984
+ sequence = existing.sequence;
6985
+ } else {
6986
+ const envelope = yield* makeEnvelope(
6987
+ recordId,
6988
+ UserInputRecorded.make({
6989
+ submissionId: claim.submissionId,
6990
+ kind: "steering",
6991
+ runId,
6992
+ input: claim.inputPayload,
6993
+ ...(payload.messageAdmission === undefined
6994
+ ? {}
6995
+ : { messageAdmission: payload.messageAdmission }),
6996
+ }),
6997
+ );
7018
6998
 
7019
- sequence = result.firstSequence;
7020
- knownIds.add(recordId);
7021
- yield* hit("join:after-canonical-append");
7022
- }
7023
- yield* session.markJoined(claim.submissionId, { recordId, sequence });
7024
- deliveredJoinInputs.add(claim.submissionId);
7025
- joinedInputs.push({
7026
- ...payload,
7027
- rendered,
7028
- });
6999
+ const result = yield* appendBatch(
7000
+ ctx,
7001
+ CanonicalBatch.make({
7002
+ batchId: submissionInputBatchId(claim.submissionId),
7003
+ producerId: config.producerId,
7004
+ records: [envelope],
7005
+ }),
7006
+ );
7007
+
7008
+ sequence = result.firstSequence;
7009
+ knownIds.add(recordId);
7010
+ yield* hit("join:after-canonical-append");
7029
7011
  }
7012
+ yield* session.markJoined(claim.submissionId, { recordId, sequence });
7013
+ deliveredJoinInputs.add(claim.submissionId);
7014
+ joinedInputs.push({
7015
+ ...payload,
7016
+ rendered,
7017
+ });
7018
+ }
7030
7019
 
7031
- return joinedInputs;
7032
- }),
7033
- );
7020
+ return joinedInputs;
7021
+ }),
7022
+ );
7034
7023
 
7035
- return yield* Effect.forEach(joinedInputs, (joinedInput) =>
7036
- (joinedInput.rendered === undefined
7037
- ? renderJoinedInput(joinedInput)
7038
- : Effect.succeed(joinedInput.rendered)
7039
- ).pipe(
7040
- Effect.map((input): RunInputCommand => ({
7041
- kind: "steering",
7042
- input,
7043
- })),
7044
- ),
7045
- );
7046
- }),
7024
+ return yield* Effect.forEach(joinedInputs, (joinedInput) =>
7025
+ (joinedInput.rendered === undefined
7026
+ ? renderJoinedInput(joinedInput.input, joinedInput.messageAdmission)
7027
+ : Effect.succeed(joinedInput.rendered)
7028
+ ).pipe(
7029
+ Effect.map((input): RunInputCommand => ({
7030
+ kind: "steering",
7031
+ input,
7032
+ })),
7033
+ ),
7034
+ );
7035
+ }),
7047
7036
  };
7048
7037
 
7049
7038
  /**
@@ -7726,50 +7715,49 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
7726
7715
  },
7727
7716
  }),
7728
7717
  ...(preparedContext === undefined ? {} : { context: preparedContext }),
7729
- beforeTurn: () =>
7730
- Effect.gen(function* () {
7718
+ beforeTurn: Effect.fnUntraced(function* () {
7719
+ if (
7720
+ yieldAfter !== undefined &&
7721
+ (yield* Clock.currentTimeMillis) >= DateTime.toEpochMillis(yieldAfter)
7722
+ ) {
7723
+ yield* Deferred.succeed(yieldSignal, undefined);
7724
+
7725
+ // The successful signal wins the outer race and closes the execution Scope;
7726
+ // no synthetic engine RunFailed event or terminal Settlement is produced.
7727
+ return yield* Effect.never;
7728
+ }
7729
+ if (submissionScheduling.yieldTo !== undefined && (yield* Ref.get(turnCounter)) > 0) {
7730
+ const candidate = yield* recordHalt(
7731
+ eligibleThreadHead(submission.threadId, submission.queueSequence),
7732
+ );
7733
+
7731
7734
  if (
7732
- yieldAfter !== undefined &&
7733
- (yield* Clock.currentTimeMillis) >= DateTime.toEpochMillis(yieldAfter)
7735
+ Option.isSome(candidate) &&
7736
+ (candidate.value.state === "ready" || candidate.value.state === "input-applied")
7734
7737
  ) {
7735
- yield* Deferred.succeed(yieldSignal, undefined);
7736
-
7737
- // The successful signal wins the outer race and closes the execution Scope;
7738
- // no synthetic engine RunFailed event or terminal Settlement is produced.
7739
- return yield* Effect.never;
7740
- }
7741
- if (submissionScheduling.yieldTo !== undefined && (yield* Ref.get(turnCounter)) > 0) {
7742
- const candidate = yield* recordHalt(
7743
- eligibleThreadHead(submission.threadId, submission.queueSequence),
7738
+ const next = yield* recordHalt(
7739
+ ledger.lookup(
7740
+ SubmissionLookupById.make({
7741
+ submissionId: candidate.value.submissionId,
7742
+ }),
7743
+ ),
7744
7744
  );
7745
7745
 
7746
7746
  if (
7747
- Option.isSome(candidate) &&
7748
- (candidate.value.state === "ready" || candidate.value.state === "input-applied")
7747
+ Option.isSome(next) &&
7748
+ next.value.agentId === submission.agentId &&
7749
+ (next.value.state === "ready" || next.value.state === "input-applied") &&
7750
+ (yield* recordHalt(
7751
+ submissionScheduling.yieldTo({ active: submission, next: next.value }),
7752
+ ))
7749
7753
  ) {
7750
- const next = yield* recordHalt(
7751
- ledger.lookup(
7752
- SubmissionLookupById.make({
7753
- submissionId: candidate.value.submissionId,
7754
- }),
7755
- ),
7756
- );
7754
+ yield* Deferred.succeed(yieldSignal, next.value.submissionId);
7757
7755
 
7758
- if (
7759
- Option.isSome(next) &&
7760
- next.value.agentId === submission.agentId &&
7761
- (next.value.state === "ready" || next.value.state === "input-applied") &&
7762
- (yield* recordHalt(
7763
- submissionScheduling.yieldTo({ active: submission, next: next.value }),
7764
- ))
7765
- ) {
7766
- yield* Deferred.succeed(yieldSignal, next.value.submissionId);
7767
-
7768
- return yield* Effect.never;
7769
- }
7756
+ return yield* Effect.never;
7770
7757
  }
7771
7758
  }
7772
- }),
7759
+ }
7760
+ }),
7773
7761
  ...(runContextPreparation.transientContext === undefined
7774
7762
  ? {}
7775
7763
  : { transientContext: runContextPreparation.transientContext }),
@@ -7844,35 +7832,39 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
7844
7832
  stagedUnobservedCalls.set(turn, (stagedUnobservedCalls.get(turn) ?? 0) + 1);
7845
7833
  }),
7846
7834
  }),
7847
- Effect.provide(ThreadHistory.layer),
7848
- Effect.provideService(SubagentHost.forTool, (source) =>
7849
- source.threadId !== submission.threadId ||
7850
- source.agentId !== submission.agentId ||
7851
- source.runId !== runId
7852
- ? SubagentHost.unavailable
7853
- : workerRuntime.facet(
7854
- {
7855
- source,
7856
- policy: agent.definition.policy,
7857
- depth: delegationDepth,
7858
- ...(inheritedGrant === undefined ? {} : { grant: inheritedGrant }),
7859
- },
7860
- submission.principal,
7861
- submission.submissionId,
7862
- ),
7863
- ),
7864
- Effect.provideService(MessagingHost.forTool, (source) =>
7865
- source.threadId !== submission.threadId ||
7866
- source.agentId !== submission.agentId ||
7867
- source.runId !== runId
7868
- ? MessagingHost.unavailable
7869
- : messagingRuntime.forTool(source, submission.principal),
7835
+ (body) =>
7836
+ body.pipe(
7837
+ Effect.provideServiceEffect(ThreadHistory, ThreadHistory.makeFromStore),
7838
+ Effect.provideServiceEffect(ConversationStore, makeMemoryStore),
7839
+ Effect.scoped,
7840
+ ),
7841
+ Effect.provideContext(
7842
+ runServices.pipe(
7843
+ Context.add(SubagentHost.forTool, (source) =>
7844
+ source.threadId !== submission.threadId ||
7845
+ source.agentId !== submission.agentId ||
7846
+ source.runId !== runId
7847
+ ? SubagentHost.unavailable
7848
+ : workerRuntime.facet(
7849
+ {
7850
+ source,
7851
+ policy: agent.definition.policy,
7852
+ depth: delegationDepth,
7853
+ ...(inheritedGrant === undefined ? {} : { grant: inheritedGrant }),
7854
+ },
7855
+ submission.principal,
7856
+ submission.submissionId,
7857
+ ),
7858
+ ),
7859
+ Context.add(MessagingHost.forTool, (source) =>
7860
+ source.threadId !== submission.threadId ||
7861
+ source.agentId !== submission.agentId ||
7862
+ source.runId !== runId
7863
+ ? MessagingHost.unavailable
7864
+ : messagingRuntime.forTool(source, submission.principal),
7865
+ ),
7866
+ ),
7870
7867
  ),
7871
- Effect.provideService(CurrentToolFailureObserver, toolFailureObserver),
7872
- Effect.provideService(RunToolVisibility, runToolVisibility),
7873
- Effect.provideService(RunToolScheduling, runToolScheduling),
7874
- Effect.provideService(ContextCompactor, compactor),
7875
- Effect.provideService(RunContextPreparation, runContextPreparation),
7876
7868
  Effect.andThen(checkpoint),
7877
7869
  // A wrapped Tool/approval failure cannot outrank retained infrastructure authority.
7878
7870
  // Preserve independent defects, interruptions and already-marked coordinator failures.
@@ -7919,21 +7911,20 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
7919
7911
  }),
7920
7912
  );
7921
7913
 
7922
- const execution = Effect.raceFirst(consume, abortWatcher);
7923
-
7924
- const raced = (
7925
- yieldAfter === undefined && submissionScheduling.yieldTo === undefined
7926
- ? execution
7927
- : Effect.raceFirst(
7928
- execution,
7914
+ const raced = Effect.raceAllFirst([
7915
+ consume,
7916
+ abortWatcher,
7917
+ ...(yieldAfter === undefined && submissionScheduling.yieldTo === undefined
7918
+ ? []
7919
+ : [
7929
7920
  Deferred.await(yieldSignal).pipe(
7930
7921
  Effect.map((nextSubmissionId) => ({
7931
7922
  _tag: "yielded" as const,
7932
7923
  ...(nextSubmissionId === undefined ? {} : { nextSubmissionId }),
7933
7924
  })),
7934
7925
  ),
7935
- )
7936
- ).pipe(Effect.provideService(IdGenerator, idGenerator));
7926
+ ]),
7927
+ ]).pipe(Effect.provideService(IdGenerator, idGenerator));
7937
7928
 
7938
7929
  const result = yield* raced.pipe(
7939
7930
  Effect.catchCauseFilter(
@@ -8112,7 +8103,8 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8112
8103
  };
8113
8104
 
8114
8105
  return completed;
8115
- });
8106
+ },
8107
+ );
8116
8108
 
8117
8109
  /**
8118
8110
  * One ownership period over the claimed lane head. The step order mirrors the pure recovery
@@ -8121,53 +8113,51 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8121
8113
  * (Unknown Outcomes, or a durable approval suspension) — no settlement occurs and the
8122
8114
  * obligation stays owed.
8123
8115
  */
8124
- const runAttempt = <
8125
- InputSchema extends Schema.Top,
8126
- OutputSchema extends Schema.Top,
8127
- Instructions,
8128
- Tools extends Record<string, Tool.Any>,
8129
- Provider,
8130
- ModelProvides,
8131
- ModelRequires,
8132
- InstructionError = InstructionErrorOf<Instructions, InputSchema["Type"]>,
8133
- InstructionRequirements = InstructionRequirementsOf<Instructions, InputSchema["Type"]>,
8134
- RunDispositionValue extends
8135
- | RunDispositionDeclaration<OutputSchema["Type"], Schema.Top>
8136
- | undefined = undefined,
8137
- InputPromptValue extends InputPromptSource<InputSchema["Type"], unknown, unknown> | undefined =
8138
- undefined,
8139
- UpdatesSchema extends Schema.Top | undefined = undefined,
8140
- >(
8141
- registeredAgent: RuntimeBinding<
8142
- InputSchema,
8143
- OutputSchema,
8116
+ const runAttempt = Effect.fnUntraced(
8117
+ function* <
8118
+ InputSchema extends Schema.Top,
8119
+ OutputSchema extends Schema.Top,
8144
8120
  Instructions,
8145
- Tools,
8121
+ Tools extends Record<string, Tool.Any>,
8146
8122
  Provider,
8147
8123
  ModelProvides,
8148
8124
  ModelRequires,
8149
- InstructionError,
8150
- InstructionRequirements,
8151
- RunDispositionValue,
8152
- InputPromptValue,
8153
- UpdatesSchema
8154
- >,
8155
- session: RunStorageSession,
8156
- resumeAfterRetention: () => void,
8157
- onHandoff: (nextSubmissionId: SubmissionId) => void,
8158
- yieldAfter?: DateTime.Utc,
8159
- ) =>
8160
- Effect.gen(function* () {
8125
+ InstructionError = InstructionErrorOf<Instructions, InputSchema["Type"]>,
8126
+ InstructionRequirements = InstructionRequirementsOf<Instructions, InputSchema["Type"]>,
8127
+ RunDispositionValue extends
8128
+ | RunDispositionDeclaration<OutputSchema["Type"], Schema.Top>
8129
+ | undefined = undefined,
8130
+ InputPromptValue extends
8131
+ | InputPromptSource<InputSchema["Type"], unknown, unknown>
8132
+ | undefined = undefined,
8133
+ UpdatesSchema extends Schema.Top | undefined = undefined,
8134
+ >(
8135
+ registeredAgent: RuntimeBinding<
8136
+ InputSchema,
8137
+ OutputSchema,
8138
+ Instructions,
8139
+ Tools,
8140
+ Provider,
8141
+ ModelProvides,
8142
+ ModelRequires,
8143
+ InstructionError,
8144
+ InstructionRequirements,
8145
+ RunDispositionValue,
8146
+ InputPromptValue,
8147
+ UpdatesSchema
8148
+ >,
8149
+ session: RunStorageSession,
8150
+ resumeAfterRetention: () => void,
8151
+ onHandoff: (nextSubmissionId: SubmissionId) => void,
8152
+ yieldAfter?: DateTime.Utc,
8153
+ freshlyAdmitted = false,
8154
+ ) {
8161
8155
  // Recheck ownership outside the disposable cache's failure recovery.
8162
8156
  yield* session.checkActive;
8163
8157
 
8164
8158
  const checkpointPrompt = yield* promptHistory
8165
8159
  .take({ threadId: session.threadId })
8166
- .pipe(
8167
- Effect.provideService(ThreadStore, store),
8168
- Effect.provideService(ThreadReader, reader),
8169
- Effect.provideService(Crypto.Crypto, crypto),
8170
- );
8160
+ .pipe(Effect.provideService(ThreadStore, store), Effect.provideContext(journalServices));
8171
8161
 
8172
8162
  // withProgressSession exposes only progress.commit/publish. Their prepare step owns and
8173
8163
  // freezes each canonical fact before reaching this raw writer. Snapshot the private array
@@ -8181,7 +8171,8 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8181
8171
  committedPromptSuffix = undefined;
8182
8172
  }),
8183
8173
  );
8184
- session = yield* withProgressSession({
8174
+
8175
+ const promptSession: RunStorageSession = {
8185
8176
  ...storageSession,
8186
8177
  append: (batch, executions) => {
8187
8178
  const suffix = committedPromptSuffix;
@@ -8216,17 +8207,117 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8216
8207
  Effect.tapCause(() => Effect.sync(() => suffix.discard())),
8217
8208
  );
8218
8209
  },
8219
- });
8210
+ };
8211
+
8212
+ session = yield* withProgressSession(promptSession);
8220
8213
  const { claim, threadId } = session;
8221
8214
  const submissionId = claim.submissionId;
8222
8215
 
8223
- const snapshot = yield* ledger.loadRecoverySnapshot(
8224
- RecoverySnapshotRequest.make({ submissionId }),
8225
- );
8216
+ // The hint only selects this preparation. A real claim, current controls, exact
8217
+ // evidence and checkpoint absence are verified under its owner before any reuse.
8218
+ // No binding, instructions, model, Tool or cross-Thread operation runs in the window.
8219
+ let prepared =
8220
+ freshlyAdmitted && session.withInitialization !== undefined
8221
+ ? yield* session.withInitialization(
8222
+ Effect.gen(function* () {
8223
+ const snapshot = yield* ledger.loadRecoverySnapshot(
8224
+ RecoverySnapshotRequest.make({ submissionId }),
8225
+ );
8226
+
8227
+ const submission = snapshot.submission;
8228
+
8229
+ if (
8230
+ submission.state !== "ready" ||
8231
+ submission.workerAdmission !== undefined ||
8232
+ submission.messageAdmission !== undefined ||
8233
+ submission.parentLinkage !== undefined ||
8234
+ snapshot.inputApplied !== undefined ||
8235
+ snapshot.abortIntent !== undefined ||
8236
+ snapshot.approvalDecisions.length !== 0
8237
+ )
8238
+ return undefined;
8239
+ yield* ensureThreadCreated(threadId, submission.agentId, submission.agentDigests);
8240
+ yield* session.refresh;
8241
+ const before = (yield* session.tail).sequence;
8242
+
8243
+ const view = yield* recoveryView(
8244
+ threadId,
8245
+ before,
8246
+ [submissionId],
8247
+ runIdForSubmission(submissionId),
8248
+ );
8249
+
8250
+ if (
8251
+ view.progress !== undefined ||
8252
+ view.context !== undefined ||
8253
+ view.operational.length !== 0 ||
8254
+ view.records.some(({ record }) => record.payload._tag !== "ThreadCreated")
8255
+ )
8256
+ return undefined;
8257
+ const progress = progressWriters.get(session);
8258
+
8259
+ const applyInput = applyCanonicalInput(
8260
+ session,
8261
+ submission,
8262
+ session,
8263
+ view.records,
8264
+ undefined,
8265
+ );
8266
+
8267
+ yield* progress === undefined
8268
+ ? applyInput
8269
+ : progress
8270
+ .withVerifiedEmptyRun(runIdForSubmission(submissionId), before, applyInput)
8271
+ .pipe(Effect.provideService(CurrentRunWriter, promptSession));
8272
+ const through = (yield* session.tail).sequence;
8273
+ const added = yield* selectedRange(threadId, [submissionId], through, before);
8274
+ const records = [...view.records, ...added];
8275
+
8276
+ const accepted = progressWriters
8277
+ .get(session)
8278
+ ?.acceptedCheckpoint(runIdForSubmission(submissionId));
8279
+
8280
+ if (
8281
+ accepted === undefined ||
8282
+ accepted.throughSequence !== through ||
8283
+ accepted.progress.runId !== runIdForSubmission(submissionId) ||
8284
+ accepted.progress.submissionId !== submissionId
8285
+ )
8286
+ return undefined;
8287
+
8288
+ const start = yield* makeRunStart(
8289
+ submission,
8290
+ Duration.toMillis(registeredAgent.definition.policy.maxDuration),
8291
+ records,
8292
+ view.operational,
8293
+ ).pipe(Effect.provideService(CurrentRunWriter, session));
8294
+
8295
+ return {
8296
+ snapshot,
8297
+ through,
8298
+ view: {
8299
+ ...view,
8300
+ records,
8301
+ progress: accepted.progress,
8302
+ progressThrough: accepted.throughSequence,
8303
+ operational: accepted.operational,
8304
+ },
8305
+ start,
8306
+ };
8307
+ }),
8308
+ )
8309
+ : undefined;
8310
+
8311
+ const preparedInput = prepared !== undefined;
8312
+
8313
+ const snapshot =
8314
+ prepared?.snapshot ??
8315
+ (yield* ledger.loadRecoverySnapshot(RecoverySnapshotRequest.make({ submissionId })));
8226
8316
 
8227
8317
  const submission = snapshot.submission;
8228
8318
 
8229
- yield* ensureThreadCreated(threadId, submission.agentId, submission.agentDigests);
8319
+ if (prepared === undefined)
8320
+ yield* ensureThreadCreated(threadId, submission.agentId, submission.agentDigests);
8230
8321
  if (submission.workerAdmission?.origin.reporting?.mode === "standard")
8231
8322
  yield* updateRuntime.repairRun(threadId, submissionId);
8232
8323
  if (submission.workerAdmission !== undefined) {
@@ -8241,18 +8332,20 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8241
8332
  if (submission.state === "admitted") {
8242
8333
  yield* ledger.markReady(MarkReadyRequest.make({ submissionId }));
8243
8334
  }
8244
- yield* session.refresh;
8335
+ if (prepared === undefined) yield* session.refresh;
8245
8336
  const ctx: AttemptAppendContext = session;
8246
- let controlThrough = (yield* session.tail).sequence;
8337
+ let controlThrough = prepared?.through ?? (yield* session.tail).sequence;
8247
8338
 
8248
8339
  const initialThrough = controlThrough;
8249
8340
 
8250
- const initialView = yield* recoveryView(
8251
- threadId,
8252
- initialThrough,
8253
- [submissionId],
8254
- runIdForSubmission(submissionId),
8255
- );
8341
+ const initialView =
8342
+ prepared?.view ??
8343
+ (yield* recoveryView(
8344
+ threadId,
8345
+ initialThrough,
8346
+ [submissionId],
8347
+ runIdForSubmission(submissionId),
8348
+ ));
8256
8349
 
8257
8350
  let journalMetadata: ReturnType<typeof makeJournalMetadata> | undefined;
8258
8351
 
@@ -8273,9 +8366,8 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8273
8366
  return metadata;
8274
8367
  };
8275
8368
 
8276
- let records: ReadonlyArray<CanonicalRecordEnvelope> = yield* Stream.runCollect(
8277
- initialView.canonical.pipe(Stream.filter(collectControl)),
8278
- );
8369
+ let records: ReadonlyArray<CanonicalRecordEnvelope> =
8370
+ initialView.records.filter(collectControl);
8279
8371
 
8280
8372
  // The append-only prefix remains valid for this Attempt. Retain only this Run's control
8281
8373
  // evidence and validate each newly visible suffix against its own captured tail.
@@ -8376,13 +8468,17 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8376
8468
  ),
8377
8469
  );
8378
8470
  }
8379
- yield* applyCanonicalInput(ctx, submission, session, records, snapshot.inputApplied);
8471
+ if (prepared === undefined)
8472
+ yield* applyCanonicalInput(ctx, submission, session, records, snapshot.inputApplied);
8380
8473
 
8381
- const start = yield* makeRunStart(
8382
- submission,
8383
- Duration.toMillis(agent.definition.policy.maxDuration),
8384
- yield* refreshControl(),
8385
- ).pipe(Effect.provideService(CurrentRunWriter, ctx));
8474
+ const start =
8475
+ prepared?.start ??
8476
+ (yield* makeRunStart(
8477
+ submission,
8478
+ Duration.toMillis(agent.definition.policy.maxDuration),
8479
+ yield* refreshControl(),
8480
+ initialView.operational,
8481
+ ).pipe(Effect.provideService(CurrentRunWriter, ctx)));
8386
8482
 
8387
8483
  const savedRunTiming = start.timing;
8388
8484
 
@@ -8404,8 +8500,11 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8404
8500
  };
8405
8501
 
8406
8502
  let expiredChildObligation = false;
8503
+ let preparedControl = prepared !== undefined;
8407
8504
 
8408
8505
  if ((yield* Clock.currentTimeMillis) >= DateTime.toEpochMillis(runTiming.deadline)) {
8506
+ prepared = undefined;
8507
+ preparedControl = false;
8409
8508
  yield* commitStart;
8410
8509
  yield* reconcileRetainedChildren(ctx, submission, session);
8411
8510
  expiredChildObligation = yield* completeJoinedReleases(submission);
@@ -8413,7 +8512,9 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8413
8512
 
8414
8513
  // Recheck after duration interruption too: that Attempt may have declared new ordinary calls.
8415
8514
  const ordinaryCallsResolved = Effect.gen(function* () {
8416
- const reconciliationRecords = yield* refreshControl();
8515
+ const reconciliationRecords = preparedControl ? records : yield* refreshControl();
8516
+
8517
+ preparedControl = false;
8417
8518
 
8418
8519
  const reconciliationEvidence = yield* evidenceFor(
8419
8520
  reconciliationRecords,
@@ -8463,7 +8564,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8463
8564
  if (!expiredChildObligation && !(yield* ordinaryCallsResolved))
8464
8565
  return Option.none<Settlement>();
8465
8566
 
8466
- const continuationRecords = yield* refreshControl();
8567
+ const continuationRecords = prepared === undefined ? yield* refreshControl() : records;
8467
8568
 
8468
8569
  const continuation = yield* pendingToolBatchFor(
8469
8570
  continuationRecords,
@@ -8603,29 +8704,35 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8603
8704
 
8604
8705
  const lineage: AttemptLineage = {
8605
8706
  attemptId: claim.attemptId,
8606
- inputWasRecorded: evidence.inputRecorded,
8707
+ inputWasRecorded: !preparedInput && evidence.inputRecorded,
8607
8708
  };
8608
8709
 
8609
8710
  let approvalDecisionIntents = snapshot.approvalDecisions;
8610
8711
 
8611
8712
  while (true) {
8612
- yield* session.refresh;
8713
+ const initialization = prepared;
8714
+
8715
+ prepared = undefined;
8716
+ if (initialization === undefined) yield* session.refresh;
8613
8717
  const tail = yield* session.tail;
8614
8718
 
8615
- const currentRecords = yield* refreshControl(tail.sequence);
8719
+ const currentRecords =
8720
+ initialization === undefined ? yield* refreshControl(tail.sequence) : records;
8616
8721
 
8617
- const view = yield* recoveryView(
8618
- threadId,
8619
- tail.sequence,
8620
- [submissionId],
8621
- runIdForSubmission(submissionId),
8622
- currentRecords,
8623
- );
8722
+ const view =
8723
+ initialization?.view ??
8724
+ (yield* recoveryView(
8725
+ threadId,
8726
+ tail.sequence,
8727
+ [submissionId],
8728
+ runIdForSubmission(submissionId),
8729
+ currentRecords,
8730
+ ));
8624
8731
 
8625
8732
  let canonical: Stream.Stream<
8626
8733
  JournalRecordEnvelope,
8627
8734
  ThreadStoreError | ThreadNotMaterialized | RunJournalError
8628
- > = view.canonical;
8735
+ > = Stream.fromIterable(view.records);
8629
8736
 
8630
8737
  let initialJournal: InitialJournalSource | undefined;
8631
8738
 
@@ -8885,7 +8992,9 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8885
8992
 
8886
8993
  return Option.some(settlement);
8887
8994
  }
8888
- }).pipe(Effect.scoped);
8995
+ },
8996
+ (effect) => effect.pipe(Effect.scoped),
8997
+ );
8889
8998
 
8890
8999
  /** A missing current binding leaves both roots and children owed, with their claims released. */
8891
9000
  type CapturedWorkerBinding = Effect.Success<ReturnType<typeof makeLegacyWorkerBinding>>;
@@ -8938,6 +9047,9 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8938
9047
  const session = claimed.value;
8939
9048
  const { claim } = session;
8940
9049
 
9050
+ const freshlyAdmitted = unclaimedAdmissions.get(claim.submissionId) === threadId;
9051
+
9052
+ unclaimedAdmissions.delete(claim.submissionId);
8941
9053
  firstClaimedSubmissionId ??= claim.submissionId;
8942
9054
 
8943
9055
  const attributes = {
@@ -9022,6 +9134,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
9022
9134
  });
9023
9135
  },
9024
9136
  options?.yieldAfter,
9137
+ freshlyAdmitted,
9025
9138
  ),
9026
9139
  threadId,
9027
9140
  claim,
@@ -9094,7 +9207,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
9094
9207
  }
9095
9208
  }).pipe((body) => withProcessing(threadId, body));
9096
9209
 
9097
- const processThreadImpl = <
9210
+ const processThreadImpl = Effect.fnUntraced(function* <
9098
9211
  InputSchema extends Schema.Top,
9099
9212
  OutputSchema extends Schema.Top,
9100
9213
  Instructions,
@@ -9126,26 +9239,25 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
9126
9239
  UpdatesSchema
9127
9240
  >,
9128
9241
  threadId: ThreadId,
9129
- ) =>
9130
- Effect.gen(function* () {
9131
- // The legacy single-binding worker is a singleton resolver (plan §1.7): identity-exact —
9132
- // a claimed head with a different Agent never runs against this binding (the latent P4
9133
- // gap) — and digest-transparent, because this call site registers no digest authority.
9134
- const binding = yield* makeLegacyWorkerBinding(agent);
9135
-
9136
- return yield* drainThread(
9137
- (submission) =>
9138
- submission.agentId === binding.agentId
9139
- ? Effect.succeed(binding)
9140
- : Effect.fail(
9141
- BindingUnavailable.make({
9142
- agentId: submission.agentId,
9143
- message: `This worker is bound to Agent ${binding.agentId}; the claimed head belongs to Agent ${submission.agentId} (SUB-023)`,
9144
- }),
9145
- ),
9146
- threadId,
9147
- );
9148
- });
9242
+ ) {
9243
+ // The legacy single-binding worker is a singleton resolver (plan §1.7): identity-exact —
9244
+ // a claimed head with a different Agent never runs against this binding (the latent P4
9245
+ // gap) — and digest-transparent, because this call site registers no digest authority.
9246
+ const binding = yield* makeLegacyWorkerBinding(agent);
9247
+
9248
+ return yield* drainThread(
9249
+ (submission) =>
9250
+ submission.agentId === binding.agentId
9251
+ ? Effect.succeed(binding)
9252
+ : Effect.fail(
9253
+ BindingUnavailable.make({
9254
+ agentId: submission.agentId,
9255
+ message: `This worker is bound to Agent ${binding.agentId}; the claimed head belongs to Agent ${submission.agentId} (SUB-023)`,
9256
+ }),
9257
+ ),
9258
+ threadId,
9259
+ );
9260
+ });
9149
9261
 
9150
9262
  const processThreadResolvedImpl = (
9151
9263
  threadId: ThreadId,
@@ -10057,9 +10169,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
10057
10169
  const hostId = snapshot.hostSubmissionId;
10058
10170
  const hostView = yield* recoveryView(submission.threadId, history.throughSequence, [hostId]);
10059
10171
 
10060
- const hostRecords = yield* Stream.runCollect(
10061
- hostView.canonical.pipe(Stream.filter(controlRecords([hostId]))),
10062
- );
10172
+ const hostRecords = hostView.records.filter(controlRecords([hostId]));
10063
10173
 
10064
10174
  const merged = new Map(history.records.map((entry) => [entry.sequence, entry]));
10065
10175
 
@@ -10403,6 +10513,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
10403
10513
  const recoverOwnedWork = Effect.fnUntraced(function* (
10404
10514
  request: WorkRecoveryRequest,
10405
10515
  recoveredAdmissions = new Map<SubmissionId, RecoveryReport>(),
10516
+ deferUnclaimedAdmissions = false,
10406
10517
  ): Effect.fn.Return<WorkRecoveryReport, DurableWorkerFailure> {
10407
10518
  const { threadId, work: entry } = yield* Schema.decodeEffect(WorkRecoveryRequest)(request).pipe(
10408
10519
  Effect.mapError((cause) =>
@@ -10456,6 +10567,21 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
10456
10567
  const original = yield* lookupKnownSubmission("recover work", owner.submissionId);
10457
10568
 
10458
10569
  if (original.threadId !== threadId) return yield* invalid();
10570
+ if (
10571
+ deferUnclaimedAdmissions &&
10572
+ unclaimedAdmissions.get(owner.submissionId) === threadId &&
10573
+ original.state === "ready" &&
10574
+ !storageOwner.hasOwner(threadId) &&
10575
+ (yield* ledger.readAbortIntent(
10576
+ AbortIntentRequest.make({ submissionId: owner.submissionId }),
10577
+ )) === undefined
10578
+ ) {
10579
+ // The worker's first scoped claim performs the authoritative control/journal
10580
+ // read. This hint skips only a redundant pre-claim recovery traversal; it grants
10581
+ // no claim, synthesizes no decision, and supplies no model-visible facts.
10582
+ return report("deferred");
10583
+ }
10584
+ unclaimedAdmissions.delete(owner.submissionId);
10459
10585
  const submission = yield* recoverAdmission(owner.submissionId);
10460
10586
 
10461
10587
  return report(submission.disposition, submission);
@@ -10934,6 +11060,29 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
10934
11060
 
10935
11061
  const outcome = yield* isolateRecovery(
10936
11062
  Effect.gen(function* () {
11063
+ // This incarnation admitted the sole untouched root. The native snapshot proves
11064
+ // there is no old owner to repair; claim and live controls still run normally.
11065
+ // New source mutations retain their dirty generation for the next maintenance scan.
11066
+ if (
11067
+ workCursor === undefined &&
11068
+ options?.deferUnclaimedAdmissions === true &&
11069
+ workDiscovery.isOnlyFreshAdmission !== undefined &&
11070
+ !storageOwner.hasOwner(threadId)
11071
+ ) {
11072
+ const fresh = [...unclaimedAdmissions].find(([, owner]) => owner === threadId);
11073
+
11074
+ if (
11075
+ fresh !== undefined &&
11076
+ (yield* workDiscovery.isOnlyFreshAdmission({
11077
+ threadId,
11078
+ submissionId: fresh[0],
11079
+ }))
11080
+ ) {
11081
+ complete = true;
11082
+
11083
+ return;
11084
+ }
11085
+ }
10937
11086
  for (
10938
11087
  let pages = 0;
10939
11088
  pages < MAX_RECOVERY_PAGES && workReports.length < MAX_RECOVERY_WORK_ITEMS;
@@ -10949,7 +11098,11 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
10949
11098
 
10950
11099
  phase = "recovery";
10951
11100
  for (const work of workPage.entries) {
10952
- const repaired = yield* recoverOwnedWork({ threadId, work }, recoveredAdmissions);
11101
+ const repaired = yield* recoverOwnedWork(
11102
+ { threadId, work },
11103
+ recoveredAdmissions,
11104
+ options?.deferUnclaimedAdmissions === true,
11105
+ );
10953
11106
 
10954
11107
  workReports.push(repaired);
10955
11108
  if (
@@ -11173,7 +11326,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
11173
11326
  queueSequence: admitted.queueSequence,
11174
11327
  });
11175
11328
  }
11176
- const admitted = yield* ledger.admit(request);
11329
+ const admitted = yield* ledger.admit(request, { readyIfInitialized: true });
11177
11330
 
11178
11331
  yield* hit("submit:after-admit");
11179
11332
 
@@ -11189,10 +11342,24 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
11189
11342
  if (admitted.replayed && admitted.state !== "admitted") {
11190
11343
  return receipt;
11191
11344
  }
11192
- yield* materializeThread(options.threadId);
11193
- yield* ensureThreadCreated(options.threadId, agent.definition.id, options.definitions);
11194
- yield* hit("submit:after-materialize");
11195
- yield* ledger.markReady(MarkReadyRequest.make({ submissionId: admitted.submissionId }));
11345
+ if (admitted.state === "admitted") {
11346
+ yield* materializeThread(options.threadId);
11347
+ yield* ensureThreadCreated(options.threadId, agent.definition.id, options.definitions);
11348
+ yield* hit("submit:after-materialize");
11349
+ yield* ledger.markReady(MarkReadyRequest.make({ submissionId: admitted.submissionId }));
11350
+ }
11351
+ if (
11352
+ !admitted.replayed &&
11353
+ messageAdmission === undefined &&
11354
+ options.workerAdmission === undefined
11355
+ ) {
11356
+ unclaimedAdmissions.set(admitted.submissionId, options.threadId);
11357
+ if (unclaimedAdmissions.size > 128) {
11358
+ const oldest = unclaimedAdmissions.keys().next().value;
11359
+
11360
+ if (oldest !== undefined) unclaimedAdmissions.delete(oldest);
11361
+ }
11362
+ }
11196
11363
  yield* wake.notify(options.threadId);
11197
11364
 
11198
11365
  return receipt;
@@ -11248,14 +11415,8 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
11248
11415
  }
11249
11416
  if (snapshot.value.state !== "settled") return Option.none();
11250
11417
 
11251
- // Finalization replay reads the canonical outcome without changing settledAt.
11252
- const settlement = yield* ledger.finalizeSettlement(
11253
- SettlementFinalization.make({
11254
- submissionId: receipt.submissionId,
11255
- settlementId: submissionSettlementId(receipt.submissionId),
11256
- }),
11257
- );
11258
-
11418
+ // Finalization replay reads the canonical outcome for adapters without finalized
11419
+ // metadata. Native projections already retain the first settledAt and exact record.
11259
11420
  const canonical = yield* getRecord({
11260
11421
  threadId: receipt.threadId,
11261
11422
  recordId: submissionSettlementRecordId(receipt.submissionId),
@@ -11278,6 +11439,40 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
11278
11439
  }
11279
11440
 
11280
11441
  const record = yield* settlementPayloadFromRecord(canonical.value.record, receipt.submissionId);
11442
+ const finalized = snapshot.value;
11443
+
11444
+ const retainedFinalization =
11445
+ finalized.settledAt !== undefined &&
11446
+ finalized.settledRecordId !== undefined &&
11447
+ finalized.settledOutcome !== undefined;
11448
+
11449
+ if (
11450
+ retainedFinalization &&
11451
+ (finalized.settledRecordId !== canonical.value.record.recordId ||
11452
+ finalized.settledOutcome !== record.outcome ||
11453
+ canonical.value.batchId !== submissionSettlementBatchId(receipt.submissionId))
11454
+ ) {
11455
+ return yield* LedgerError.make({
11456
+ operation: "awaitSettlement",
11457
+ message: "Settled projection disagrees with its canonical Settlement",
11458
+ });
11459
+ }
11460
+
11461
+ const settlement = retainedFinalization
11462
+ ? Settlement.make({
11463
+ submissionId: receipt.submissionId,
11464
+ settlementId: record.settlementId,
11465
+ receiptId: record.receiptId,
11466
+ outcome: record.outcome,
11467
+ ...(record.outcome === "failed" ? { failure: record.result } : {}),
11468
+ settledAt: finalized.settledAt,
11469
+ })
11470
+ : yield* ledger.finalizeSettlement(
11471
+ SettlementFinalization.make({
11472
+ submissionId: receipt.submissionId,
11473
+ settlementId: submissionSettlementId(receipt.submissionId),
11474
+ }),
11475
+ );
11281
11476
 
11282
11477
  if (
11283
11478
  record.receiptId !== receipt.receiptId ||