@yielded/agent 0.1.0-beta.174 → 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 (175) hide show
  1. package/dist/{Admin-BHmp2V9p.d.mts → Admin-Qa3I1oA5.d.mts} +38 -7
  2. package/dist/{AgentRuntime-BD3ZHAMq.d.mts → AgentRuntime-CHDyRbSf.d.mts} +3 -3
  3. package/dist/{AgentRuntime-B6qZlD3f.mjs → AgentRuntime-DaDKMasp.mjs} +123 -114
  4. package/dist/AgentRuntime-DaDKMasp.mjs.map +1 -0
  5. package/dist/{Digest-EpeqWEdv.mjs → Digest-C3EODOee.mjs} +2 -2
  6. package/dist/{Digest-EpeqWEdv.mjs.map → Digest-C3EODOee.mjs.map} +1 -1
  7. package/dist/{DurableAgentRuntime-CBBjRNJN.mjs → DurableAgentRuntime-CPlw5_7e.mjs} +161 -85
  8. package/dist/DurableAgentRuntime-CPlw5_7e.mjs.map +1 -0
  9. package/dist/{EventSource-C9aaJaXT.d.mts → EventSource-BTu68yDW.d.mts} +2 -2
  10. package/dist/InMemory.d.mts +2 -2
  11. package/dist/{MessageDelivery-BGDm-c46.d.mts → MessageDelivery-DjAZJ4Fd.d.mts} +4 -4
  12. package/dist/{MessagingHost-CRUK4JBi.mjs → MessagingHost-CZCyrnvJ.mjs} +27 -26
  13. package/dist/MessagingHost-CZCyrnvJ.mjs.map +1 -0
  14. package/dist/{PreparedInputAdmission-BvDqlgiP.d.mts → PreparedInputAdmission-wgY4JMZY.d.mts} +3 -3
  15. package/dist/{Records-BiuJDWNe.mjs → Records-DzL4Kbtw.mjs} +2 -2
  16. package/dist/{Records-BiuJDWNe.mjs.map → Records-DzL4Kbtw.mjs.map} +1 -1
  17. package/dist/{RunContinuation-CTJ1jGrO.d.mts → RunContinuation-3jB5pULC.d.mts} +10 -4
  18. package/dist/{RunContinuation-4ZATcVLB.mjs → RunContinuation-BiyKJK32.mjs} +74 -40
  19. package/dist/RunContinuation-BiyKJK32.mjs.map +1 -0
  20. package/dist/{RunJournal-CNZqA3Ge.mjs → RunJournal-CPMKcfd0.mjs} +84 -79
  21. package/dist/RunJournal-CPMKcfd0.mjs.map +1 -0
  22. package/dist/{RunStorage-BHjuuERI.mjs → RunStorage-CzutHJBE.mjs} +3 -2
  23. package/dist/RunStorage-CzutHJBE.mjs.map +1 -0
  24. package/dist/{Scheduling-NG8slKdI.mjs → Scheduling-07_dsGED.mjs} +3 -3
  25. package/dist/{Scheduling-NG8slKdI.mjs.map → Scheduling-07_dsGED.mjs.map} +1 -1
  26. package/dist/{Subagent-CTIpnzlB.mjs → Subagent-CexndZSp.mjs} +2 -2
  27. package/dist/{Subagent-CTIpnzlB.mjs.map → Subagent-CexndZSp.mjs.map} +1 -1
  28. package/dist/{Subagent-DKhZA72u.d.mts → Subagent-xhN_aD1r.d.mts} +3 -3
  29. package/dist/{Subscription-BNg9VCRl.d.mts → Subscription-DmIHlOJK.d.mts} +2 -2
  30. package/dist/{SubscriptionInput-BUoXv-_K.d.mts → SubscriptionInput-DgcQhvbe.d.mts} +2 -2
  31. package/dist/{Thread-dk-QiNml.d.mts → Thread-DzdJAY4J.d.mts} +4 -3
  32. package/dist/{ThreadHistory-ByxS8HU1.d.mts → ThreadHistory-KPPBJLJb.d.mts} +12 -2
  33. package/dist/{ThreadImport-CodYoJoa.mjs → ThreadImport-CjlG57vH.mjs} +8 -8
  34. package/dist/{ThreadImport-CodYoJoa.mjs.map → ThreadImport-CjlG57vH.mjs.map} +1 -1
  35. package/dist/{ThreadImport-CMgzttvr.d.mts → ThreadImport-DP-vOk2F.d.mts} +4 -4
  36. package/dist/{ThreadStore-59uDGXul.mjs → ThreadStore-EuUilthA.mjs} +5 -7
  37. package/dist/ThreadStore-EuUilthA.mjs.map +1 -0
  38. package/dist/{ThreadTransfer-3jigUl3g.mjs → ThreadTransfer-DB_zsrtJ.mjs} +4 -4
  39. package/dist/{ThreadTransfer-3jigUl3g.mjs.map → ThreadTransfer-DB_zsrtJ.mjs.map} +1 -1
  40. package/dist/{WorkerAdmission-DDYJdOsU.mjs → WorkerAdmission-eBeXt_mu.mjs} +5 -5
  41. package/dist/{WorkerAdmission-DDYJdOsU.mjs.map → WorkerAdmission-eBeXt_mu.mjs.map} +1 -1
  42. package/dist/{agent-registration-DRbyhAu-.mjs → agent-registration-a6QYaG3h.mjs} +4 -4
  43. package/dist/{agent-registration-DRbyhAu-.mjs.map → agent-registration-a6QYaG3h.mjs.map} +1 -1
  44. package/dist/capabilities/Messaging.mjs +1 -1
  45. package/dist/capabilities/ModelContext.d.mts +1 -1
  46. package/dist/capabilities/RunHooks.d.mts +1 -1
  47. package/dist/capabilities/Subagent.d.mts +1 -1
  48. package/dist/capabilities/Subagent.mjs +1 -1
  49. package/dist/core/Thread.d.mts +2 -2
  50. package/dist/core/Thread.mjs +5 -3
  51. package/dist/core/Thread.mjs.map +1 -1
  52. package/dist/durable/ActivityStore.mjs +1 -1
  53. package/dist/durable/Admin.d.mts +1 -1
  54. package/dist/durable/Admin.mjs +1 -1
  55. package/dist/durable/AgentRegistration.d.mts +1 -1
  56. package/dist/durable/AgentRegistration.mjs +1 -1
  57. package/dist/durable/Certification.d.mts +1 -1
  58. package/dist/durable/CommittedActivity.d.mts +1 -1
  59. package/dist/durable/CommittedActivity.mjs +3 -3
  60. package/dist/durable/Digest.mjs +1 -1
  61. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  62. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  63. package/dist/durable/EventSource.d.mts +1 -1
  64. package/dist/durable/EventSource.mjs +1 -1
  65. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  66. package/dist/durable/LifecyclePublication.d.mts +1 -1
  67. package/dist/durable/LifecyclePublication.mjs +2 -2
  68. package/dist/durable/MessageDelivery.d.mts +1 -1
  69. package/dist/durable/MessageDelivery.mjs +2 -2
  70. package/dist/durable/MessageDeliveryStoreConformance.d.mts +40 -40
  71. package/dist/durable/MessageDeliveryStoreConformance.mjs +2 -2
  72. package/dist/durable/PersistentHistory.d.mts +2 -2
  73. package/dist/durable/PersistentHistory.mjs +4 -4
  74. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  75. package/dist/durable/RecordFormat.mjs +1 -1
  76. package/dist/durable/Records.mjs +1 -1
  77. package/dist/durable/Recovery.d.mts +1 -1
  78. package/dist/durable/Recovery.mjs +1 -1
  79. package/dist/durable/RunContinuation.d.mts +2 -2
  80. package/dist/durable/RunContinuation.mjs +3 -3
  81. package/dist/durable/RunJournal.d.mts +2 -2
  82. package/dist/durable/RunJournal.mjs +2 -2
  83. package/dist/durable/RunStorage.d.mts +1 -1
  84. package/dist/durable/RunStorage.mjs +1 -1
  85. package/dist/durable/Schedule.mjs +1 -1
  86. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  87. package/dist/durable/ScheduleTransition.mjs +1 -1
  88. package/dist/durable/Scheduling.d.mts +1 -1
  89. package/dist/durable/Scheduling.mjs +1 -1
  90. package/dist/durable/SettlementPublisher.d.mts +1 -1
  91. package/dist/durable/SettlementPublisher.mjs +3 -3
  92. package/dist/durable/SqlMemoryStore.mjs +5 -3
  93. package/dist/durable/SqlMemoryStore.mjs.map +1 -1
  94. package/dist/durable/SubmissionLedger.d.mts +1 -1
  95. package/dist/durable/SubmissionLedger.mjs +7 -3
  96. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  97. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  98. package/dist/durable/SubmissionLedgerConformance.mjs +6 -6
  99. package/dist/durable/SubmissionStatus.d.mts +1 -1
  100. package/dist/durable/Subscription.d.mts +1 -1
  101. package/dist/durable/Subscription.mjs +2 -2
  102. package/dist/durable/SubscriptionInput.d.mts +1 -1
  103. package/dist/durable/SubscriptionInput.mjs +2 -2
  104. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  105. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  106. package/dist/durable/SubscriptionTools.d.mts +2 -2
  107. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  108. package/dist/durable/SubscriptionTransition.mjs +1 -1
  109. package/dist/durable/Subscriptions.d.mts +5 -5
  110. package/dist/durable/Subscriptions.mjs +2 -2
  111. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  112. package/dist/durable/ThreadArchiveRange.mjs +1 -1
  113. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  114. package/dist/durable/ThreadContextHistory.mjs +2 -2
  115. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  116. package/dist/durable/ThreadImport.d.mts +1 -1
  117. package/dist/durable/ThreadImport.mjs +1 -1
  118. package/dist/durable/ThreadInvariants.d.mts +3 -3
  119. package/dist/durable/ThreadInvariants.mjs +4 -4
  120. package/dist/durable/ThreadProjection.mjs +1 -1
  121. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  122. package/dist/durable/ThreadReader.d.mts +1 -1
  123. package/dist/durable/ThreadReader.mjs +1 -1
  124. package/dist/durable/ThreadStore.d.mts +1 -1
  125. package/dist/durable/ThreadStore.mjs +2 -2
  126. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  127. package/dist/durable/ThreadStoreConformance.mjs +4 -4
  128. package/dist/durable/ThreadTransfer.d.mts +1 -1
  129. package/dist/durable/ThreadTransfer.mjs +1 -1
  130. package/dist/durable/ThreadWork.d.mts +2 -2
  131. package/dist/durable/ThreadWork.mjs +12 -6
  132. package/dist/durable/ThreadWork.mjs.map +1 -1
  133. package/dist/durable/ToolReconciler.mjs +1 -1
  134. package/dist/durable/WakeScheduler.mjs +23 -39
  135. package/dist/durable/WakeScheduler.mjs.map +1 -1
  136. package/dist/durable/WorkerAdmission.d.mts +1 -1
  137. package/dist/durable/WorkerAdmission.mjs +1 -1
  138. package/dist/durable/WorkerHost.d.mts +1 -1
  139. package/dist/engine/AgentRuntime.d.mts +1 -1
  140. package/dist/engine/AgentRuntime.mjs +1 -1
  141. package/dist/engine/MessagingHost.mjs +1 -1
  142. package/dist/engine/Output.mjs +1 -1
  143. package/dist/engine/ThreadHistory.d.mts +1 -1
  144. package/dist/engine/ThreadHistory.mjs +3 -2
  145. package/dist/engine/ThreadHistory.mjs.map +1 -1
  146. package/dist/index.d.mts +12 -12
  147. package/dist/index.mjs +13 -13
  148. package/dist/{output-contract-CMIhu4Cw.mjs → output-contract-DCnLYCdp.mjs} +12 -15
  149. package/dist/{output-contract-CMIhu4Cw.mjs.map → output-contract-DCnLYCdp.mjs.map} +1 -1
  150. package/dist/{run-context-reader-CC7ZD95F.d.mts → run-context-reader-DVhJZ1Qe.d.mts} +2 -2
  151. package/package.json +1 -1
  152. package/src/core/Thread.ts +143 -146
  153. package/src/durable/DurableAgentRuntime.ts +381 -206
  154. package/src/durable/RunContinuation.ts +113 -62
  155. package/src/durable/RunJournal.ts +99 -108
  156. package/src/durable/RunStorage.ts +7 -0
  157. package/src/durable/SqlMemoryStore.ts +8 -6
  158. package/src/durable/SubmissionLedger.ts +7 -0
  159. package/src/durable/ThreadStore.ts +9 -7
  160. package/src/durable/ThreadWork.ts +10 -0
  161. package/src/durable/WakeScheduler.ts +34 -66
  162. package/src/durable/internal/usage-totals.ts +28 -31
  163. package/src/engine/MessagingHost.ts +4 -2
  164. package/src/engine/ThreadHistory.ts +47 -48
  165. package/src/engine/internal/agent-runtime.ts +234 -202
  166. package/src/engine/internal/output-contract.ts +15 -15
  167. package/src/engine/internal/primitive-delta.ts +2 -3
  168. package/src/engine/internal/tool-telemetry.ts +3 -5
  169. package/dist/AgentRuntime-B6qZlD3f.mjs.map +0 -1
  170. package/dist/DurableAgentRuntime-CBBjRNJN.mjs.map +0 -1
  171. package/dist/MessagingHost-CRUK4JBi.mjs.map +0 -1
  172. package/dist/RunContinuation-4ZATcVLB.mjs.map +0 -1
  173. package/dist/RunJournal-CNZqA3Ge.mjs.map +0 -1
  174. package/dist/RunStorage-BHjuuERI.mjs.map +0 -1
  175. package/dist/ThreadStore-59uDGXul.mjs.map +0 -1
