effect-agent 0.1.0-beta.97 → 0.1.0-beta.99

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 (143) hide show
  1. package/dist/{AgentRuntime-B64362Id.d.mts → AgentRuntime-BMzrJos6.d.mts} +2 -2
  2. package/dist/{AgentRuntime-Cdvqw7l_.mjs → AgentRuntime-BWOnNc0d.mjs} +78 -42
  3. package/dist/AgentRuntime-BWOnNc0d.mjs.map +1 -0
  4. package/dist/{DurableAgentRuntime-DbUvZHDZ.mjs → DurableAgentRuntime-BE_OjQjz.mjs} +471 -252
  5. package/dist/DurableAgentRuntime-BE_OjQjz.mjs.map +1 -0
  6. package/dist/{DurableFailpoint-DQJet828.d.mts → DurableFailpoint-mx3-nsxU.d.mts} +3 -3
  7. package/dist/{EventSource-D6PAAEyK.d.mts → EventSource-BZ73J0s5.d.mts} +3 -3
  8. package/dist/{PreparedInputAdmission-omMbwd--.d.mts → PreparedInputAdmission-BkeI-Xvr.d.mts} +4 -4
  9. package/dist/{Records-CV49DgQK.d.mts → Records-BIiRLqlz.d.mts} +92 -128
  10. package/dist/{Records-BAutSSnJ.mjs → Records-DR1xtbXY.mjs} +35 -8
  11. package/dist/Records-DR1xtbXY.mjs.map +1 -0
  12. package/dist/{RunJournal-DRRRZmG7.mjs → RunJournal-DNs3PM4K.mjs} +80 -23
  13. package/dist/RunJournal-DNs3PM4K.mjs.map +1 -0
  14. package/dist/{RunOptions-Clj8cLsp.d.mts → RunOptions-Bruk54SE.d.mts} +13 -4
  15. package/dist/{Schedule-DePbGfgd.d.mts → Schedule-BNFlluAl.d.mts} +2 -2
  16. package/dist/{Scheduling-C3QpYn0W.mjs → Scheduling-HFH0pST1.mjs} +2 -2
  17. package/dist/{Scheduling-C3QpYn0W.mjs.map → Scheduling-HFH0pST1.mjs.map} +1 -1
  18. package/dist/{Subagent-Cbfzylz1.mjs → Subagent-C2s8PFrd.mjs} +2 -2
  19. package/dist/{Subagent-Cbfzylz1.mjs.map → Subagent-C2s8PFrd.mjs.map} +1 -1
  20. package/dist/{Subagent-Bm1dRecN.d.mts → Subagent-m7ywTCe4.d.mts} +3 -3
  21. package/dist/{Subscription-BR_8Cxh3.d.mts → Subscription-BCQ3L9Zp.d.mts} +3 -3
  22. package/dist/{SubscriptionInput-B95sQbuk.d.mts → SubscriptionInput-Cqu4_RW3.d.mts} +3 -3
  23. package/dist/{ToolReconciler-DQVaJfpE.d.mts → ToolReconciler-BmVRAHKs.d.mts} +8 -4
  24. package/dist/agent-registration-cXpB8cBU.mjs +199 -0
  25. package/dist/agent-registration-cXpB8cBU.mjs.map +1 -0
  26. package/dist/capabilities/Commands.d.mts +1 -1
  27. package/dist/capabilities/RunHooks.d.mts +1 -1
  28. package/dist/capabilities/Subagent.d.mts +1 -1
  29. package/dist/capabilities/Subagent.mjs +1 -1
  30. package/dist/durable/ActivityStore.d.mts +1 -1
  31. package/dist/durable/ActivityStore.mjs +1 -1
  32. package/dist/durable/Admin.d.mts +1 -1
  33. package/dist/durable/Admin.mjs +12 -10
  34. package/dist/durable/Admin.mjs.map +1 -1
  35. package/dist/durable/AgentRegistration.d.mts +2 -2
  36. package/dist/durable/AgentRegistration.mjs +4 -8
  37. package/dist/durable/AgentRegistration.mjs.map +1 -1
  38. package/dist/durable/Certification.d.mts +2 -2
  39. package/dist/durable/CommittedActivity.d.mts +1 -1
  40. package/dist/durable/CommittedActivity.mjs +1 -1
  41. package/dist/durable/Digest.d.mts +1 -1
  42. package/dist/durable/Digest.mjs +1 -1
  43. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  44. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  45. package/dist/durable/DurableFailpoint.d.mts +1 -1
  46. package/dist/durable/DurableFailpoint.mjs +2 -0
  47. package/dist/durable/DurableFailpoint.mjs.map +1 -1
  48. package/dist/durable/DurableFailpointTestControl.d.mts +1 -1
  49. package/dist/durable/EventSource.d.mts +1 -1
  50. package/dist/durable/EventSource.mjs +1 -1
  51. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  52. package/dist/durable/GitHubWorkflowSource.mjs +1 -1
  53. package/dist/durable/MessageDelivery.d.mts +3 -3
  54. package/dist/durable/MessageDelivery.mjs +1 -1
  55. package/dist/durable/MessageDeliveryStoreConformance.d.mts +39 -39
  56. package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
  57. package/dist/durable/PersistentHistory.d.mts +1 -1
  58. package/dist/durable/PersistentHistory.mjs +2 -2
  59. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  60. package/dist/durable/Records.d.mts +2 -2
  61. package/dist/durable/Records.mjs +2 -2
  62. package/dist/durable/Recovery.d.mts +1 -1
  63. package/dist/durable/Recovery.mjs +9 -9
  64. package/dist/durable/Recovery.mjs.map +1 -1
  65. package/dist/durable/RunJournal.d.mts +2 -2
  66. package/dist/durable/RunJournal.mjs +2 -2
  67. package/dist/durable/Schedule.d.mts +1 -1
  68. package/dist/durable/Schedule.mjs +1 -1
  69. package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
  70. package/dist/durable/ScheduleStoreConformance.mjs +1 -1
  71. package/dist/durable/ScheduleTransition.d.mts +1 -1
  72. package/dist/durable/ScheduleTransition.mjs +1 -1
  73. package/dist/durable/Scheduling.d.mts +2 -2
  74. package/dist/durable/Scheduling.mjs +1 -1
  75. package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
  76. package/dist/durable/SqlStorageV2Upgrade.mjs +1 -1
  77. package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
  78. package/dist/durable/SqlSubscriptionStore.mjs +1 -1
  79. package/dist/durable/SubmissionLedger.d.mts +1 -1
  80. package/dist/durable/SubmissionLedger.mjs +14 -13
  81. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  82. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  83. package/dist/durable/SubmissionLedgerConformance.mjs +9 -2
  84. package/dist/durable/SubmissionLedgerConformance.mjs.map +1 -1
  85. package/dist/durable/SubmissionStatus.d.mts +1 -1
  86. package/dist/durable/Subscription.d.mts +1 -1
  87. package/dist/durable/Subscription.mjs +1 -1
  88. package/dist/durable/SubscriptionInput.d.mts +1 -1
  89. package/dist/durable/SubscriptionInput.mjs +2 -2
  90. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  91. package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
  92. package/dist/durable/SubscriptionTools.d.mts +3 -3
  93. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  94. package/dist/durable/SubscriptionTransition.mjs +1 -1
  95. package/dist/durable/Subscriptions.d.mts +5 -5
  96. package/dist/durable/Subscriptions.mjs +1 -1
  97. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  98. package/dist/durable/ThreadContextHistory.mjs +1 -1
  99. package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
  100. package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
  101. package/dist/durable/ThreadInvariants.d.mts +4 -2
  102. package/dist/durable/ThreadInvariants.mjs +49 -3
  103. package/dist/durable/ThreadInvariants.mjs.map +1 -1
  104. package/dist/durable/ThreadProjection.d.mts +1 -1
  105. package/dist/durable/ThreadProjection.mjs +1 -1
  106. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  107. package/dist/durable/ThreadStore.d.mts +1 -1
  108. package/dist/durable/ThreadStore.mjs +1 -1
  109. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  110. package/dist/durable/ThreadStoreConformance.mjs +1 -1
  111. package/dist/durable/ToolReconciler.d.mts +1 -1
  112. package/dist/durable/ToolReconciler.mjs +9 -5
  113. package/dist/durable/ToolReconciler.mjs.map +1 -1
  114. package/dist/durable/WorkerHost.d.mts +1 -1
  115. package/dist/engine/AgentRuntime.d.mts +1 -1
  116. package/dist/engine/AgentRuntime.mjs +1 -1
  117. package/dist/engine/RunOptions.d.mts +1 -1
  118. package/dist/engine/RunOptions.mjs.map +1 -1
  119. package/dist/index.d.mts +11 -11
  120. package/dist/index.mjs +6 -6
  121. package/package.json +1 -1
  122. package/src/durable/Admin.ts +20 -11
  123. package/src/durable/AgentRegistration.ts +1 -5
  124. package/src/durable/DurableAgentRuntime.ts +947 -464
  125. package/src/durable/DurableFailpoint.ts +2 -0
  126. package/src/durable/Records.ts +30 -5
  127. package/src/durable/Recovery.ts +8 -8
  128. package/src/durable/RunJournal.ts +128 -51
  129. package/src/durable/SubmissionLedger.ts +13 -12
  130. package/src/durable/SubmissionLedgerConformance.ts +13 -2
  131. package/src/durable/ThreadInvariants.ts +70 -5
  132. package/src/durable/ToolReconciler.ts +7 -3
  133. package/src/durable/internal/agent-registration.ts +65 -414
  134. package/src/durable/internal/messaging-host.ts +0 -2
  135. package/src/durable/internal/worker-host.ts +34 -96
  136. package/src/engine/RunOptions.ts +13 -3
  137. package/src/engine/internal/agent-runtime.ts +160 -106
  138. package/dist/AgentRuntime-Cdvqw7l_.mjs.map +0 -1
  139. package/dist/DurableAgentRuntime-DbUvZHDZ.mjs.map +0 -1
  140. package/dist/Records-BAutSSnJ.mjs.map +0 -1
  141. package/dist/RunJournal-DRRRZmG7.mjs.map +0 -1
  142. package/dist/agent-registration-CXGDwvJH.mjs +0 -379
  143. package/dist/agent-registration-CXGDwvJH.mjs.map +0 -1
