@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
@@ -1,12 +1,12 @@
1
- import { Context, Crypto, DateTime, Effect, Option, Schema, Semaphore, Stream } from "effect";
1
+ import { Context, DateTime, Effect, Option, Result, Schema, Semaphore, Stream } from "effect";
2
2
  import { Prompt } from "effect/ai";
3
3
 
4
4
  import { AgentPersistenceCapacityError } from "../core/AgentError.ts";
5
5
  import type { RunId, SubmissionId, ThreadId } from "../core/Identifiers.ts";
6
6
  import { ToolCallId } from "../core/Identifiers.ts";
7
7
  import { utf8ByteLength } from "../core/internal/utf8.ts";
8
- import { summarizeModelUsage } from "../core/Usage.ts";
9
- import { reference, resolveEvidence } from "./internal/evidence.ts";
8
+ import { resolveEvidence } from "./internal/evidence.ts";
9
+ import { continuationUsage } from "./internal/usage-totals.ts";
10
10
  export { reference, resolveEvidence } from "./internal/evidence.ts";
11
11
  import {
12
12
  captureRecord,
@@ -119,56 +119,124 @@ const isExecutionProgressRecord = (
119
119
  record.payload._tag === "RunContinuation";
120
120
 
121
121
  const decodeResponsePrompt = Schema.decodeUnknownEffect(Prompt.Prompt);
122
+ // The decoded Prompt stays private. Progress and capacity are recomputed on every invocation.
123
+ const responsePrompts = new WeakMap<ModelResponseRecorded, Prompt.Prompt>();
122
124
 
123
- const validateBatchHeader = Schema.decodeSync(
125
+ /** Cache eligibility only: freezing does not make accessors or custom iterators immutable. */
126
+ const isFrozenJsonData = (input: Schema.Json): boolean => {
127
+ const pending: Array<unknown> = [input];
128
+ const seen = new WeakSet<object>();
129
+
130
+ while (pending.length > 0) {
131
+ const value = pending.pop();
132
+
133
+ if (value === null) continue;
134
+ if (typeof value !== "object") {
135
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
136
+ return false;
137
+ continue;
138
+ }
139
+ if (seen.has(value)) continue;
140
+ seen.add(value);
141
+ const prototype = Object.getPrototypeOf(value);
142
+
143
+ if (
144
+ !Object.isFrozen(value) ||
145
+ (prototype !== null && prototype !== Object.prototype && prototype !== Array.prototype)
146
+ )
147
+ return false;
148
+ for (const key of Reflect.ownKeys(value)) {
149
+ if (typeof key !== "string") return false;
150
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
151
+
152
+ if (descriptor === undefined || !("value" in descriptor)) return false;
153
+ pending.push(descriptor.value);
154
+ }
155
+ }
156
+
157
+ return true;
158
+ };
159
+
160
+ const validateBatchShape = Schema.decodeUnknownSync(
124
161
  Schema.Struct({
125
162
  batchId: CanonicalBatch.fields.batchId,
126
163
  producerId: CanonicalBatch.fields.producerId,
164
+ records: Schema.NonEmptyArray(Schema.Unknown).check(Schema.isMaxLength(256)),
127
165
  }),
128
166
  );
129
167
 
130
- const validateBatchCount = Schema.decodeUnknownSync(
131
- Schema.NonEmptyArray(Schema.Unknown).check(Schema.isMaxLength(256)),
132
- );
133
-
134
168
  const ContinuationBytes = Schema.Struct({
135
169
  turnBytes: RunContinuation.fields.turnBytes,
136
170
  terminalBytes: RunContinuation.fields.terminalBytes,
137
171
  });
138
172
 
173
+ const decodeExecutionFrontier = Schema.decodeUnknownEffect(
174
+ Schema.Struct({
175
+ canonicalRecords: ExecutionCheckpoint.fields.canonicalRecords,
176
+ throughSequence: ExecutionCheckpoint.fields.throughSequence,
177
+ }),
178
+ );
179
+
139
180
  /** One private snapshot for every retry; callers cannot change facts while awaiting the gate. */
140
181
  const captureFacts = (batch: CommitBatch) =>
141
182
  Effect.try({
142
- try: () => {
143
- const header = validateBatchHeader(batch);
183
+ try: () =>
184
+ Result.gen(function* () {
185
+ const inputs = batch.records;
186
+
187
+ const { batchId, producerId } = validateBatchShape({
188
+ batchId: batch.batchId,
189
+ producerId: batch.producerId,
190
+ records: inputs,
191
+ });
144
192
 
145
- validateBatchCount(batch.records);
146
- const records = batch.records.map((record) => captureRecord(record).canonical);
193
+ const encodings = yield* Result.all(inputs.map(captureRecord));
194
+ const records = encodings.map((record) => record.canonical);
147
195
 
148
- return Object.freeze(
149
- new CanonicalBatch(
150
- { ...header, records: [records[0]!, ...records.slice(1)] },
151
- { disableChecks: true },
152
- ),
153
- );
154
- },
196
+ return Object.freeze(
197
+ CanonicalBatch.make(
198
+ { batchId, producerId, records: [records[0]!, ...records.slice(1)] },
199
+ { disableChecks: true },
200
+ ),
201
+ );
202
+ }),
155
203
  catch: (cause) => failure("Cannot capture canonical progress facts", cause),
156
- });
204
+ }).pipe(
205
+ Effect.flatMap(Effect.fromResult),
206
+ Effect.mapError((cause) =>
207
+ Schema.is(ThreadStoreError)(cause)
208
+ ? cause
209
+ : failure("Cannot capture canonical progress facts", cause),
210
+ ),
211
+ );
157
212
 
158
213
  /** Exact UTF-8 wire accounting reuses the append's privately captured Schema encoding. */
159
- export const canonicalRecordBytes = (record: RecordEnvelope): number =>
160
- recordEncoding(record).bytes;
214
+ export const canonicalRecordBytes = (record: RecordEnvelope): number => {
215
+ const encoded = recordEncoding(record);
216
+
217
+ if (Result.isFailure(encoded)) {
218
+ // oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Public synchronous byte-accounting validation must reject malformed records before returning a number.
219
+ throw encoded.failure.cause ?? new RangeError(encoded.failure.message);
220
+ }
221
+
222
+ return encoded.success.bytes;
223
+ };
161
224
 
162
225
  export type { ProgressAppendRecord } from "./internal/record-encoding.ts";
163
226
 
164
227
  /** Capture before acquiring the mutation owner; summaries never cross a wire boundary. */
165
228
  export const prepareProgressAppend = (
166
229
  records: ReadonlyArray<RecordEnvelope>,
167
- ): ReadonlyArray<ProgressAppendRecord> =>
168
- Object.freeze(records.map((record) => captureRecord(record).progress));
230
+ ): ReadonlyArray<ProgressAppendRecord> => {
231
+ const encodings = Result.all(records.map(captureRecord));
232
+
233
+ if (Result.isFailure(encodings)) {
234
+ // oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Public synchronous progress construction must reject malformed records before acquiring a mutation owner.
235
+ throw encodings.failure.cause ?? new RangeError(encodings.failure.message);
236
+ }
169
237
 
170
- const factUsageCharge = (facts: ReadonlyArray<RecordEnvelope>): number =>
171
- facts.reduce((bytes, record) => bytes + recordEncoding(record).progress.terminalUsageBytes, 0);
238
+ return Object.freeze(encodings.success.map((record) => record.progress));
239
+ };
172
240
 