@@ -5,7 +5,7 @@ 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 { reference, resolveEvidence } from "./internal/evidence.ts";
8
+ import { resolveEvidence } from "./internal/evidence.ts";
9
9
  import { continuationUsage } from "./internal/usage-totals.ts";
10
10
  export { reference, resolveEvidence } from "./internal/evidence.ts";
11
11
  import {
@@ -157,17 +157,14 @@ const isFrozenJsonData = (input: Schema.Json): boolean => {
157
157
  return true;
158
158
  };
159
159
 
160
- const validateBatchHeader = Schema.decodeSync(
160
+ const validateBatchShape = Schema.decodeUnknownSync(
161
161
  Schema.Struct({
162
162
  batchId: CanonicalBatch.fields.batchId,
163
163
  producerId: CanonicalBatch.fields.producerId,
164
+ records: Schema.NonEmptyArray(Schema.Unknown).check(Schema.isMaxLength(256)),
164
165
  }),
165
166
  );
166
167
 
167
- const validateBatchCount = Schema.decodeUnknownSync(
168
- Schema.NonEmptyArray(Schema.Unknown).check(Schema.isMaxLength(256)),
169
- );
170
-
171
168
  const ContinuationBytes = Schema.Struct({
172
169
  turnBytes: RunContinuation.fields.turnBytes,
173
170
  terminalBytes: RunContinuation.fields.terminalBytes,
@@ -185,15 +182,20 @@ const captureFacts = (batch: CommitBatch) =>
185
182
  Effect.try({
186
183
  try: () =>
187
184
  Result.gen(function* () {
188
- const header = validateBatchHeader(batch);
185
+ const inputs = batch.records;
189
186
 
190
- validateBatchCount(batch.records);
191
- const encodings = yield* Result.all(batch.records.map(captureRecord));
187
+ const { batchId, producerId } = validateBatchShape({
188
+ batchId: batch.batchId,
189
+ producerId: batch.producerId,
190
+ records: inputs,
191
+ });
192
+
193
+ const encodings = yield* Result.all(inputs.map(captureRecord));
192
194
  const records = encodings.map((record) => record.canonical);
193
195
 
194
196
  return Object.freeze(
195
197
  CanonicalBatch.make(
196
- { ...header, records: [records[0]!, ...records.slice(1)] },
198
+ { batchId, producerId, records: [records[0]!, ...records.slice(1)] },
197
199
  { disableChecks: true },
198
200
  ),
199
201
  );
@@ -236,14 +238,6 @@ export const prepareProgressAppend = (
236
238
  return Object.freeze(encodings.success.map((record) => record.progress));
237
239
  };
238
240
 
239
- const factUsageCharge = (facts: ReadonlyArray<RecordEnvelope>) =>
240
- Effect.fromResult(Result.all(facts.map(recordEncoding))).pipe(
241
- Effect.map((encodings) =>
242
- encodings.reduce((bytes, record) => bytes + record.progress.terminalUsageBytes, 0),
243
- ),
244
- Effect.mapError((cause) => failure("Cannot account canonical progress facts", cause)),
245
- );
246
-
247
241
  /** These values belong to the active Run, not its permanent conversation history. */
248
242
  export const isOperationalRecord = ({ payload }: RecordEnvelope): boolean =>
249
243
  payload._tag === "RunStarted" ||
@@ -717,9 +711,27 @@ const advanceFacts = Effect.fnUntraced(function* (
717
711
  let results = new Map(previous?.results);
718
712
  let childResults = new Map(previous?.childResults);
719
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;
720
719
 
721
720
  for (const fact of facts) {
722
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
+ }
723
735
 
724
736
  switch (payload._tag) {
725
737
  case "RunStarted":
@@ -728,7 +740,7 @@ const advanceFacts = Effect.fnUntraced(function* (
728
740
  case "RunContextRecorded":
729
741
  if (savedContext !== undefined)
730
742
  return yield* failure("A Run's original context cannot be replaced");
731
- savedContext = yield* reference(fact);
743
+ savedContext = EvidenceReference.make({ recordId: fact.recordId });
732
744
  position = "awaiting-model";
733
745
  break;
734
746
  case "ModelResponseRecorded": {
@@ -782,7 +794,7 @@ const advanceFacts = Effect.fnUntraced(function* (
782
794
  accounting.lastInputTokens = summary?.inputTokens ?? payload.inputTokens ?? 0;
783
795
  accounting.lastOutputTokens = summary?.outputTokens ?? payload.outputTokens ?? 0;
784
796
  accounting.unobservedModelCalls += payload.unobservedModelCalls ?? 0;
785
- latestResponse = yield* reference(fact);
797
+ latestResponse = EvidenceReference.make({ recordId: fact.recordId });
786
798
  response = payload;
787
799
  results = new Map();
788
800
  childResults = new Map();
@@ -866,12 +878,12 @@ const advanceFacts = Effect.fnUntraced(function* (
866
878
  ? 0
867
879
  : utf8ByteLength(JSON.stringify(payload.runDisposition)))
868
880
  : undefined;
869
- terminal = yield* reference(fact);
881
+ terminal = EvidenceReference.make({ recordId: fact.recordId });
870
882
  position = "settling";
871
883
  break;
872
884
  case "SubmissionSettled":
873
885
  if (payload.submissionId !== submissionId) break;
874
- terminal = yield* reference(fact);
886
+ terminal = EvidenceReference.make({ recordId: fact.recordId });
875
887
  position = "settled";
876
888
  if (payload.usageSummary !== undefined) {
877
889
  accounting.modelCalls = payload.usageSummary.modelCalls;
@@ -929,22 +941,13 @@ const advanceFacts = Effect.fnUntraced(function* (
929
941
  if (previous?.continuation.position === "settled") position = "settled";
930
942
  else if (previous?.continuation.position === "settling" && position !== "settled")
931
943
  position = "settling";
932
- const factBytes = facts.reduce((bytes, fact) => bytes + canonicalRecordBytes(fact), 0);
933
-
934
- const turn = facts.reduce(
935
- (current, fact) => ("turn" in fact.payload ? Math.max(current, fact.payload.turn) : current),
936
- previous?.continuation.turn ?? 1,
937
- );
938
944
 
939
945
  const turnBase =
940
946
  (previous?.continuation.turn === turn ? previous.continuation.turnBytes : initialBytes) +
941
947
  factBytes;
942
948
 
943
- const usageCharge = yield* factUsageCharge(facts);
944
949
  const terminalUsageBytes = (previous?.continuation.terminalUsageBytes ?? 0) + usageCharge;
945
950
 
946
- const closing = facts.filter(isTerminalBudgetFact);
947
-
948
951
  return {
949
952
  accounting,
950
953
  savedContext,
@@ -961,11 +964,9 @@ const advanceFacts = Effect.fnUntraced(function* (
961
964
  turnBase,
962
965
  usageCharge,
963
966
  terminalUsageBytes,
964
- terminalBytes:
965
- (previous?.continuation.terminalBytes ?? 0) +
966
- closing.reduce((bytes, fact) => bytes + canonicalRecordBytes(fact), 0),
967
- terminalRecords: (previous?.continuation.terminalRecords ?? 0) + closing.length,
968
- closing: closing.length > 0,
967
+ terminalBytes: (previous?.continuation.terminalBytes ?? 0) + closingBytes,
968
+ terminalRecords: (previous?.continuation.terminalRecords ?? 0) + closingCount,
969
+ closing: closingCount > 0,
969
970
  };
970
971
  });
971
972
 
@@ -1046,6 +1047,14 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1046
1047
  const gate = yield* Semaphore.make(1);
1047
1048
  const cached = new Map<RunId, ProgressState>();
1048
1049
 
1050
+ let verifiedEmpty:
1051
+ | {
1052
+ readonly runId: RunId;
1053
+ readonly through: CanonicalSequence;
1054
+ readonly writer: CurrentRunWriter["Service"];
1055
+ }
1056
+ | undefined;
1057
+
1049
1058
  const reservations = new Map<
1050
1059
  RecordId,
1051
1060
  {
@@ -1180,13 +1189,6 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1180
1189
  };
1181
1190
  });
1182
1191
 
1183
- const loadState = (runId: RunId, after: CanonicalSequence) =>
1184
- Effect.suspend(() => {
1185
- const retained = cached.get(runId);
1186
-
1187
- return retained === undefined ? loadUncachedState(runId, after) : Effect.succeed(retained);
1188
- });
1189
-
1190
1192
  const checkFutureShapes = (
1191
1193
  state: ProgressState,
1192
1194
  cursorBytes?: number,
@@ -1316,12 +1318,9 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1316
1318
  if (batch.records.some((record) => record.payload._tag === "RunContinuation"))
1317
1319
  return yield* failure("A caller cannot supply interpreter progress to the progress writer");
1318
1320
 
1319
- const ownedRecords = yield* Effect.fromResult(
1320
- Result.all(batch.records.map(captureRecord)),
1321
- ).pipe(
1322
- Effect.map((encodings) => encodings.map((record) => record.canonical)),
1323
- Effect.mapError((cause) => failure("Cannot capture canonical progress facts", cause)),
1324
- );
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];
1325
1324
 
1326
1325
  const groups = new Map<RunId, Array<RecordEnvelope>>();
1327
1326
 
@@ -1338,7 +1337,16 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1338
1337
  let executionCount = 0;
1339
1338
 
1340
1339
  for (const [runId, facts] of groups) {
1341
- 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)));
1342
1350
 
1343
1351
  let original = facts.find(
1344
1352
  (record) =>
@@ -1379,7 +1387,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1379
1387
  if (previous === undefined && original === undefined)
1380
1388
  return yield* failure("Run has no accepted original input");
1381
1389
 
1382
- if (previous === undefined) {
1390
+ if (previous === undefined && !empty) {
1383
1391
  const settled = yield* provide(
1384
1392
  getRecord({
1385
1393
  threadId,
@@ -1460,7 +1468,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1460
1468
  ? previous.continuation.originalInput
1461
1469
  : original === undefined
1462
1470
  ? yield* failure("Run progress has no original input")
1463
- : yield* reference(original);
1471
+ : EvidenceReference.make({ recordId: original.recordId });
1464
1472
 
1465
1473
  const next = yield* advanceFacts(previous, facts, submissionId, initialBytes);
1466
1474
 
@@ -1498,7 +1506,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1498
1506
  ...(savedContext === undefined ? {} : { savedContext }),
1499
1507
  ...(latestResponse === undefined ? {} : { latestResponse }),
1500
1508
  ...(terminal === undefined ? {} : { terminal }),
1501
- lastFact: yield* reference(last),
1509
+ lastFact: EvidenceReference.make({ recordId: last.recordId }),
1502
1510
  position,
1503
1511
  accounting,
1504
1512
  }).pipe(
@@ -1824,7 +1832,7 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1824
1832
 
1825
1833
  yield* prepare(yield* captureFacts(probe), after);
1826
1834
  const recordId = probe.records[0].recordId;
1827
- const state = yield* loadState(runId, after);
1835
+ const state = cached.get(runId) ?? (yield* loadUncachedState(runId, after));
1828
1836
 
1829
1837
  if (state === undefined) return yield* failure("Dispatch has no canonical Run progress");
1830
1838
  if (reservations.has(recordId))
@@ -1859,7 +1867,10 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1859
1867
 
1860
1868
  if (response === undefined || response.payload._tag !== "ModelResponseRecorded")
1861
1869
  return yield* failure("Deferred dispatch has no response");
1862
- 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));
1863
1874
 
1864
1875
  if (progress === undefined) return yield* failure("Deferred dispatch has no Run progress");
1865
1876
 
@@ -1893,16 +1904,33 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1893
1904
  }),
1894
1905
  );
1895
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
+
1896
1919
  const check = (runId: RunId) =>
1897
1920
  gate.withPermits(1)(
1898
- Effect.gen(function* () {
1899
- 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"));
1924
+
1925
+ const retained = cached.get(runId);
1900
1926
 
1901
- if (state === undefined) return yield* failure("Execution has no canonical Run progress");
1902
- // Accepted states were shape-checked during preparation; recovered states still need proof.
1903
- if (cached.get(runId) !== state) yield* checkFutureShapes(state);
1904
- if (state.continuation.position !== "settling" && state.continuation.position !== "settled")
1905
- yield* checkCapacity(state);
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
+ );
1906
1934
  }),
1907
1935
  );
1908
1936
 
@@ -1912,6 +1940,29 @@ export const makeProgressWriter = Effect.fnUntraced(function* (
1912
1940
  reserve,
1913
1941
  defer,
1914
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
+ }),
1915
1966
  stageUsage: (runId: RunId, bytes: number) => {
1916
1967
  if (bytes === 0) stagedUsage.delete(runId);
1917
1968
  else stagedUsage.set(runId, bytes);
@@ -13,7 +13,7 @@ import { type ExhaustedLimit } from "../core/RunEvent.ts";
13
13
  import { RunPolicyUsage } from "../core/RunPolicyUsage.ts";
14
14
  import { type Selection, type Snapshot } from "../core/ToolExposure.ts";
15
15
  import type { ToolParameterRejection } from "../core/ToolResult.ts";
16
- import { ModelCallUsage, summarizeModelUsage, type RunUsageSummary } from "../core/Usage.ts";
16
+ import { type ModelCallUsage, summarizeModelUsage, type RunUsageSummary } from "../core/Usage.ts";
17
17
  import type { WorkerRef } from "../core/Worker.ts";
18
18
  import {
19
19
  CLEARED_TOOL_RESULT,
@@ -436,6 +436,36 @@ export interface RunJournalProjection {
436
436
  readonly pendingContextToolCallId?: string | undefined;
437
437
  }
438
438
 
439
+ const initialPolicyUsage = () => ({
440
+ committedTurns: 0,
441
+ toolCalls: 0,
442
+ programmaticToolCalls: 0,
443
+ consecutiveToolFailures: 0,
444
+ finalizationUsed: false,
445
+ modelRestarts: 0,
446
+ });
447
+
448
+ const initialUsage = (modelUsage: Array<ModelCallUsage>) => ({
449
+ modelCalls: 0,
450
+ inputTokens: 0,
451
+ outputTokens: 0,
452
+ lastInputTokens: 0,
453
+ lastOutputTokens: 0,
454
+ costMicrousd: 0,
455
+ modelUsage,
456
+ });
457
+
458
+ /** Internal first-Run projection after exact input/checkpoint-absence verification. The
459
+ * new UserInputRecorded contributes no model message; the engine evaluates it later. */
460
+ export const initialRunJournal = (history: Prompt.Prompt): RunJournalProjection => ({
461
+ policyUsage: RunPolicyUsage.make(initialPolicyUsage()),
462
+ usage: initialUsage([]),
463
+ prompt: history,
464
+ historyBefore: history,
465
+ historyFrom: CanonicalSequence.make(1),
466
+ committedTurns: 0,
467
+ });
468
+
439
469
  interface ProjectedResponseUsage {
440
470
  readonly modelCalls: number;
441
471
  readonly inputTokens: number;
@@ -1050,15 +1080,7 @@ export const projectRunJournalStream = Effect.fnUntraced(function* <
1050
1080
  const modelUsage: Array<ModelCallUsage> = [];
1051
1081
  let unobservedModelCalls = 0;
1052
1082
 
1053
- const usage = {
1054
- modelCalls: 0,
1055
- inputTokens: 0,
1056
- outputTokens: 0,
1057
- lastInputTokens: 0,
1058
- lastOutputTokens: 0,
1059
- costMicrousd: 0,
1060
- modelUsage,
1061
- };
1083
+ const usage = initialUsage(modelUsage);
1062
1084
 
1063
1085
  let toolSelection: Selection | undefined;
1064
1086
  let usageTurn = 0;
@@ -1080,14 +1102,7 @@ export const projectRunJournalStream = Effect.fnUntraced(function* <
1080
1102
  let pendingContextToolCallId: string | undefined;
1081
1103
  let ownerTerminated = false;
1082
1104
 
1083
- const policyUsage = {
1084
- committedTurns: 0,
1085
- toolCalls: 0,
1086
- programmaticToolCalls: 0,
1087
- consecutiveToolFailures: 0,
1088
- finalizationUsed: false,
1089
- modelRestarts: 0,
1090
- };
1105
+ const policyUsage = initialPolicyUsage();
1091
1106
 
1092
1107
  const accountResponse = Effect.fnUntraced(function* (
1093
1108
  envelope: JournalRecordEnvelope,
@@ -1639,18 +1654,6 @@ export const promptFromCanonicalRecords = (
1639
1654
  );
1640
1655
 
1641
1656
  /** Everything one committed Turn contributes to its canonical batch. */
1642
- /**
1643
- * Staged usage is validated, never repaired: clamping negatives or truncating
1644
- * fractions would under-record canonical usage, and NaN/Infinity must fail
1645
- * typed instead of escaping as a record-construction defect (RUN-023).
1646
- */
1647
- const validStagedUsage = (label: string, value: number): Effect.Effect<number, RunJournalError> =>
1648
- Number.isSafeInteger(value) && value >= 0
1649
- ? Effect.succeed(value)
1650
- : Effect.fail(
1651
- journalError(`Staged ${label} must be a non-negative safe integer, got ${String(value)}`),
1652
- );
1653
-
1654
1657
  export interface TurnCommitInput {
1655
1658
  readonly toolResultMaxBytes?: number | undefined;
1656
1659
  readonly toolSelectionMaxBytes?: number | undefined;
@@ -1695,8 +1698,9 @@ export interface TurnCommitInput {
1695
1698
  }
1696
1699
 
1697
1700
  const decodePersistedJson = Schema.decodeUnknownEffect(PersistedJson);
1701
+ const decodeModelResponse = Schema.decodeUnknownEffect(ModelResponseRecorded);
1702
+ const decodeToolSettled = Schema.decodeUnknownEffect(ToolCallSettled);
1698
1703
  const encodePrompt = Schema.encodeEffect(Prompt.Prompt);
1699
- const decodeModelUsage = Schema.decodeUnknownEffect(Schema.Array(ModelCallUsage));
1700
1704
 
1701
1705
  const requireCanonicalTurn = (turn: number): Effect.Effect<void, RunJournalError> =>
1702
1706
  !Number.isInteger(turn) || turn <= 0
@@ -1742,30 +1746,60 @@ const modelResponseRecord = Effect.fnUntraced(function* (
1742
1746
  ),
1743
1747
  );
1744
1748
 
1745
- const modelUsage =
1746
- input.usage?.modelUsage === undefined
1747
- ? undefined
1748
- : yield* decodeModelUsage(input.usage.modelUsage).pipe(
1749
- Effect.mapError((cause) => journalError("Turn model usage failed to decode", cause)),
1750
- );
1749
+ // Validate the detached messages and usage together; no mutable caller data is trusted.
1750
+ const modelUsage = input.usage?.modelUsage;
1751
1751
 
1752
- if (modelUsage !== undefined) {
1753
- if (modelUsage.length === 0) {
1752
+ const payload = yield* decodeModelResponse({
1753
+ _tag: "ModelResponseRecorded",
1754
+ toolResultMaxBytes: input.toolResultMaxBytes ?? MAX_PERSISTED_JSON_BYTES,
1755
+ toolSelectionMaxBytes: input.toolSelectionMaxBytes ?? 0,
1756
+ toolOperations: input.toolOperations ?? [],
1757
+ ...(input.toolParameterRejections === undefined || input.toolParameterRejections.length === 0
1758
+ ? {}
1759
+ : { toolParameterRejections: input.toolParameterRejections }),
1760
+ ...(input.toolExposure === undefined ? {} : { toolExposure: input.toolExposure }),
1761
+ runId: input.runId,
1762
+ turnId: input.turnId,
1763
+ turn: input.turn,
1764
+ messages,
1765
+ ...(runScopedPrefixLength === undefined ? {} : { runScopedPrefixLength }),
1766
+ ...(modelUsage === undefined ? {} : { modelUsage }),
1767
+ ...(input.unobservedModelCalls === undefined || input.unobservedModelCalls === 0
1768
+ ? {}
1769
+ : {
1770
+ unobservedModelCalls: input.unobservedModelCalls,
1771
+ }),
1772
+ ...(input.usage === undefined
1773
+ ? {}
1774
+ : {
1775
+ inputTokens: input.usage.inputTokens,
1776
+ outputTokens: input.usage.outputTokens,
1777
+ // Written only when non-zero: absent re-seeds as zero, so the
1778
+ // no-estimator case stays byte-identical to pre-cost histories.
1779
+ ...(input.usage.costMicrousd === undefined || input.usage.costMicrousd === 0
1780
+ ? {}
1781
+ : {
1782
+ costMicrousd: input.usage.costMicrousd,
1783
+ }),
1784
+ }),
1785
+ }).pipe(
1786
+ Effect.mapError((cause) => journalError("Invalid model response operation evidence", cause)),
1787
+ );
1788
+
1789
+ if (payload.modelUsage !== undefined) {
1790
+ if (payload.modelUsage.length === 0)
1754
1791
  return yield* journalError("Turn model usage must contain at least one completed call");
1755
- }
1756
1792
 
1757
- const summary = yield* continuationUsage(modelUsage).pipe(
1793
+ const summary = yield* continuationUsage(payload.modelUsage).pipe(
1758
1794
  Effect.mapError((cause) => journalError("Turn model usage exceeds accounting bounds", cause)),
1759
1795
  );
1760
1796
 
1761
1797
  if (
1762
- input.usage === undefined ||
1763
- input.usage.inputTokens !== summary.inputTokens ||
1764
- input.usage.outputTokens !== summary.outputTokens ||
1765
- (input.usage.costMicrousd ?? 0) !== summary.costMicrousd
1766
- ) {
1798
+ payload.inputTokens !== summary.inputTokens ||
1799
+ payload.outputTokens !== summary.outputTokens ||
1800
+ (payload.costMicrousd ?? 0) !== summary.costMicrousd
1801
+ )
1767
1802
  return yield* journalError("Turn detailed and aggregate model usage disagree");
1768
- }
1769
1803
  }
1770
1804
 
1771
1805
  return RecordEnvelope.make({
@@ -1774,44 +1808,7 @@ const modelResponseRecord = Effect.fnUntraced(function* (
1774
1808
  schemaVersion: 1,
1775
1809
  createdAt: input.createdAt,
1776
1810
  deploymentId: input.deploymentId,
1777
- payload: yield* ModelResponseRecorded.makeEffect({
1778
- toolResultMaxBytes: input.toolResultMaxBytes ?? MAX_PERSISTED_JSON_BYTES,
1779
- toolSelectionMaxBytes: input.toolSelectionMaxBytes ?? 0,
1780
- toolOperations: input.toolOperations ?? [],
1781
- ...(input.toolParameterRejections === undefined || input.toolParameterRejections.length === 0
1782
- ? {}
1783
- : { toolParameterRejections: input.toolParameterRejections }),
1784
- ...(input.toolExposure === undefined ? {} : { toolExposure: input.toolExposure }),
1785
- runId: input.runId,
1786
- turnId: input.turnId,
1787
- turn: input.turn,
1788
- messages,
1789
- ...(runScopedPrefixLength === undefined ? {} : { runScopedPrefixLength }),
1790
- ...(modelUsage === undefined ? {} : { modelUsage }),
1791
- ...(input.unobservedModelCalls === undefined || input.unobservedModelCalls === 0
1792
- ? {}
1793
- : {
1794
- unobservedModelCalls: yield* validStagedUsage(
1795
- "unobservedModelCalls",
1796
- input.unobservedModelCalls,
1797
- ),
1798
- }),
1799
- ...(input.usage === undefined
1800
- ? {}
1801
- : {
1802
- inputTokens: yield* validStagedUsage("inputTokens", input.usage.inputTokens),
1803
- outputTokens: yield* validStagedUsage("outputTokens", input.usage.outputTokens),
1804
- // Written only when non-zero: absent re-seeds as zero, so the
1805
- // no-estimator case stays byte-identical to pre-cost histories.
1806
- ...(input.usage.costMicrousd === undefined || input.usage.costMicrousd === 0
1807
- ? {}
1808
- : {
1809
- costMicrousd: yield* validStagedUsage("costMicrousd", input.usage.costMicrousd),
1810
- }),
1811
- }),
1812
- }).pipe(
1813
- Effect.mapError((cause) => journalError("Invalid model response operation evidence", cause)),
1814
- ),
1811
+ payload,
1815
1812
  });
1816
1813
  });
1817
1814
 
@@ -1821,33 +1818,27 @@ const toolSettledRecords = Effect.fnUntraced(function* (
1821
1818
  const toolRecords: Array<RecordEnvelope> = [];
1822
1819
 
1823
1820
  for (const part of input.toolResults) {
1824
- const result = yield* decodePersistedJson(part.result).pipe(
1825
- Effect.mapError((cause) =>
1826
- journalError(`Tool result ${part.toolCallId} exceeds canonical persistence bounds`, cause),
1827
- ),
1821
+ const payload = yield* decodeToolSettled({
1822
+ _tag: "ToolCallSettled",
1823
+ ...(part.toolSelection === undefined ? {} : { toolSelection: part.toolSelection }),
1824
+ runId: input.runId,
1825
+ toolCallId: part.toolCallId,
1826
+ toolName: part.toolName,
1827
+ result: part.result,
1828
+ isFailure: part.isFailure,
1829
+ ...(part.budgetRejected === true ? { budgetRejected: true } : {}),
1830
+ }).pipe(
1831
+ Effect.mapError((cause) => journalError(`Invalid Tool result ${part.toolCallId}`, cause)),
1828
1832
  );
1829
1833
 
1830
- const toolCallId = yield* Effect.try({
1831
- try: () => decodeToolCallId(part.toolCallId),
1832
- catch: (cause) => journalError(`Invalid Tool Call ID ${part.toolCallId}`, cause),
1833
- });
1834
-
1835
1834
  toolRecords.push(
1836
1835
  RecordEnvelope.make({
1837
- recordId: toolCallSettledRecordId(input.runId, input.turn, toolCallId),
1836
+ recordId: toolCallSettledRecordId(input.runId, input.turn, payload.toolCallId),
1838
1837
  family: "thread",
1839
1838
  schemaVersion: 1,
1840
1839
  createdAt: input.createdAt,
1841
1840
  deploymentId: input.deploymentId,
1842
- payload: ToolCallSettled.make({
1843
- ...(part.toolSelection === undefined ? {} : { toolSelection: part.toolSelection }),
1844
- runId: input.runId,
1845
- toolCallId,
1846
- toolName: part.toolName,
1847
- result,
1848
- isFailure: part.isFailure,
1849
- ...(part.budgetRejected === true ? { budgetRejected: true } : {}),
1850
- }),
1841
+ payload,
1851
1842
  }),
1852
1843
  );
1853
1844
  }
@@ -1855,7 +1846,7 @@ const toolSettledRecords = Effect.fnUntraced(function* (
1855
1846
  return toolRecords;
1856
1847
  });
1857
1848
 
1858
- const runCompletionRecord = Effect.fnUntraced(function* (input: TurnCommitInput) {
1849
+ const runCompletionRecord = (input: TurnCommitInput) => {
1859
1850
  if (input.runCompletion === undefined) return undefined;
1860
1851
  const completion = { runId: input.runId, ...input.runCompletion };
1861
1852
 
@@ -1869,7 +1860,7 @@ const runCompletionRecord = Effect.fnUntraced(function* (input: TurnCommitInput)
1869
1860
  ...completion,
1870
1861
  }),
1871
1862
  });
1872
- });
1863
+ };
1873
1864
 
1874
1865
  /**
1875
1866
  * Build a canonical batch from interpreter-validated Turn facts: one
@@ -1887,7 +1878,7 @@ export const turnCanonicalBatch = Effect.fnUntraced(function* (
1887
1878
  yield* requireCanonicalTurn(input.turn);
1888
1879
  const modelResponse = yield* modelResponseRecord(input);
1889
1880
  const toolRecords = yield* toolSettledRecords(input);
1890
- const completionRecord = yield* runCompletionRecord(input);
1881
+ const completionRecord = runCompletionRecord(input);
1891
1882
 
1892
1883
  return CanonicalBatch.make({
1893
1884
  batchId: turnBatchId(input.runId, input.turn),
@@ -1931,7 +1922,7 @@ export const turnResultsBatch = Effect.fnUntraced(function* (
1931
1922
  yield* requireCanonicalTurn(input.turn);
1932
1923
  const toolRecords = yield* toolSettledRecords(input);
1933
1924
  const first = toolRecords[0];
1934
- const completionRecord = yield* runCompletionRecord(input);
1925
+ const completionRecord = runCompletionRecord(input);
1935
1926
 
1936
1927
  if (first === undefined) {
1937
1928
  if (completionRecord === undefined) {
@@ -113,6 +113,11 @@ export interface RunWriter {
113
113
 
114
114
  export interface RunStorageSession extends RunWriter, RunOwnership {
115
115
  readonly claim: Claim;
116
+ /** Library-only initialization, before application/model IO. Hold progress before mutation
117
+ * so the captured controls and this owner's acknowledged input remain one read view. */
118
+ readonly withInitialization?: <A, E, R>(
119
+ effect: Effect.Effect<A, E, R>,
120
+ ) => Effect.Effect<A, E | OwnershipLost, R>;
116
121
  readonly refresh: Effect.Effect<void, ThreadStoreFailure>;
117
122
  readonly publishSettlement: (
118
123
  batch: CanonicalBatch,
@@ -316,6 +321,8 @@ export const make = Effect.gen(function* () {
316
321
  const session: RunStorageSession = {
317
322
  ...writer,
318
323
  claim: claimed,
324
+ withInitialization: (effect) =>
325
+ owner.withProgress(request.threadId, generation.use(effect)),
319
326
  release: generation.release,
320
327
  refresh: writer.refresh,
321
328
  markInputApplied: (marker) =>