@@ -32,6 +32,8 @@ export const DurableRuntimeFailpointLocation = Schema.Literals([
32
32
  "checkpoint:after-save",
33
33
  "tools:after-prepared-append",
34
34
  "tools:before-prepared-append",
35
+ "tools:before-unavailable-append",
36
+ "tools:after-unavailable-append",
35
37
  "policy:before-reservation-append",
36
38
  "policy:after-reservation-append",
37
39
  "step:after-step-append",
@@ -200,17 +200,17 @@ export class DefinitionDigestInput extends Schema.Class<DefinitionDigestInput>(
200
200
  tools: PersistedJson,
201
201
  }) {}
202
202
 
203
- /** The contracts a retained Run may decode or execute, independent of its deployment. */
203
+ /** Admission-time operation fingerprints. Agent fingerprints remain immutable evidence only. */
204
204
  export class ReplayContract extends Schema.Class<ReplayContract>(
205
205
  "@effect-agent/thread/ReplayContract",
206
206
  )({
207
207
  agent: Digest,
208
- /** Agent semantics and output/completion codecs; the saved input is checked separately. */
208
+ /** Retained for reading earlier admissions; never selects executable code. */
209
209
  agentBehavior: Schema.optionalKey(Digest),
210
210
  tools: Schema.Record(Schema.String, Digest),
211
211
  }) {}
212
212
 
213
- /** Digests identify admission exactly; replay contracts separately authorize continuation. */
213
+ /** Digests identify admission exactly; only an unfinished operation needs replay compatibility. */
214
214
  export class DefinitionDigests extends Schema.Class<DefinitionDigests>(
215
215
  "@effect-agent/thread/DefinitionDigests",
216
216
  )({
@@ -292,6 +292,24 @@ export class ToolCallSettled extends Schema.TaggedClass<ToolCallSettled>(
292
292
  budgetRejected: Schema.optionalKey(Schema.Literal(true)),
293
293
  }) {}
294
294
 
295
+ /** Compact execution evidence committed with a declaration, before any handler can start. */
296
+ export class ToolOperation extends Schema.Class<ToolOperation>(
297
+ "@effect-agent/thread/ToolOperation",
298
+ )({
299
+ toolCallId: ToolCallId,
300
+ toolName: BoundedName,
301
+ executionClass: Schema.Literals(["readonly", "idempotent", "uncertain"]),
302
+ executionKind: ToolExecutionKind,
303
+ replay: Digest,
304
+ }) {}
305
+
306
+ /** A retired call's runtime result. Unavailable readonly work may already have been observed. */
307
+ export class ToolUnavailable extends Schema.TaggedClass<ToolUnavailable>()("ToolUnavailable", {
308
+ toolName: BoundedName,
309
+ execution: Schema.Literals(["not-executed", "unavailable"]),
310
+ message: BoundedText,
311
+ }) {}
312
+
295
313
  /**
296
314
  * One committed model Turn. `messages` carries the Schema-encoded Effect AI Prompt messages this
297
315
  * Turn appended (assistant response plus any tool-call declarations), committed atomically at the
@@ -299,6 +317,8 @@ export class ToolCallSettled extends Schema.TaggedClass<ToolCallSettled>(
299
317
  * `messagesDigest` pins the exact encoded content.
300
318
  */
301
319
  const ModelResponseRecordedFields = Schema.Struct({
320
+ /** Original operation identity/semantics, independent of later Agent and toolbox changes. */
321
+ toolOperations: Schema.optionalKey(Schema.Array(ToolOperation)),
302
322
  /** Explicit pre-execution failures, committed with the original arguments before any approval. */
303
323
  toolParameterRejections: Schema.optionalKey(Schema.Array(ToolParameterRejection)),
304
324
  toolExposure: Schema.optionalKey(Snapshot),
@@ -309,8 +329,9 @@ const ModelResponseRecordedFields = Schema.Struct({
309
329
  messagesDigest: Digest,
310
330
  /**
311
331
  * Number of leading messages that belong only to this Run's evaluated instructions and wake
312
- * input. They remain canonical and are visible while recovering this Run, but later Runs omit
313
- * them from their model-facing history. Records without this field retain their full history.
332
+ * input. They remain canonical. Continuations replace the instruction messages with current
333
+ * instructions while retaining user input; later Runs also retain that original user intent.
334
+ * Records without this field retain their full history.
314
335
  */
315
336
  runScopedPrefixLength: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0))),
316
337
  /**
@@ -366,6 +387,8 @@ export class ToolCallPrepared extends Schema.TaggedClass<ToolCallPrepared>(
366
387
  parametersDigest: Digest,
367
388
  /** Absent legacy evidence grants no delegation replay authority. */
368
389
  executionKind: Schema.optionalKey(ToolExecutionKind),
390
+ executionClass: Schema.optionalKey(ToolOperation.fields.executionClass),
391
+ replay: Schema.optionalKey(Digest),
369
392
  }) {}
370
393
 
371
394
  /** Monotonic reservations charged before programmatic execution or grace finalization. */
@@ -520,6 +543,8 @@ export class RunFailed extends Schema.TaggedClass<RunFailed>("@effect-agent/thre
520
543
  const RunCompletedFields = Schema.Struct({
521
544
  runId: RunId,
522
545
  output: PersistedJson,
546
+ /** Integrity of validated terminal values, independent of future application codecs. */
547
+ resultDigest: Schema.optionalKey(Digest),
523
548
  /** Application disposition captured with an ordinary completion. */
524
549
  runDisposition: Schema.optionalKey(PersistedJson),
525
550
  /** Honest soft-landing marker, present exactly when `exhausted` is present. */
@@ -22,8 +22,8 @@ export class OpenToolCallEvidence extends Schema.Class<OpenToolCallEvidence>(
22
22
 
23
23
  /**
24
24
  * A committed tool-declaring response with ZERO prepared and ZERO settled records for its Turn:
25
- * the provably-safe durability §15 window — no prepared records means no handler ran, so the
26
- * declared batch resumes without model re-invocation and without Unknown.
25
+ * the durability §15 batch-resume window. Mutation preparation has not committed; readonly
26
+ * handlers may already have run. The worker checks each unfinished operation before execution.
27
27
  */
28
28
  export class DeclaredPendingBatchEvidence extends Schema.Class<DeclaredPendingBatchEvidence>(
29
29
  "@effect-agent/thread/DeclaredPendingBatchEvidence",
@@ -217,7 +217,7 @@ export class MarkUnknown extends Schema.TaggedClass<MarkUnknown>(
217
217
  }) {}
218
218
 
219
219
  /** A committed tool-declaring response has zero prepared and zero settled records: a worker
220
- * resumes the declared batch — no model re-invocation, no Unknown (durability §15). S2 also
220
+ * checks unfinished contracts and resumes the declared batch without model re-invocation. S2 also
221
221
  * routes an open delegation call WITHOUT establishment evidence here (spec §13 row 1): the
222
222
  * declared batch re-executes and the idempotent establishment converges on one child. */
223
223
  export class ResumePendingToolBatch extends Schema.TaggedClass<ResumePendingToolBatch>(
@@ -239,14 +239,14 @@ export class ResumeSuspended extends Schema.TaggedClass<ResumeSuspended>(
239
239
  "@effect-agent/thread/ResumeSuspended",
240
240
  )("ResumeSuspended", { submissionId: SubmissionId }) {}
241
241
 
242
- /** Unknown Outcomes lack covering resolutions: the lane stays blocked awaiting the authorized
242
+ /** Unknown Outcomes lack covering resolutions: this Submission stays parked awaiting the authorized
243
243
  * DUR-017 resolution path; the obligation stays visible, nothing replays. */
244
244
  export class AwaitUnknownResolution extends Schema.TaggedClass<AwaitUnknownResolution>(
245
245
  "@effect-agent/thread/AwaitUnknownResolution",
246
246
  )("AwaitUnknownResolution", { submissionId: SubmissionId }) {}
247
247
 
248
- /** Durable resolution intents cover every open call: apply them canonically
249
- * (`ToolCallResolved` + `ToolCallSettled` for recovered results) and reopen the lane. */
248
+ /** Durable resolution intents cover every open call: wake the original Submission when its
249
+ * required retry contract is supported. Canonical outcomes are applied by the next claim. */
250
250
  export class ApplyUnknownResolutions extends Schema.TaggedClass<ApplyUnknownResolutions>(
251
251
  "@effect-agent/thread/ApplyUnknownResolutions",
252
252
  )("ApplyUnknownResolutions", { submissionId: SubmissionId }) {}
@@ -843,8 +843,8 @@ const classifyDelegationAbort = (
843
843
  * ApplyJoinAccounting → CompleteChildAdmission → RepairSubagentStartLink →
844
844
  * AwaitChildAdmissionResolution → ResumePendingToolBatch (idempotent handler re-entry) →
845
845
  * EnsureWaitingForChild → ResumeWaitingParent → ReleaseOrphanChildReservation.
846
- * 10. declared-but-unprepared tool batch → ResumePendingToolBatch — no handler ran (no prepared
847
- * records), so the batch resumes with no model re-invocation and no Unknown (§15).
846
+ * 10. declared-but-unprepared tool batch → ResumePendingToolBatch — the worker checks original
847
+ * operation contracts; missing preparation proves no mutation dispatch, not readonly nonexecution (§15).
848
848
  * 11. `admitted` → a parent-linked Submission whose Thread lacks the canonical lineage
849
849
  * record defers (AwaitParentEstablishment: the parent's idempotent establishment
850
850
  * completes it); otherwise
@@ -372,7 +372,7 @@ export interface RunJournalProjection {
372
372
  readonly policyUsage: RunPolicyUsage;
373
373
  /** Canonical projection for the requested Run; may end at its resumable Tool declaration. */
374
374
  readonly prompt: Prompt.Prompt;
375
- /** Valid prior-Run history excluding the projected Run's records and orphan Tool batches. */
375
+ /** Prior-Run history with model-only unknown results closing incomplete application calls. */
376
376
  readonly historyBefore: Prompt.Prompt;
377
377
  /** Number of canonical Turns already committed for the projected Run. */
378
378
  readonly committedTurns: number;
@@ -471,13 +471,6 @@ const declaredApplicationToolCallIds = (prompt: Prompt.Prompt): ReadonlyArray<st
471
471
  return ids;
472
472
  };
473
473
 
474
- const withoutApplicationToolCallMessages = (prompt: Prompt.Prompt): ReadonlyArray<Prompt.Message> =>
475
- prompt.content.filter(
476
- (message) =>
477
- message.role !== "assistant" ||
478
- !message.content.some((part) => part.type === "tool-call" && !part.providerExecuted),
479
- );
480
-
481
474
  /**
482
475
  * Phase 5 audit tags that are prompt-transparent: they carry durability evidence (preparation,
483
476
  * unknown marking, resolution, Step results, approvals, interruption) but contribute nothing to
@@ -517,9 +510,8 @@ const PROMPT_TRANSPARENT_TAGS: ReadonlySet<string> = new Set([
517
510
 
518
511
  /**
519
512
  * Pure projection: rebuild one Run's resume state from canonical records (DUR-015). Canonical
520
- * order is authoritative; the fold projects each complete `ModelResponseRecorded` Turn and the
521
- * owning Run's resumable incomplete Tool Turn, while excluding incomplete Tool batches from prior
522
- * Runs. It flushes each contiguous group of valid `ToolCallSettled` records into one Tool message,
513
+ * order is authoritative; the fold projects each `ModelResponseRecorded` Turn and the
514
+ * owning Run's resumable incomplete Tool Turn. It flushes each contiguous group of valid `ToolCallSettled` records into one Tool message,
523
515
  * exactly mirroring the per-Turn commit shape produced by `turnCanonicalBatch` (no-tool Turns) and
524
516
  * by the
525
517
  * `turnResponseBatch`/`turnResultsBatch` split (tool-declaring Turns). The Phase 5 audit tags
@@ -528,8 +520,10 @@ const PROMPT_TRANSPARENT_TAGS: ReadonlySet<string> = new Set([
528
520
  *
529
521
  * An incomplete application Tool turn remains visible while projecting its owning Run so active
530
522
  * recovery can resume the declared batch. It is not a valid model-visible Turn boundary for a
531
- * later Run: the orphan assistant Tool declaration and any partial Tool results from that Turn
532
- * are excluded, while preceding instruction/user messages in the response record remain history.
523
+ * later Run until model-only unknown results close its missing calls. Those explanatory results
524
+ * are never canonical settlements or evidence for compaction, recovery or accounting. Real
525
+ * results replace them at the original declaration, including results appended after another Run.
526
+ * Prior user intent and assistant text remain visible; prior system instructions do not.
533
527
  */
534
528
  /** @internal Lightweight canonical boundaries collected without retaining record payloads. */
535
529
  export interface JournalBoundary {
@@ -921,6 +915,18 @@ export const projectRunJournalStream = Effect.fn("RunJournal.projectRunJournalSt
921
915
  }
922
916
  });
923
917
 
918
+ // Slots refer to the retained Prompt itself, not an additional history payload index.
919
+ // Only still-unseen results retain a slot; late results fill the original declaration in place.
920
+ const historicalResults = new Map<
921
+ string,
922
+ {
923
+ readonly allIndex: number;
924
+ readonly beforeIndex: number;
925
+ readonly parts: Array<Prompt.ToolResultPart>;
926
+ readonly partIndex: number;
927
+ }
928
+ >();
929
+
924
930
  let pendingToolOrder = new Map<string, number>();
925
931
 
926
932
  const flushTools = Effect.fn("RunJournal.flushTools")(function* (
@@ -1009,12 +1015,32 @@ export const projectRunJournalStream = Effect.fn("RunJournal.projectRunJournalSt
1009
1015
  }
1010
1016
  emitSummary();
1011
1017
  if (payload._tag === "ToolCallSettled") {
1012
- if (payload.runId !== ownerRunId && incompleteToolCalls.has(envelope.record.recordId)) {
1018
+ if (payload.runId !== ownerRunId) {
1019
+ const slot = historicalResults.get(envelope.record.recordId);
1020
+
1021
+ if (slot === undefined)
1022
+ return yield* journalError("Historical Tool result has no matching declaration");
1023
+ const declared = slot.parts[slot.partIndex];
1024
+
1025
+ if (declared?.id !== payload.toolCallId || declared.name !== payload.toolName)
1026
+ return yield* journalError("Historical Tool result differs from its declaration");
1027
+ slot.parts[slot.partIndex] = Prompt.makePart("tool-result", {
1028
+ id: payload.toolCallId,
1029
+ name: payload.toolName,
1030
+ result: envelope.sequence <= clearBound ? CLEARED_TOOL_RESULT : payload.result,
1031
+ isFailure: payload.isFailure,
1032
+ providerExecuted: false,
1033
+ });
1034
+ const message = Prompt.makeMessage("tool", { content: [...slot.parts] });
1035
+
1036
+ state.all[slot.allIndex] = message;
1037
+ state.before[slot.beforeIndex] = message;
1038
+ historicalResults.delete(envelope.record.recordId);
1013
1039
  onBoundary?.({
1014
1040
  sequence: envelope.sequence,
1015
1041
  tag: payload._tag,
1016
- promptLength: state.all.length + (state.pendingTools.length === 0 ? 0 : 1),
1017
- incomplete: true,
1042
+ promptLength: state.all.length,
1043
+ ...(incompleteToolCalls.has(envelope.record.recordId) ? { incomplete: true } : {}),
1018
1044
  ...(isTerminalPriorRun(payload.runId, ownerRunId, Number.POSITIVE_INFINITY)
1019
1045
  ? { terminalPriorRun: true }
1020
1046
  : {}),
@@ -1066,20 +1092,59 @@ export const projectRunJournalStream = Effect.fn("RunJournal.projectRunJournalSt
1066
1092
 
1067
1093
  yield* accountResponse(envelope, payload, messages);
1068
1094
 
1069
- const modelVisible =
1070
- !forRun && payload.runScopedPrefixLength !== undefined
1071
- ? Prompt.fromMessages(messages.content.slice(payload.runScopedPrefixLength))
1072
- : messages;
1073
-
1074
- const visibleMessages =
1075
- !forRun && incompleteToolTurns.has(envelope.record.recordId)
1076
- ? withoutApplicationToolCallMessages(modelVisible)
1077
- : modelVisible.content;
1095
+ const visibleMessages = forRun
1096
+ ? messages.content
1097
+ : messages.content.filter((message) => message.role !== "system");
1078
1098
 
1079
1099
  for (const message of visibleMessages) {
1080
1100
  state.all.push(message);
1081
1101
  if (!forRun) state.before.push(message);
1082
1102
  }
1103
+ if (!forRun) {
1104
+ const calls = messages.content.flatMap((message) =>
1105
+ message.role === "assistant"
1106
+ ? message.content.filter(
1107
+ (part): part is Prompt.ToolCallPart =>
1108
+ part.type === "tool-call" && !part.providerExecuted,
1109
+ )
1110
+ : [],
1111
+ );
1112
+
1113
+ if (calls.length > 0) {
1114
+ const parts = calls.map((call) =>
1115
+ Prompt.makePart("tool-result", {
1116
+ id: call.id,
1117
+ name: call.name,
1118
+ result: {
1119
+ _tag: "ToolOutcomeUnknown",
1120
+ message:
1121
+ "This earlier operation has no recorded outcome. It may have executed. Do not assume success or retry it; its original operation remains unresolved.",
1122
+ },
1123
+ isFailure: true,
1124
+ providerExecuted: false,
1125
+ }),
1126
+ );
1127
+
1128
+ const allIndex = state.all.length;
1129
+ const beforeIndex = state.before.length;
1130
+ const message = Prompt.makeMessage("tool", { content: parts });
1131
+
1132
+ state.all.push(message);
1133
+ state.before.push(message);
1134
+ for (const [partIndex, call] of calls.entries()) {
1135
+ const callId = yield* Schema.decodeEffect(ToolCallId)(call.id).pipe(
1136
+ Effect.mapError((cause) => journalError("Invalid historical Tool Call ID", cause)),
1137
+ );
1138
+
1139
+ historicalResults.set(toolCallSettledRecordId(payload.runId, payload.turn, callId), {
1140
+ allIndex,
1141
+ beforeIndex,
1142
+ parts,
1143
+ partIndex,
1144
+ });
1145
+ }
1146
+ }
1147
+ }
1083
1148
  state = {
1084
1149
  ...state,
1085
1150
  committedTurns: forRun
@@ -1158,6 +1223,7 @@ const validStagedUsage = (label: string, value: number): Effect.Effect<number, R
1158
1223
  );
1159
1224
 
1160
1225
  export interface TurnCommitInput {
1226
+ readonly toolOperations?: ModelResponseRecorded["toolOperations"] | undefined;
1161
1227
  readonly toolParameterRejections?: ReadonlyArray<ToolParameterRejection> | undefined;
1162
1228
  readonly toolExposure?: Snapshot | undefined;
1163
1229
  readonly toolSelections?: ReadonlyMap<string, Selection> | undefined;
@@ -1300,6 +1366,7 @@ const modelResponseRecord = Effect.fn("RunJournal.modelResponseRecord")(function
1300
1366
  createdAt: input.createdAt,
1301
1367
  deploymentId: input.deploymentId,
1302
1368
  payload: ModelResponseRecorded.make({
1369
+ ...(input.toolOperations === undefined ? {} : { toolOperations: input.toolOperations }),
1303
1370
  ...(input.toolParameterRejections === undefined || input.toolParameterRejections.length === 0
1304
1371
  ? {}
1305
1372
  : { toolParameterRejections: input.toolParameterRejections }),
@@ -1379,29 +1446,39 @@ const toolSettledRecords = Effect.fn("RunJournal.toolSettledRecords")(function*
1379
1446
  return toolRecords;
1380
1447
  });
1381
1448
 
1382
- const runCompletionRecord = (input: TurnCommitInput): RecordEnvelope | undefined =>
1383
- input.runCompletion === undefined
1384
- ? undefined
1385
- : RecordEnvelope.make({
1386
- recordId: runCompletedRecordId(input.runId),
1387
- family: "thread",
1388
- schemaVersion: 1,
1389
- createdAt: input.createdAt,
1390
- deploymentId: input.deploymentId,
1391
- payload: RunCompleted.make({
1392
- runId: input.runId,
1393
- output: input.runCompletion.output,
1394
- ...(input.runCompletion.runDisposition === undefined
1395
- ? {}
1396
- : { runDisposition: input.runCompletion.runDisposition }),
1397
- ...(input.runCompletion.finishReason === undefined
1398
- ? {}
1399
- : { finishReason: input.runCompletion.finishReason }),
1400
- ...(input.runCompletion.exhausted === undefined
1401
- ? {}
1402
- : { exhausted: input.runCompletion.exhausted }),
1403
- }),
1404
- });
1449
+ /** Integrity of the original validated terminal values; never a current-code projection. */
1450
+ export const runCompletionDigest = (
1451
+ completion: Pick<
1452
+ RunCompleted,
1453
+ "runId" | "output" | "runDisposition" | "finishReason" | "exhausted"
1454
+ >,
1455
+ ) =>
1456
+ digestJson({
1457
+ runId: completion.runId,
1458
+ output: completion.output,
1459
+ runDisposition: completion.runDisposition ?? null,
1460
+ finishReason: completion.finishReason ?? null,
1461
+ exhausted: completion.exhausted ?? null,
1462
+ });
1463
+
1464
+ const runCompletionRecord = Effect.fn("RunJournal.runCompletionRecord")(function* (
1465
+ input: TurnCommitInput,
1466
+ ) {
1467
+ if (input.runCompletion === undefined) return undefined;
1468
+ const completion = { runId: input.runId, ...input.runCompletion };
1469
+
1470
+ return RecordEnvelope.make({
1471
+ recordId: runCompletedRecordId(input.runId),
1472
+ family: "thread",
1473
+ schemaVersion: 1,
1474
+ createdAt: input.createdAt,
1475
+ deploymentId: input.deploymentId,
1476
+ payload: RunCompleted.make({
1477
+ ...completion,
1478
+ resultDigest: yield* runCompletionDigest(completion),
1479
+ }),
1480
+ });
1481
+ });
1405
1482
 
1406
1483
  /**
1407
1484
  * Pure per-Turn canonical batch builder (TurnCompleted seam fold, D6/D8): one
@@ -1420,7 +1497,7 @@ export const turnCanonicalBatch = Effect.fn("RunJournal.turnCanonicalBatch")(fun
1420
1497
  const { promptMessages, toolParts } = splitTurnMessages(input.appended);
1421
1498
  const modelResponse = yield* modelResponseRecord(input, promptMessages);
1422
1499
  const toolRecords = yield* toolSettledRecords(input, toolParts);
1423
- const completionRecord = runCompletionRecord(input);
1500
+ const completionRecord = yield* runCompletionRecord(input);
1424
1501
 
1425
1502
  return CanonicalBatch.make({
1426
1503
  batchId: turnBatchId(input.runId, input.turn),
@@ -1465,7 +1542,7 @@ export const turnResponseBatch = Effect.fn("RunJournal.turnResponseBatch")(funct
1465
1542
  */
1466
1543
  export const turnResultsBatch = Effect.fn("RunJournal.turnResultsBatch")(function* (
1467
1544
  input: TurnCommitInput,
1468
- ): Effect.fn.Return<CanonicalBatch, RunJournalError> {
1545
+ ): Effect.fn.Return<CanonicalBatch, RunJournalError | DigestError, Crypto.Crypto> {
1469
1546
  yield* requireCanonicalTurn(input.turn);
1470
1547
  const { toolParts } = splitTurnMessages(input.appended);
1471
1548
  const toolRecords = yield* toolSettledRecords(input, toolParts);
@@ -1477,7 +1554,7 @@ export const turnResultsBatch = Effect.fn("RunJournal.turnResultsBatch")(functio
1477
1554
  if (input.runCompletion !== undefined && toolRecords.length !== 1) {
1478
1555
  return yield* journalError("A terminal Tool completion requires exactly one settled result");
1479
1556
  }
1480
- const completionRecord = runCompletionRecord(input);
1557
+ const completionRecord = yield* runCompletionRecord(input);
1481
1558
 
1482
1559
  return CanonicalBatch.make({
1483
1560
  batchId: turnResultsBatchId(input.runId, input.turn),
@@ -61,12 +61,12 @@ export type OwnershipToken = typeof OwnershipToken.Type;
61
61
  * host Run's outcome.
62
62
  * - `suspended` — durably waiting for explicit approval decisions; the ownership period has
63
63
  * ended and the lane consumes no worker permit while the obligation stays owed.
64
- * - `unknown` — at least one ordinary Tool Call has a durable Unknown Outcome; the lane is
65
- * blocked until an authorized resolution covers every open call or a durable abort intent
66
- * authorizes cleanup and aborted settlement without Tool replay (DUR-012/DUR-017).
64
+ * - `unknown` — at least one Tool Call has a durable Unknown Outcome; this Submission is
65
+ * parked until an authorized resolution covers every open call or a durable abort intent
66
+ * authorizes cleanup without Tool replay. Later eligible input may run (DUR-012/DUR-017).
67
67
  *
68
- * `claim` never grants a `joining`, `joined`, or `suspended` head. An `unknown` head is
69
- * claimable only with a durable abort intent, under the ordinary lease and fencing rules.
68
+ * `claim` never grants or bypasses a `joining`, `joined`, or `suspended` head. It skips unknown
69
+ * work without an abort intent. Any live ownership in the Thread prevents another claim.
70
70
  */
71
71
  export const SubmissionState = Schema.Literals([
72
72
  "admitted",
@@ -676,7 +676,7 @@ export class ApprovalDecisionIntent extends Schema.Class<ApprovalDecisionIntent>
676
676
 
677
677
  /**
678
678
  * Durable Unknown marking for one Submission's open ordinary Tool Calls (DUR-009): state →
679
- * `unknown`, the lane blocks, ownership-free (recovery may mark without a claim). Idempotent.
679
+ * `unknown`, parking that Submission without changing ownership or the Thread epoch. Idempotent.
680
680
  */
681
681
  export class MarkUnknownRequest extends Schema.Class<MarkUnknownRequest>(
682
682
  "@effect-agent/thread/MarkUnknownRequest",
@@ -954,10 +954,10 @@ export type SubmissionLedgerFailure =
954
954
  * Marking an already-ready (or later-state) Submission is a no-op.
955
955
  * - `lookup` — read one Submission by identity or scoped idempotency key; strongly consistent
956
956
  * with prior ledger writes.
957
- * - `claim` — FIFO-head claim rule: claims ONLY the lowest unsettled `queueSequence` of the
958
- * requested Thread lane (DUR-004/DUR-005), regardless of that head's nonterminal state,
959
- * and returns `Option.none` when the lane has no unsettled work or the head's lease is still
960
- * live under another owner. A successful claim atomically bumps the Thread's producer
957
+ * - `claim` — claims the lowest unsettled `queueSequence` after skipping only unknown rows
958
+ * without abort intent. Approval, joining and child-wait barriers remain ordered. Returns
959
+ * `Option.none` when no eligible work exists or ANY lease in the Thread is live, including
960
+ * a lease belonging to the same producer. A successful claim atomically bumps the Thread's producer
961
961
  * epoch — fencing every stale Attempt out of canonical appends (DUR-006) — mints a fresh
962
962
  * `attemptId` and `ownershipToken`, and starts the ownership lease (D5: adapters default to
963
963
  * `DEFAULT_OWNERSHIP_LEASE_DURATION`, configurable). An expired lease makes the head
@@ -1040,8 +1040,9 @@ export type SubmissionLedgerFailure =
1040
1040
  * with `ChildReservationConflict`. "A crash before release leaves budget unavailable until
1041
1041
  * repair, never available twice" (spec §12).
1042
1042
  * - `markUnknown` — idempotent, ownership-free (canonical evidence authorizes it): state →
1043
- * `unknown`, the lane blocks and stops consuming worker permits while the accepted settlement
1044
- * obligation stays visible (DUR-009/DUR-017). Fails with `SettlementConflict` once settled.
1043
+ * `unknown`, parking the Submission while its accepted settlement obligation stays visible.
1044
+ * Existing ownership must release or expire before another claim (DUR-009/DUR-017).
1045
+ * Fails with `SettlementConflict` once settled.
1045
1046
  * - `recordUnknownResolution` — durable, idempotent per `(submissionId, toolCallId)`; a
1046
1047
  * divergent re-resolution fails with `UnknownResolutionConflict`. Transitions
1047
1048
  * `unknown → input-applied` once no open call remains, waking the lane. Fails with
@@ -2877,9 +2877,20 @@ const unknownAbortClaim = conformanceCase(
2877
2877
  }
2878
2878
  yield* advancePastLease(original.leaseExpiresAt);
2879
2879
  if (!abortFirst) {
2880
+ const later = yield* expectSome(
2881
+ "runnable follower behind unknown work",
2882
+ yield* claimLane(threadId, PRODUCER_B),
2883
+ );
2884
+
2880
2885
  yield* ensure(
2881
- Option.isNone(yield* claimLane(threadId, PRODUCER_B)),
2882
- "Unknown work and its follower must wait without an authorized abort",
2886
+ later.submissionId === follower.submissionId,
2887
+ "Unknown work must preserve the queue order of runnable followers",
2888
+ );
2889
+ yield* ledger.releaseOwnership(
2890
+ ReleaseOwnershipRequest.make({
2891
+ submissionId: later.submissionId,
2892
+ ownershipToken: later.ownershipToken,
2893
+ }),
2883
2894
  );
2884
2895
  }
2885
2896
  const intent = yield* ledger.requestAbort(command);
@@ -10,6 +10,7 @@ import {
10
10
  type ProducerId,
11
11
  type RecordEnvelope,
12
12
  } from "./Records.ts";
13
+ import { runIdForSubmission } from "./RunJournal.ts";
13
14
  import {
14
15
  submissionInputRecordId,
15
16
  submissionSettlementRecordId,
@@ -110,7 +111,9 @@ const batchRunsOf = (records: ReadonlyArray<CanonicalRecordEnvelope>): Array<Bat
110
111
  * 4. `digest-chain` — the tail digest recomputes from `EMPTY_TAIL_DIGEST` through every batch
111
112
  * (requires the per-batch producer directory; skipped honestly otherwise).
112
113
  * 5. `fifo-input-order` / `fifo-settlement-order` — canonical input and settlement records
113
- * follow the admitted queue order (DUR-004). Aborted settlements of never-run work (no
114
+ * follow the admitted queue order (DUR-004), except a later input may settle before a Run
115
+ * with canonically unresolved unknown calls when that input became active. Resolving those
116
+ * calls later does not revoke the later Run's ownership. Aborted settlements of never-run work (no
114
117
  * canonical `input:{sid}` record) are exempt from the settlement comparison: P7 §7(c)
115
118
  * settles them immediately without waiting for the head, and DUR-004 bounds execution
116
119
  * order, which never-run work has none of.
@@ -279,14 +282,65 @@ export const verifyThreadInvariants = Effect.fn("Thread.verifyThreadInvariants")
279
282
  // 5b. fifo-settlement-order — P7 §7(c) exemption: an ABORTED settlement for never-run work
280
283
  // (no canonical `input:{sid}` record) settles immediately by design, without waiting to
281
284
  // head the lane, so it is excluded from the FIFO comparison. DUR-004 bounds EXECUTION
282
- // order; the exempted rows provably never executed.
285
+ // order; the exempted rows provably never executed. Unknown work can also be bypassed,
286
+ // but only the inputs that became active while it was parked may settle ahead of it.
283
287
  {
284
288
  const recordIdSet = new Set<string>(recordIds);
285
289
  const abortedNeverRun = new Set<string>();
286
290
 
291
+ const settlementByRun = new Map<string, string>(
292
+ ordered.map((row) => [
293
+ runIdForSubmission(row.submissionId),
294
+ submissionSettlementRecordId(row.submissionId),
295
+ ]),
296
+ );
297
+
298
+ const inputSettlements = new Map(
299
+ ordered.map((row) => [
300
+ submissionInputRecordId(row.submissionId),
301
+ submissionSettlementRecordId(row.submissionId),
302
+ ]),
303
+ );
304
+
305
+ const unknownCalls = new Map<string, Set<string>>();
306
+ const abortsOrTerminals = new Set<string>();
307
+ const bypassedSettlements = new Map<string, Set<string>>();
308
+
287
309
  for (const envelope of records) {
288
310
  const payload = envelope.record.payload;
289
311
 
312
+ if (payload._tag === "ToolCallUnknown" && !abortsOrTerminals.has(payload.runId)) {
313
+ const calls = unknownCalls.get(payload.runId) ?? new Set<string>();
314
+
315
+ calls.add(payload.toolCallId);
316
+ unknownCalls.set(payload.runId, calls);
317
+ } else if (payload._tag === "ToolCallResolved" || payload._tag === "ToolCallSettled") {
318
+ const calls = unknownCalls.get(payload.runId);
319
+
320
+ calls?.delete(payload.toolCallId);
321
+ if (calls?.size === 0) unknownCalls.delete(payload.runId);
322
+ } else if (payload._tag === "AbortRequested" || payload._tag === "SubmissionSettled") {
323
+ const runId = runIdForSubmission(payload.submissionId);
324
+
325
+ abortsOrTerminals.add(runId);
326
+ unknownCalls.delete(runId);
327
+ } else if (payload._tag === "RunCompleted" || payload._tag === "RunFailed") {
328
+ abortsOrTerminals.add(payload.runId);
329
+ unknownCalls.delete(payload.runId);
330
+ } else if (payload._tag === "UserInputRecorded") {
331
+ const settlementId = inputSettlements.get(envelope.record.recordId);
332
+
333
+ if (settlementId !== undefined) {
334
+ const bypassed = new Set<string>();
335
+
336
+ for (const runId of unknownCalls.keys()) {
337
+ const earlier = settlementByRun.get(runId);
338
+
339
+ if (earlier !== undefined) bypassed.add(earlier);
340
+ }
341
+ bypassedSettlements.set(settlementId, bypassed);
342
+ }
343
+ }
290
344
  if (
291
345
  payload._tag === "SubmissionSettled" &&
292
346
  payload.outcome === "aborted" &&
@@ -309,9 +363,20 @@ export const verifyThreadInvariants = Effect.fn("Thread.verifyThreadInvariants")
309
363
  presentSettlements.has(expected),
310
364
  );
311
365
 
312
- const matches =
313
- settlementOrder.length === expectedPresent.length &&
314
- settlementOrder.every((recordId, index) => recordId === expectedPresent[index]);
366
+ const remaining = new Set(expectedPresent);
367
+ let matches = settlementOrder.length === expectedPresent.length;
368
+
369
+ for (const recordId of settlementOrder) {
370
+ for (const earlier of remaining) {
371
+ if (earlier === recordId) break;
372
+ if (!bypassedSettlements.get(recordId)?.has(earlier)) {
373
+ matches = false;
374
+ break;
375
+ }
376
+ }
377
+ if (!matches) break;
378
+ remaining.delete(recordId);
379
+ }
315
380
 
316
381
  checks.push(
317
382
  matches
@@ -20,9 +20,12 @@ export class PreparedToolCallEvidence extends Schema.Class<PreparedToolCallEvide
20
20
  toolName: ToolCallPrepared.fields.toolName,
21
21
  parameters: PersistedJson,
22
22
  parametersDigest: Digest,
23
+ executionKind: ToolCallPrepared.fields.executionKind,
24
+ executionClass: ToolCallPrepared.fields.executionClass,
25
+ replay: ToolCallPrepared.fields.replay,
23
26
  }) {}
24
27
 
25
- /** Proof that the external execution never started: the call may re-execute on resume. */
28
+ /** Proof of nonexecution: resume the original operation or report it unavailable. */
26
29
  export class ReconciliationNeverStarted extends Schema.TaggedClass<ReconciliationNeverStarted>(
27
30
  "@effect-agent/thread/ReconciliationNeverStarted",
28
31
  )("NeverStarted", {}) {}
@@ -38,7 +41,7 @@ export class ReconciliationCompleted extends Schema.TaggedClass<ReconciliationCo
38
41
  isFailure: Schema.Boolean,
39
42
  }) {}
40
43
 
41
- /** The call is safe to repeat under a stable external idempotency contract. */
44
+ /** Only the original operation, identity and parameters may repeat under supported semantics. */
42
45
  export class ReconciliationSafeToRetry extends Schema.TaggedClass<ReconciliationSafeToRetry>(
43
46
  "@effect-agent/thread/ReconciliationSafeToRetry",
44
47
  )("SafeToRetry", {}) {}
@@ -54,7 +57,8 @@ export class ReconciliationUncertain extends Schema.TaggedClass<ReconciliationUn
54
57
  * What a reconciliation policy can prove about one prepared-but-unsettled ordinary Tool Call
55
58
  * (durability §10): execution never started, execution completed with a recoverable result,
56
59
  * execution is safe to repeat, or nothing — in which case the Run enters Unknown. The engine
57
- * never manufactures an error result and continues.
60
+ * never invents an external result. Proven nonexecution can close a retired operation with
61
+ * an explicit not-executed result; uncertainty stays parked.
58
62
  */
59
63
  export const ReconciliationDecision = Schema.Union([
60
64
  ReconciliationNeverStarted,