173
241
  /** These values belong to the active Run, not its permanent conversation history. */
174
242
  export const isOperationalRecord = ({ payload }: RecordEnvelope): boolean =>
@@ -643,9 +711,27 @@ const advanceFacts = Effect.fnUntraced(function* (
643
711
  let results = new Map(previous?.results);
644
712
  let childResults = new Map(previous?.childResults);
645
713
  let completionBytes = previous?.completionBytes;
714
+ let factBytes = 0;
715
+ let turn = previous?.continuation.turn ?? 1;
716
+ let usageCharge = 0;
717
+ let closingBytes = 0;
718
+ let closingCount = 0;
646
719
 
647
720
  for (const fact of facts) {
648
721
  const payload = fact.payload;
722
+ const captured = recordEncoding(fact);
723
+
724
+ if (Result.isFailure(captured))
725
+ return yield* failure("Cannot account canonical progress facts", captured.failure);
726
+ const encoded = captured.success;
727
+
728
+ factBytes += encoded.bytes;
729
+ if ("turn" in payload) turn = Math.max(turn, payload.turn);
730
+ usageCharge += encoded.progress.terminalUsageBytes;
731
+ if (encoded.progress.terminal) {
732
+ closingBytes += encoded.bytes;
733
+ closingCount++;
734
+ }
649
735
 
650
736
  switch (payload._tag) {
651
737
  case "RunStarted":
@@ -654,7 +740,7 @@ const advanceFacts = Effect.fnUntraced(function* (
654
740
  case "RunContextRecorded":
655
741
  if (savedContext !== undefined)
656
742
  return yield* failure("A Run's original context cannot be replaced");
657
- savedContext = yield* reference(fact);
743
+ savedContext = EvidenceReference.make({ recordId: fact.recordId });
658
744
  position = "awaiting-model";
659
745
  break;
660
746
  case "ModelResponseRecorded": {
@@ -663,10 +749,27 @@ const advanceFacts = Effect.fnUntraced(function* (
663
749
  if (payload.turn !== accounting.committedTurns + 1)
664
750
  return yield* failure("Canonical Turn progress must advance once");
665
751
 
666
- const messages = yield* decodeResponsePrompt(payload.messages).pipe(
667
- Effect.mapError((cause) => failure("Invalid model evidence", cause)),
668
- );
752
+ const promptInput = payload.messages;
753
+ const retainedPrompt = responsePrompts.get(payload);
669
754
 
755
+ const messages =
756
+ retainedPrompt ??
757
+ (yield* decodeResponsePrompt(promptInput).pipe(
758
+ Effect.mapError((cause) => failure("Invalid model evidence", cause)),
759
+ ));
760
+
761
+ if (retainedPrompt === undefined) {
762
+ const property = Object.getOwnPropertyDescriptor(payload, "messages");
763
+
764
+ if (
765
+ Object.isFrozen(payload) &&
766
+ property !== undefined &&
767
+ "value" in property &&
768
+ property.value === promptInput &&
769
+ isFrozenJsonData(promptInput)
770
+ )
771
+ responsePrompts.set(payload, messages);
772
+ }
670
773
  responsePrompt = messages;
671
774
 
672
775
  const calls = messages.content.flatMap((message) =>
@@ -678,20 +781,20 @@ const advanceFacts = Effect.fnUntraced(function* (
678
781
  const summary =
679
782
  payload.modelUsage === undefined
680
783
  ? undefined
681
- : yield* summarizeModelUsage(payload.modelUsage).pipe(
784
+ : yield* continuationUsage(payload.modelUsage).pipe(
682
785
  Effect.mapError((cause) => failure("Invalid usage evidence", cause)),
683
786
  );
684
787
 
685
788
  accounting.committedTurns = payload.turn;
686
789
  accounting.toolCalls += calls.length;
687
790
  accounting.modelCalls += summary?.modelCalls ?? 1;
688
- accounting.inputTokens += summary?.inputTokens.total ?? payload.inputTokens ?? 0;
689
- accounting.outputTokens += summary?.outputTokens.total ?? payload.outputTokens ?? 0;
791
+ accounting.inputTokens += summary?.inputTokens ?? payload.inputTokens ?? 0;
792
+ accounting.outputTokens += summary?.outputTokens ?? payload.outputTokens ?? 0;
690
793
  accounting.costMicrousd += summary?.costMicrousd ?? payload.costMicrousd ?? 0;
691
- accounting.lastInputTokens = summary?.inputTokens.total ?? payload.inputTokens ?? 0;
692
- accounting.lastOutputTokens = summary?.outputTokens.total ?? payload.outputTokens ?? 0;
794
+ accounting.lastInputTokens = summary?.inputTokens ?? payload.inputTokens ?? 0;
795
+ accounting.lastOutputTokens = summary?.outputTokens ?? payload.outputTokens ?? 0;
693
796
  accounting.unobservedModelCalls += payload.unobservedModelCalls ?? 0;
694
- latestResponse = yield* reference(fact);
797
+ latestResponse = EvidenceReference.make({ recordId: fact.recordId });
695
798
  response = payload;
696
799
  results = new Map();
697
800
  childResults = new Map();
@@ -754,14 +857,14 @@ const advanceFacts = Effect.fnUntraced(function* (
754
857
  if (payload.restart !== (accounting.modelRestarts ?? 0) + 1)
755
858
  return yield* failure("Model restart progress must advance once");
756
859
 
757
- const summary = yield* summarizeModelUsage(payload.modelUsage).pipe(
860
+ const summary = yield* continuationUsage(payload.modelUsage).pipe(
758
861
  Effect.mapError((cause) => failure("Invalid cancelled model usage", cause)),
759
862
  );
760
863
 
761
864
  accounting.modelRestarts = payload.restart;
762
865
  accounting.modelCalls += summary.modelCalls;
763
- accounting.inputTokens += summary.inputTokens.total;
764
- accounting.outputTokens += summary.outputTokens.total;
866
+ accounting.inputTokens += summary.inputTokens;
867
+ accounting.outputTokens += summary.outputTokens;
765
868
  accounting.costMicrousd += summary.costMicrousd;
766
869
  accounting.unobservedModelCalls += payload.unobservedModelCalls;
767
870
  break;
@@ -775,12 +878,12 @@ const advanceFacts = Effect.fnUntraced(function* (
775
878
  ? 0
776
879
  : utf8ByteLength(JSON.stringify(payload.runDisposition)))
777
880
  : undefined;
778
- terminal = yield* reference(fact);
881
+ terminal = EvidenceReference.make({ recordId: fact.recordId });
779
882
  position = "settling";
780
883
  break;
781
884
  case "SubmissionSettled":
782
885
  if (payload.submissionId !== submissionId) break;
783
- terminal = yield* reference(fact);
886
+ terminal = EvidenceReference.make({ recordId: fact.recordId });
784
887
  position = "settled";
785
888
  if (payload.usageSummary !== undefined) {
786
889
  accounting.modelCalls = payload.usageSummary.modelCalls;
@@ -802,6 +905,7 @@ const advanceFacts = Effect.fnUntraced(function* (
802
905
  ) {
803
906
  const messages =
804
907
  responsePrompt ??
908
+ responsePrompts.get(response) ??
805
909
  (yield* decodeResponsePrompt(response.messages).pipe(
806
910
  Effect.mapError((cause) => failure("Invalid batch evidence", cause)),
807
911
  ));
@@ -837,22 +941,13 @@ const advanceFacts = Effect.fnUntraced(function* (
837
941
  if (previous?.continuation.position === "settled") position = "settled";
838
942
  else if (previous?.continuation.position === "settling" && position !== "settled")
839
943
  position = "settling";
840
- const factBytes = facts.reduce((bytes, fact) => bytes + canonicalRecordBytes(fact), 0);
841
-
842
- const turn = facts.reduce(
843
- (current, fact) => ("turn" in fact.payload ? Math.max(current, fact.payload.turn) : current),
844
- previous?.continuation.turn ?? 1,
845
- );
846
944
 
847
945
  const turnBase =
848
946
  (previous?.continuation.turn === turn ? previous.continuation.turnBytes : initialBytes) +
849
947
  factBytes;
850
948
 
851
- const usageCharge = factUsageCharge(facts);
852
949
  const terminalUsageBytes = (previous?.continuation.terminalUsageBytes ?? 0) + usageCharge;
853
950
 
854
- const closing = facts.filter(isTerminalBudgetFact);
855
-
856
951
  return {
857
952
  accounting,
858
953
  savedContext,
@@ -869,11 +964,9 @@ const advanceFacts = Effect.fnUntraced(function* (
869
964
  turnBase,
870
965
  usageCharge,
871
966
  terminalUsageBytes,
872
- terminalBytes:
873
- (previous?.continuation.terminalBytes ?? 0) +
874
- closing.reduce((bytes, fact) => bytes + canonicalRecordBytes(fact), 0),
875
- terminalRecords: (previous?.continuation.terminalRecords ?? 0) + closing.length,
876
- closing: closing.length > 0,
967
+ terminalBytes: (previous?.continuation.terminalBytes ?? 0) + closingBytes,
968
+ terminalRecords: (previous?.continuation.terminalRecords ?? 0) + closingCount,
969
+ closing: closingCount > 0,
877
970
  };
878
971
  });
879
972
 
@@ -951,10 +1044,17 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
951
1044
  deploymentId: DeploymentId,
952
1045
  ) {
953
1046
  const reader = yield* ThreadReader;
954
- const crypto = yield* Crypto.Crypto;
955
1047
  const gate = yield* Semaphore.make(1);
956
1048
  const cached = new Map<RunId, ProgressState>();
957
1049
 
1050
+ let verifiedEmpty:
1051
+ | {
1052
+ readonly runId: RunId;
1053
+ readonly through: CanonicalSequence;
1054
+ readonly writer: CurrentRunWriter["Service"];
1055
+ }
1056
+ | undefined;
1057
+
958
1058
  const reservations = new Map<
959
1059
  RecordId,
960
1060
  {
@@ -967,16 +1067,10 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
967
1067
 
968
1068
  const stagedUsage = new Map<RunId, number>();
969
1069
 
970
- const provide = <A, E>(effect: Effect.Effect<A, E, ThreadReader | Crypto.Crypto>) =>
971
- effect.pipe(
972
- Effect.provideService(ThreadReader, reader),
973
- Effect.provideService(Crypto.Crypto, crypto),
974
- );
975
-
976
- const loadState = Effect.fnUntraced(function* (runId: RunId, after: CanonicalSequence) {
977
- const retained = cached.get(runId);
1070
+ const provide = <A, E>(effect: Effect.Effect<A, E, ThreadReader>) =>
1071
+ Effect.provideService(effect, ThreadReader, reader);
978
1072
 
979
- if (retained !== undefined) return retained;
1073
+ const loadUncachedState = Effect.fnUntraced(function* (runId: RunId, after: CanonicalSequence) {
980
1074
  const loaded = yield* provide(readContinuation(threadId, runId, after));
981
1075
 
982
1076
  if (Option.isNone(loaded)) return undefined;
@@ -1095,11 +1189,14 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1095
1189
  };
1096
1190
  });
1097
1191
 
1098
- const checkFutureShapes = (state: ProgressState): Effect.Effect<void, ThreadStoreError> => {
1192
+ const checkFutureShapes = (
1193
+ state: ProgressState,
1194
+ cursorBytes?: number,
1195
+ ): Effect.Effect<void, ThreadStoreError> => {
1099
1196
  const progress = state.continuation;
1100
1197
 
1101
1198
  if (progress.position === "settled") return Effect.void;
1102
- const size = canonicalRecordBytes(state.record);
1199
+ const size = cursorBytes ?? canonicalRecordBytes(state.record);
1103
1200
  const textBytes = (value: string) => utf8ByteLength(JSON.stringify(value));
1104
1201
  const refBytes = (value: EvidenceReference) => utf8ByteLength(JSON.stringify(value));
1105
1202
 
@@ -1213,14 +1310,17 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1213
1310
  return Effect.void;
1214
1311
  };
1215
1312
 
1216
- const prepare = Effect.fnUntraced(function* (batch: CanonicalBatch, after: CanonicalSequence) {
1313
+ const prepare = Effect.fnUntraced(function* (
1314
+ batch: CanonicalBatch,
1315
+ after: CanonicalSequence,
1316
+ mode: "commit" | "preflight" = "commit",
1317
+ ) {
1217
1318
  if (batch.records.some((record) => record.payload._tag === "RunContinuation"))
1218
1319
  return yield* failure("A caller cannot supply interpreter progress to the progress writer");
1219
1320
 
1220
- const ownedRecords = yield* Effect.try({
1221
- try: () => batch.records.map((record) => captureRecord(record).canonical),
1222
- catch: (cause) => failure("Cannot capture canonical progress facts", cause),
1223
- });
1321
+ // Every private caller passes captureFacts' owned snapshot. Keep a mutable
1322
+ // list for settlement accounting without capturing that snapshot a second time.
1323
+ const ownedRecords = [...batch.records];
1224
1324
 
1225
1325
  const groups = new Map<RunId, Array<RecordEnvelope>>();
1226
1326
 
@@ -1234,9 +1334,19 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1234
1334
  }
1235
1335
  const executions: Array<ExecutionUpdate> = [];
1236
1336
  const accepted = new Map<RunId, ProgressState>();
1337
+ let executionCount = 0;
1237
1338
 
1238
1339
  for (const [runId, facts] of groups) {
1239
- const previous = yield* loadState(runId, after);
1340
+ const empty =
1341
+ verifiedEmpty?.runId === runId &&
1342
+ verifiedEmpty.through === after &&
1343
+ // Settlement preparation need not have a Run writer; absence disables reuse.
1344
+ verifiedEmpty.writer ===
1345
+ Option.getOrUndefined(yield* Effect.serviceOption(CurrentRunWriter));
1346
+
1347
+ const previous = empty
1348
+ ? undefined
1349
+ : (cached.get(runId) ?? (yield* loadUncachedState(runId, after)));
1240
1350
 
1241
1351
  let original = facts.find(
1242
1352
  (record) =>
@@ -1277,7 +1387,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1277
1387
  if (previous === undefined && original === undefined)
1278
1388
  return yield* failure("Run has no accepted original input");
1279
1389
 
1280
- if (previous === undefined) {
1390
+ if (previous === undefined && !empty) {
1281
1391
  const settled = yield* provide(
1282
1392
  getRecord({
1283
1393
  threadId,
@@ -1337,12 +1447,17 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1337
1447
 
1338
1448
  if (payload._tag !== "SubmissionSettled" || payload.runId !== runId) continue;
1339
1449
 
1340
- const retained = captureRecord(
1341
- RecordEnvelope.make({
1342
- ...fact,
1343
- payload: { ...payload, historyRecordCount: canonicalRecords },
1344
- }),
1345
- ).canonical;
1450
+ const retained = yield* Effect.fromResult(
1451
+ captureRecord(
1452
+ RecordEnvelope.make({
1453
+ ...fact,
1454
+ payload: { ...payload, historyRecordCount: canonicalRecords },
1455
+ }),
1456
+ ),
1457
+ ).pipe(
1458
+ Effect.map((record) => record.canonical),
1459
+ Effect.mapError((cause) => failure("Cannot capture canonical progress facts", cause)),
1460
+ );
1346
1461
 
1347
1462
  facts[index] = retained;
1348
1463
  ownedRecords[ownedRecords.indexOf(fact)] = retained;
@@ -1353,9 +1468,9 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1353
1468
  ? previous.continuation.originalInput
1354
1469
  : original === undefined
1355
1470
  ? yield* failure("Run progress has no original input")
1356
- : yield* provide(reference(original));
1471
+ : EvidenceReference.make({ recordId: original.recordId });
1357
1472
 
1358
- const next = yield* provide(advanceFacts(previous, facts, submissionId, initialBytes));
1473
+ const next = yield* advanceFacts(previous, facts, submissionId, initialBytes);
1359
1474
 
1360
1475
  const {
1361
1476
  accounting,
@@ -1391,7 +1506,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1391
1506
  ...(savedContext === undefined ? {} : { savedContext }),
1392
1507
  ...(latestResponse === undefined ? {} : { latestResponse }),
1393
1508
  ...(terminal === undefined ? {} : { terminal }),
1394
- lastFact: yield* provide(reference(last)),
1509
+ lastFact: EvidenceReference.make({ recordId: last.recordId }),
1395
1510
  position,
1396
1511
  accounting,
1397
1512
  }).pipe(
@@ -1451,23 +1566,63 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1451
1566
  );
1452
1567
 
1453
1568
  // Every other field was checked above; byte accounting changes only these counters.
1454
- continuation = new RunContinuation(
1569
+ continuation = RunContinuation.make(
1455
1570
  { ...continuation, ...checkedBytes },
1456
1571
  { disableChecks: true },
1457
1572
  );
1458
1573
 
1459
- const record = captureRecord(
1460
- new RecordEnvelope({ ...header, payload: continuation }, { disableChecks: true }),
1461
- ).canonical;
1574
+ const proposedRecord = RecordEnvelope.make(
1575
+ { ...header, payload: continuation },
1576
+ { disableChecks: true },
1577
+ );
1462
1578
 
1463
- if (!isExecutionProgressRecord(record))
1464
- return yield* failure("Captured execution has no Run continuation");
1465
- if (canonicalRecordBytes(record) !== turnBytes - turnBase)
1466
- return yield* failure("Continuation byte accounting differs from its Schema encoding");
1467
- if (canonicalRecordBytes(record) > MAX_RUN_CONTINUATION_BYTES)
1579
+ const cursorBytes = turnBytes - turnBase;
1580
+
1581
+ if (cursorBytes > MAX_RUN_CONTINUATION_BYTES)
1468
1582
  return yield* capacityFailure(
1469
1583
  "Encoded continuation exceeds 8192 bytes including its envelope",
1470
1584
  );
1585
+ executionCount++;
1586
+ if (
1587
+ mode === "preflight" &&
1588
+ previous?.checkpoint !== undefined &&
1589
+ facts.every(({ payload }) => payload._tag === "ModelResponseRecorded")
1590
+ ) {
1591
+ // The payload and header are validated, and operational facts are unchanged.
1592
+ // Dispatch needs their capacity proof; only a commit needs checkpoint wire.
1593
+ yield* decodeExecutionFrontier({
1594
+ canonicalRecords,
1595
+ throughSequence: after + ownedRecords.filter((fact) => !isOperationalRecord(fact)).length,
1596
+ }).pipe(Effect.mapError((cause) => failure("Active execution exceeds its bounds", cause)));
1597
+
1598
+ const state: ProgressState = {
1599
+ continuation,
1600
+ record: proposedRecord,
1601
+ ...(response === undefined ? {} : { response }),
1602
+ results,
1603
+ childResults,
1604
+ ...(completionBytes === undefined ? {} : { completionBytes }),
1605
+ };
1606
+
1607
+ yield* checkCapacity(
1608
+ state,
1609
+ new Set(batch.records.map(({ recordId }) => recordId)),
1610
+ undefined,
1611
+ usageCharge,
1612
+ );
1613
+ yield* checkFutureShapes(state, cursorBytes);
1614
+ continue;
1615
+ }
1616
+
1617
+ const record = yield* Effect.fromResult(captureRecord(proposedRecord)).pipe(
1618
+ Effect.map((record) => record.canonical),
1619
+ Effect.mapError((cause) => failure("Cannot capture canonical progress facts", cause)),
1620
+ );
1621
+
1622
+ if (!isExecutionProgressRecord(record))
1623
+ return yield* failure("Captured execution has no Run continuation");
1624
+ if (canonicalRecordBytes(record) !== cursorBytes)
1625
+ return yield* failure("Continuation byte accounting differs from its Schema encoding");
1471
1626
 
1472
1627
  const operational = new Map(
1473
1628
  (previous?.checkpoint?.operational ?? []).map((fact) => [fact.recordId, fact]),
@@ -1478,7 +1633,15 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1478
1633
  for (const fact of changed) {
1479
1634
  const prior = operational.get(fact.recordId);
1480
1635
 
1481
- if (prior !== undefined && recordEncoding(prior).json !== recordEncoding(fact).json)
1636
+ if (
1637
+ prior !== undefined &&
1638
+ (yield* Effect.fromResult(recordEncoding(prior)).pipe(
1639
+ Effect.mapError((cause) => failure("Cannot encode active Run fact", cause)),
1640
+ )).json !==
1641
+ (yield* Effect.fromResult(recordEncoding(fact)).pipe(
1642
+ Effect.mapError((cause) => failure("Cannot encode active Run fact", cause)),
1643
+ )).json
1644
+ )
1482
1645
  return yield* failure("An active Run fact cannot change after becoming durable");
1483
1646
  operational.set(fact.recordId, fact);
1484
1647
  }
@@ -1535,10 +1698,10 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1535
1698
  accepted.set(runId, state);
1536
1699
  }
1537
1700
 
1538
- if (ownedRecords.length + executions.length > 256)
1701
+ if (ownedRecords.length + executionCount > 256)
1539
1702
  return yield* failure("Progress and facts exceed the atomic batch bound");
1540
1703
 
1541
- const prepared = new CommitBatch(
1704
+ const prepared = CommitBatch.make(
1542
1705
  {
1543
1706
  ...batch,
1544
1707
  records: ownedRecords.filter((fact) => !isOperationalRecord(fact)),
@@ -1566,14 +1729,11 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1566
1729
  };
1567
1730
  });
1568
1731
 
1569
- const tail = Effect.gen(function* () {
1570
- const writer = yield* CurrentRunWriter;
1571
-
1572
- if (writer.threadId !== threadId)
1573
- return yield* failure("Progress writer belongs to another Thread");
1574
-
1575
- return (yield* writer.tail).sequence;
1576
- });
1732
+ const tail = Effect.flatMap(CurrentRunWriter, (writer) =>
1733
+ writer.threadId !== threadId
1734
+ ? Effect.fail(failure("Progress writer belongs to another Thread"))
1735
+ : Effect.map(writer.tail, (position) => position.sequence),
1736
+ );
1577
1737
 
1578
1738
  const commitCaptured = Effect.fnUntraced(
1579
1739
  function* (batch: CanonicalBatch) {
@@ -1672,7 +1832,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1672
1832
 
1673
1833
  yield* prepare(yield* captureFacts(probe), after);
1674
1834
  const recordId = probe.records[0].recordId;
1675
- const state = yield* loadState(runId, after);
1835
+ const state = cached.get(runId) ?? (yield* loadUncachedState(runId, after));
1676
1836
 
1677
1837
  if (state === undefined) return yield* failure("Dispatch has no canonical Run progress");
1678
1838
  if (reservations.has(recordId))
@@ -1699,7 +1859,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1699
1859
 
1700
1860
  const owned = yield* captureFacts(batch);
1701
1861
 
1702
- yield* prepare(owned, after);
1862
+ yield* prepare(owned, after, "preflight");
1703
1863
 
1704
1864
  const response = owned.records.find(
1705
1865
  (record) => record.payload._tag === "ModelResponseRecorded",
@@ -1707,7 +1867,10 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1707
1867
 
1708
1868
  if (response === undefined || response.payload._tag !== "ModelResponseRecorded")
1709
1869
  return yield* failure("Deferred dispatch has no response");
1710
- const progress = yield* loadState(response.payload.runId, after);
1870
+
1871
+ const progress =
1872
+ cached.get(response.payload.runId) ??
1873
+ (yield* loadUncachedState(response.payload.runId, after));
1711
1874
 
1712
1875
  if (progress === undefined) return yield* failure("Deferred dispatch has no Run progress");
1713
1876
 
@@ -1741,16 +1904,33 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1741
1904
  }),
1742
1905
  );
1743
1906
 
1907
+ const checkState = (state: ProgressState | undefined, recovered: boolean) => {
1908
+ if (state === undefined) return Effect.fail(failure("Execution has no canonical Run progress"));
1909
+
1910
+ const capacity = () =>
1911
+ state.continuation.position === "settling" || state.continuation.position === "settled"
1912
+ ? Effect.void
1913
+ : checkCapacity(state);
1914
+
1915
+ // Accepted states were shape-checked, but usage and reservations remain live inputs.
1916
+ return recovered ? Effect.andThen(checkFutureShapes(state), capacity) : capacity();
1917
+ };
1918
+
1744
1919
  const check = (runId: RunId) =>
1745
1920
  gate.withPermits(1)(
1746
- Effect.gen(function* () {
1747
- const state = yield* loadState(runId, yield* tail);
1921
+ Effect.flatMap(CurrentRunWriter, (writer) => {
1922
+ if (writer.threadId !== threadId)
1923
+ return Effect.fail(failure("Progress writer belongs to another Thread"));
1748
1924
 
1749
- if (state === undefined) return yield* failure("Execution has no canonical Run progress");
1750
- // Accepted states were shape-checked during preparation; recovered states still need proof.
1751
- if (cached.get(runId) !== state) yield* checkFutureShapes(state);
1752
- if (state.continuation.position !== "settling" && state.continuation.position !== "settled")
1753
- yield* checkCapacity(state);
1925
+ const retained = cached.get(runId);
1926
+
1927
+ if (retained !== undefined) return checkState(retained, false);
1928
+
1929
+ return Effect.flatMap(writer.tail, (position) =>
1930
+ Effect.flatMap(loadUncachedState(runId, position.sequence), (state) =>
1931
+ checkState(state, true),
1932
+ ),
1933
+ );
1754
1934
  }),
1755
1935
  );
1756
1936
 
@@ -1760,6 +1940,29 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1760
1940
  reserve,
1761
1941
  defer,
1762
1942
  check,
1943
+ /** Internal read view immediately after this writer's acknowledged commit, while
1944
+ * holding its initialization gates. Never authority across ownership or writes. */
1945
+ acceptedCheckpoint: (runId: RunId) => cached.get(runId)?.checkpoint,
1946
+ /** Scope a verified empty execution/evidence view to one owned initialization body.
1947
+ * The caller holds progress+mutation gates; a changed writer or tail never reuses it. */
1948
+ withVerifiedEmptyRun: <A, E, R>(
1949
+ runId: RunId,
1950
+ through: CanonicalSequence,
1951
+ body: Effect.Effect<A, E, R>,
1952
+ ): Effect.Effect<A, E, R | CurrentRunWriter> =>
1953
+ Effect.flatMap(CurrentRunWriter, (writer) => {
1954
+ const view = { runId, through, writer };
1955
+
1956
+ verifiedEmpty = view;
1957
+
1958
+ return body.pipe(
1959
+ Effect.ensuring(
1960
+ Effect.sync(() => {
1961
+ if (verifiedEmpty === view) verifiedEmpty = undefined;
1962
+ }),
1963
+ ),
1964
+ );
1965
+ }),
1763
1966
  stageUsage: (runId: RunId, bytes: number) => {
1764
1967
  if (bytes === 0) stagedUsage.delete(runId);
1765
1968
  else stagedUsage.set(runId, bytes);