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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/{Admin-Qa3I1oA5.d.mts → Admin-DTDihBra.d.mts} +13 -7
  2. package/dist/{DurableAgentRuntime-CPlw5_7e.mjs → DurableAgentRuntime-BJbuIqQq.mjs} +84 -37
  3. package/dist/DurableAgentRuntime-BJbuIqQq.mjs.map +1 -0
  4. package/dist/{EventSource-BTu68yDW.d.mts → EventSource-Cc58uEso.d.mts} +2 -2
  5. package/dist/{MessageDelivery-DjAZJ4Fd.d.mts → MessageDelivery-ClHg7c0O.d.mts} +42 -42
  6. package/dist/{PreparedInputAdmission-wgY4JMZY.d.mts → PreparedInputAdmission-CjcX1OP0.d.mts} +3 -3
  7. package/dist/{RunContinuation-BiyKJK32.mjs → RunContinuation-CWHuciup.mjs} +3 -3
  8. package/dist/RunContinuation-CWHuciup.mjs.map +1 -0
  9. package/dist/{RunContinuation-3jB5pULC.d.mts → RunContinuation-DSnUTSSb.d.mts} +4 -4
  10. package/dist/{RunJournal-CPMKcfd0.mjs → RunJournal-DGblbFas.mjs} +5 -5
  11. package/dist/{RunJournal-CPMKcfd0.mjs.map → RunJournal-DGblbFas.mjs.map} +1 -1
  12. package/dist/{RunStorage-CzutHJBE.mjs → RunStorage-BF2YgWkg.mjs} +43 -33
  13. package/dist/RunStorage-BF2YgWkg.mjs.map +1 -0
  14. package/dist/{Subscription-DmIHlOJK.d.mts → Subscription-DuC_PcGx.d.mts} +2 -2
  15. package/dist/{SubscriptionInput-DgcQhvbe.d.mts → SubscriptionInput-B1IeAEDi.d.mts} +2 -2
  16. package/dist/{ThreadImport-DP-vOk2F.d.mts → ThreadImport-D_Biid6w.d.mts} +42 -42
  17. package/dist/{ThreadImport-CjlG57vH.mjs → ThreadImport-ZgWxf7bB.mjs} +5 -5
  18. package/dist/{ThreadImport-CjlG57vH.mjs.map → ThreadImport-ZgWxf7bB.mjs.map} +1 -1
  19. package/dist/{ThreadStore-EuUilthA.mjs → ThreadStore-BC4V-mY-.mjs} +39 -18
  20. package/dist/ThreadStore-BC4V-mY-.mjs.map +1 -0
  21. package/dist/{ThreadTransfer-DB_zsrtJ.mjs → ThreadTransfer-S7FnTRHt.mjs} +2 -2
  22. package/dist/{ThreadTransfer-DB_zsrtJ.mjs.map → ThreadTransfer-S7FnTRHt.mjs.map} +1 -1
  23. package/dist/{WorkerAdmission-eBeXt_mu.mjs → WorkerAdmission-C47Vy_cK.mjs} +3 -3
  24. package/dist/{WorkerAdmission-eBeXt_mu.mjs.map → WorkerAdmission-C47Vy_cK.mjs.map} +1 -1
  25. package/dist/durable/Admin.d.mts +1 -1
  26. package/dist/durable/AgentRegistration.d.mts +1 -1
  27. package/dist/durable/Certification.d.mts +1 -1
  28. package/dist/durable/CommittedActivity.d.mts +1 -1
  29. package/dist/durable/CommittedActivity.mjs +1 -1
  30. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  31. package/dist/durable/DurableAgentRuntime.mjs +2 -2
  32. package/dist/durable/EventSource.d.mts +1 -1
  33. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  34. package/dist/durable/LifecyclePublication.d.mts +1 -1
  35. package/dist/durable/LifecyclePublication.mjs +1 -1
  36. package/dist/durable/MessageDelivery.d.mts +1 -1
  37. package/dist/durable/MessageDeliveryStoreConformance.d.mts +2 -2
  38. package/dist/durable/PersistentHistory.d.mts +1 -1
  39. package/dist/durable/PersistentHistory.mjs +3 -3
  40. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  41. package/dist/durable/Recovery.d.mts +1 -1
  42. package/dist/durable/RunContinuation.d.mts +2 -2
  43. package/dist/durable/RunContinuation.mjs +2 -2
  44. package/dist/durable/RunJournal.d.mts +1 -1
  45. package/dist/durable/RunJournal.mjs +1 -1
  46. package/dist/durable/RunStorage.d.mts +1 -1
  47. package/dist/durable/RunStorage.mjs +1 -1
  48. package/dist/durable/Scheduling.d.mts +1 -1
  49. package/dist/durable/SettlementPublisher.d.mts +1 -1
  50. package/dist/durable/SettlementPublisher.mjs +2 -2
  51. package/dist/durable/SubmissionLedger.d.mts +1 -1
  52. package/dist/durable/SubmissionLedger.mjs.map +1 -1
  53. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  54. package/dist/durable/SubmissionLedgerConformance.mjs +3 -3
  55. package/dist/durable/SubmissionStatus.d.mts +1 -1
  56. package/dist/durable/Subscription.d.mts +1 -1
  57. package/dist/durable/SubscriptionInput.d.mts +1 -1
  58. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  59. package/dist/durable/SubscriptionTools.d.mts +2 -2
  60. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  61. package/dist/durable/Subscriptions.d.mts +5 -5
  62. package/dist/durable/ThreadArchiveRange.d.mts +1 -1
  63. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  64. package/dist/durable/ThreadContextHistory.mjs +1 -1
  65. package/dist/durable/ThreadImport.d.mts +1 -1
  66. package/dist/durable/ThreadImport.mjs +1 -1
  67. package/dist/durable/ThreadInvariants.d.mts +3 -3
  68. package/dist/durable/ThreadInvariants.mjs +3 -3
  69. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  70. package/dist/durable/ThreadReader.d.mts +1 -1
  71. package/dist/durable/ThreadReader.mjs +1 -1
  72. package/dist/durable/ThreadStore.d.mts +1 -1
  73. package/dist/durable/ThreadStore.mjs +1 -1
  74. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  75. package/dist/durable/ThreadStoreConformance.mjs +3 -3
  76. package/dist/durable/ThreadTransfer.d.mts +1 -1
  77. package/dist/durable/ThreadTransfer.mjs +1 -1
  78. package/dist/durable/ThreadWork.d.mts +1 -1
  79. package/dist/durable/ThreadWork.mjs +3 -3
  80. package/dist/durable/WorkerAdmission.d.mts +68 -68
  81. package/dist/durable/WorkerAdmission.mjs +1 -1
  82. package/dist/durable/WorkerHost.d.mts +1 -1
  83. package/dist/index.d.mts +8 -8
  84. package/dist/index.mjs +7 -7
  85. package/dist/{run-context-reader-DVhJZ1Qe.d.mts → run-context-reader-DvTEq2RC.d.mts} +2 -2
  86. package/package.json +1 -1
  87. package/src/durable/DurableAgentRuntime.ts +169 -55
  88. package/src/durable/RunContinuation.ts +2 -2
  89. package/src/durable/RunJournal.ts +9 -5
  90. package/src/durable/RunStorage.ts +83 -61
  91. package/src/durable/SubmissionLedger.ts +8 -0
  92. package/src/durable/ThreadStore.ts +39 -19
  93. package/src/durable/internal/initial-context.ts +17 -2
  94. package/src/durable/internal/prompt-history-cache.ts +13 -8
  95. package/dist/DurableAgentRuntime-CPlw5_7e.mjs.map +0 -1
  96. package/dist/RunContinuation-BiyKJK32.mjs.map +0 -1
  97. package/dist/RunStorage-CzutHJBE.mjs.map +0 -1
  98. package/dist/ThreadStore-EuUilthA.mjs.map +0 -1
@@ -158,7 +158,7 @@ import {
158
158
  } from "./internal/agent-registration.ts";
159
159
  import { makeAgentUpdateRuntime } from "./internal/agent-updates.ts";
160
160
  import { inspectForeignDiagnostic, safeUnknownString } from "./internal/foreign-diagnostic.ts";
161
- import { initialContext } from "./internal/initial-context.ts";
161
+ import { initialContext, type InitialContextInput } from "./internal/initial-context.ts";
162
162
  import {
163
163
  makeJournalMetadata,
164
164
  type JournalMetadata,
@@ -403,6 +403,9 @@ import {
403
403
  getRunInput,
404
404
  ThreadReader,
405
405
  MAX_THREAD_EXPORT_PAGE_BYTES,
406
+ PreparedAppend,
407
+ type CommitBatch,
408
+ type ExecutionUpdate,
406
409
  } from "./ThreadStore.ts";
407
410
  import {
408
411
  type WorkIndexRebuildRequest,
@@ -594,6 +597,11 @@ export { threadCreatedBatchId, threadCreatedRecordId } from "./internal/thread-i
594
597
  export const recoveryRepairBatchId = (submissionId: SubmissionId, decisionTag: string): BatchId =>
595
598
  decodeBatchId(`repair:${submissionId}:${decisionTag}`);
596
599
 
600
+ const isOriginalInputRecord = (
601
+ record: RecordEnvelope,
602
+ ): record is RecordEnvelope & { readonly payload: UserInputRecorded } =>
603
+ record.payload._tag === "UserInputRecorded";
604
+
597
605
  /** Deterministic record identity of one executed recovery decision's `RepairAnnotated` record. */
598
606
  export const recoveryRepairRecordId = (submissionId: SubmissionId, decisionTag: string): RecordId =>
599
607
  decodeRecordId(`repair:${submissionId}:${decisionTag}`);
@@ -1364,7 +1372,7 @@ interface OpenCallReview {
1364
1372
 
1365
1373
  interface InitialJournalSource {
1366
1374
  records: ReadonlyArray<JournalRecordEnvelope> | undefined;
1367
- readonly original: CanonicalRecordEnvelope;
1375
+ readonly original: InitialContextInput;
1368
1376
  readonly suffix: ReadonlyArray<CanonicalRecordEnvelope>;
1369
1377
  cacheable: boolean;
1370
1378
  readonly prefix?: CachedPromptHistory;
@@ -1472,7 +1480,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
1472
1480
 
1473
1481
  // Rebuild the pinned admission and captured suffix using this runtime's host dependencies.
1474
1482
  const readInitialJournal = (
1475
- original: CanonicalRecordEnvelope,
1483
+ original: InitialContextInput,
1476
1484
  suffix: ReadonlyArray<CanonicalRecordEnvelope>,
1477
1485
  ) =>
1478
1486
  initialContext(original).pipe(
@@ -2617,7 +2625,10 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
2617
2625
  Effect.map(({ owned }) => owned),
2618
2626
  );
2619
2627
 
2620
- const withProgressSession = Effect.fnUntraced(function* (session: RunStorageSession) {
2628
+ const withProgressSession = Effect.fnUntraced(function* (
2629
+ session: RunStorageSession,
2630
+ originalInput?: () => InitialContextInput | undefined,
2631
+ ) {
2621
2632
  const { owned, progress } = yield* withProgress(session);
2622
2633
 
2623
2634
  activeProgress.set(session.threadId, { writer: progress });
@@ -2628,9 +2639,44 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
2628
2639
  }),
2629
2640
  );
2630
2641
 
2642
+ const inputWriter =
2643
+ session.appendInput === undefined
2644
+ ? undefined
2645
+ : {
2646
+ ...session,
2647
+ append: session.appendInput,
2648
+ };
2649
+
2631
2650
  const wrapped: RunStorageSession = {
2632
2651
  ...session,
2633
2652
  append: owned.append,
2653
+ ...(inputWriter === undefined
2654
+ ? {}
2655
+ : {
2656
+ appendInput: (batch: CommitBatch) =>
2657
+ storageOwner.withProgress(
2658
+ session.threadId,
2659
+ storageOwner.withMutation(
2660
+ Effect.gen(function* () {
2661
+ yield* session.checkActive;
2662
+ // The captured empty canonical prefix is enforced again by the append tail and
2663
+ // checkpoint revision-zero CAS. It is not a cache entry across preparation IO.
2664
+ const original = originalInput?.();
2665
+ const commit = progress.commit(batch);
2666
+
2667
+ return yield* (
2668
+ original === undefined
2669
+ ? commit
2670
+ : progress.withVerifiedEmptyRun(
2671
+ runIdForSubmission(session.claim.submissionId),
2672
+ CanonicalSequence.make(original.sequence - 1),
2673
+ commit,
2674
+ )
2675
+ ).pipe(Effect.provideService(CurrentRunWriter, inputWriter));
2676
+ }),
2677
+ ),
2678
+ ),
2679
+ }),
2634
2680
  publishSettlement: (batch) =>
2635
2681
  progress.publish(batch).pipe(Effect.provideService(CurrentRunSettlement, session)),
2636
2682
  };
@@ -3167,6 +3213,20 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3167
3213
  );
3168
3214
  });
3169
3215
 
3216
+ const prepareOriginalInput = (submission: SubmissionSnapshot) =>
3217
+ makeEnvelope(
3218
+ submissionInputRecordId(submission.submissionId),
3219
+ UserInputRecorded.make({
3220
+ submissionId: submission.submissionId,
3221
+ kind: "user",
3222
+ runId: runIdForSubmission(submission.submissionId),
3223
+ input: submission.inputPayload,
3224
+ ...(submission.messageAdmission === undefined
3225
+ ? {}
3226
+ : { messageAdmission: submission.messageAdmission }),
3227
+ }),
3228
+ );
3229
+
3170
3230
  /**
3171
3231
  * Plan step 2: append the deterministic `UserInputRecorded` record (batch idempotency makes it
3172
3232
  * exactly-once canonical, DUR-007) and mark it applied. When canonical history already carries
@@ -3192,18 +3252,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3192
3252
  return;
3193
3253
  }
3194
3254
 
3195
- const envelope = yield* makeEnvelope(
3196
- recordId,
3197
- UserInputRecorded.make({
3198
- submissionId,
3199
- kind: "user",
3200
- runId: runIdForSubmission(submissionId),
3201
- input: submission.inputPayload,
3202
- ...(submission.messageAdmission === undefined
3203
- ? {}
3204
- : { messageAdmission: submission.messageAdmission }),
3205
- }),
3206
- );
3255
+ const envelope = yield* prepareOriginalInput(submission);
3207
3256
 
3208
3257
  const result = yield* appendBatch(
3209
3258
  ctx,
@@ -3254,8 +3303,13 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3254
3303
  maxDurationMillis: number,
3255
3304
  records: ReadonlyArray<CanonicalRecordEnvelope>,
3256
3305
  operational: ReadonlyArray<RecordEnvelope>,
3306
+ input?: RecordEnvelope,
3257
3307
  ) {
3258
3308
  const writer = yield* CurrentRunWriter;
3309
+ const append = input === undefined ? writer.append : writer.appendInput;
3310
+
3311
+ if (append === undefined)
3312
+ return yield* RunJournalError.make({ message: "Fresh startup requires an input publisher" });
3259
3313
  const runId = runIdForSubmission(submission.submissionId);
3260
3314
  const recordId = runStartedRecordId(runId);
3261
3315
  const existing = yield* readRunStart(submission.threadId, runId, operational);
@@ -3304,13 +3358,21 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
3304
3358
  }
3305
3359
  yield* hit("run:before-start-append");
3306
3360
  yield* appendBatch(
3307
- writer,
3361
+ { threadId: writer.threadId, append },
3308
3362
  CanonicalBatch.make({
3309
- batchId: runStartedBatchId(runId),
3363
+ batchId:
3364
+ input === undefined
3365
+ ? runStartedBatchId(runId)
3366
+ : submissionInputBatchId(submission.submissionId),
3310
3367
  producerId: config.producerId,
3311
- records: [start, ...(context === undefined ? [] : [context])],
3368
+ records: [
3369
+ input ?? start,
3370
+ ...(input === undefined ? [] : [start]),
3371
+ ...(context === undefined ? [] : [context]),
3372
+ ],
3312
3373
  }),
3313
3374
  );
3375
+ if (input !== undefined) yield* hit("input:after-canonical-append");
3314
3376
  committed = true;
3315
3377
  yield* hit("run:after-start-append");
3316
3378
  });
@@ -4856,12 +4918,14 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
4856
4918
  // Unrelated Thread traffic does not invalidate an already validated owner projection.
4857
4919
  const projectionThrough = Math.max(progressThrough ?? 0, records.at(-1)?.sequence ?? 0);
4858
4920
 
4859
- const originalInput = records.find(
4860
- ({ record: { payload } }) =>
4861
- payload._tag === "UserInputRecorded" &&
4862
- payload.kind === "user" &&
4863
- payload.submissionId === submissionId,
4864
- );
4921
+ const originalInput =
4922
+ initialJournal?.original ??
4923
+ records.find(
4924
+ ({ record: { payload } }) =>
4925
+ payload._tag === "UserInputRecorded" &&
4926
+ payload.kind === "user" &&
4927
+ payload.submissionId === submissionId,
4928
+ );
4865
4929
 
4866
4930
  if (originalInput === undefined)
4867
4931
  return yield* RunJournalError.make({ message: "Run has no original context boundary" });
@@ -5447,18 +5511,6 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5447
5511
  usageSummary: yield* currentUsageSummary(),
5448
5512
  };
5449
5513
  }
5450
- // Summaries cover only prior Runs. Pruning and rollover also map fully settled
5451
- // current-Run records and preserves the canonical instruction/input block during replay.
5452
- let ownerFirstSequence: CanonicalSequence | undefined;
5453
-
5454
- for (const envelope of records) {
5455
- const payload = envelope.record.payload;
5456
-
5457
- if ("runId" in payload && payload.runId === runId) {
5458
- ownerFirstSequence = envelope.sequence;
5459
- break;
5460
- }
5461
- }
5462
5514
 
5463
5515
  /** Only canonical joined inputs can require restoration from an earlier Attempt. */
5464
5516
  let joinBacklog: ReadonlyArray<JoinSnapshot> | undefined =
@@ -5839,6 +5891,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
5839
5891
  const start = yield* CurrentRunStart;
5840
5892
 
5841
5893
  yield* start.commit(yield* makeEnvelope(recordId, payload));
5894
+ knownIds.add(originalInput.record.recordId);
5842
5895
  knownIds.add(runStartedRecordId(runId));
5843
5896
  knownIds.add(recordId);
5844
5897
  historyEvidence.clear();
@@ -6400,13 +6453,10 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
6400
6453
  );
6401
6454
  }
6402
6455
 
6403
- // Summaries use the initial prior-Run boundaries; pruning and rollover also admit
6404
- // complete current-Run boundaries from the fresh canonical source above.
6456
+ // The validated original input bounds summaries even when startup deferred its append.
6457
+ // Pruning and rollover also admit complete current-Run canonical boundaries.
6405
6458
  const coverable = sourceBoundaries.filter(
6406
- (boundary) =>
6407
- commit.kind !== "summarize" ||
6408
- ownerFirstSequence === undefined ||
6409
- boundary.sequence < ownerFirstSequence,
6459
+ (boundary) => commit.kind !== "summarize" || boundary.sequence < originalInput.sequence,
6410
6460
  );
6411
6461
 
6412
6462
  if (coverable.length === 0) {
@@ -8164,6 +8214,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8164
8214
  // before suspension; only acknowledged ranges enter this Attempt's disposable suffix.
8165
8215
  let committedPromptSuffix: CommittedPromptSuffix | undefined;
8166
8216
  const storageSession = session;
8217
+ let pendingOriginal: InitialContextInput | undefined;
8167
8218
 
8168
8219
  yield* Effect.addFinalizer(() =>
8169
8220
  Effect.sync(() => {
@@ -8174,6 +8225,27 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8174
8225
 
8175
8226
  const promptSession: RunStorageSession = {
8176
8227
  ...storageSession,
8228
+ ...(storageSession.appendInput === undefined
8229
+ ? {}
8230
+ : {
8231
+ appendInput: (batch: CommitBatch, executions?: ReadonlyArray<ExecutionUpdate>) =>
8232
+ storageSession.appendInput!(batch, executions).pipe(
8233
+ Effect.tap((result) =>
8234
+ Effect.sync(() => {
8235
+ if (
8236
+ pendingOriginal !== undefined &&
8237
+ !result.replayed &&
8238
+ result.firstSequence === pendingOriginal.sequence
8239
+ )
8240
+ committedPromptSuffix = makeCommittedPromptSuffix({
8241
+ threadId: storageSession.threadId,
8242
+ sequence: result.firstSequence,
8243
+ record: batch.records[0]!,
8244
+ });
8245
+ }),
8246
+ ),
8247
+ ),
8248
+ }),
8177
8249
  append: (batch, executions) => {
8178
8250
  const suffix = committedPromptSuffix;
8179
8251
  const append = storageSession.append(batch, executions);
@@ -8209,7 +8281,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8209
8281
  },
8210
8282
  };
8211
8283
 
8212
- session = yield* withProgressSession(promptSession);
8284
+ session = yield* withProgressSession(promptSession, () => pendingOriginal);
8213
8285
  const { claim, threadId } = session;
8214
8286
  const submissionId = claim.submissionId;
8215
8287
 
@@ -8254,6 +8326,41 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8254
8326
  view.records.some(({ record }) => record.payload._tag !== "ThreadCreated")
8255
8327
  )
8256
8328
  return undefined;
8329
+ if (session.appendInput !== undefined) {
8330
+ const captured = yield* PreparedAppend.capture(
8331
+ AppendRequest.make({
8332
+ threadId,
8333
+ expectedTailSequence: before,
8334
+ batch: CanonicalBatch.make({
8335
+ batchId: submissionInputBatchId(submissionId),
8336
+ producerId: config.producerId,
8337
+ records: [yield* prepareOriginalInput(submission)],
8338
+ }),
8339
+ }),
8340
+ );
8341
+
8342
+ const record = captured.records[0]!.canonical;
8343
+
8344
+ if (!isOriginalInputRecord(record))
8345
+ return yield* RunJournalError.make({
8346
+ message: "Captured startup has no original input",
8347
+ });
8348
+ pendingOriginal = {
8349
+ threadId,
8350
+ sequence: CanonicalSequence.make(before + 1),
8351
+ record,
8352
+ };
8353
+
8354
+ const start = yield* makeRunStart(
8355
+ submission,
8356
+ Duration.toMillis(registeredAgent.definition.policy.maxDuration),
8357
+ view.records,
8358
+ view.operational,
8359
+ record,
8360
+ ).pipe(Effect.provideService(CurrentRunWriter, session));
8361
+
8362
+ return { snapshot, through: before, view, start, input: pendingOriginal };
8363
+ }
8257
8364
  const progress = progressWriters.get(session);
8258
8365
 
8259
8366
  const applyInput = applyCanonicalInput(
@@ -8303,6 +8410,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8303
8410
  operational: accepted.operational,
8304
8411
  },
8305
8412
  start,
8413
+ input: undefined,
8306
8414
  };
8307
8415
  }),
8308
8416
  )
@@ -8737,12 +8845,14 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8737
8845
  let initialJournal: InitialJournalSource | undefined;
8738
8846
 
8739
8847
  if (view.context === undefined) {
8740
- const original = currentRecords.find(
8741
- ({ record }) =>
8742
- record.payload._tag === "UserInputRecorded" &&
8743
- record.payload.kind === "user" &&
8744
- record.payload.submissionId === submissionId,
8745
- );
8848
+ const original =
8849
+ initialization?.input ??
8850
+ currentRecords.find(
8851
+ ({ record }) =>
8852
+ record.payload._tag === "UserInputRecorded" &&
8853
+ record.payload.kind === "user" &&
8854
+ record.payload.submissionId === submissionId,
8855
+ );
8746
8856
 
8747
8857
  if (original === undefined)
8748
8858
  return yield* RunJournalError.make({
@@ -8752,7 +8862,10 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8752
8862
  const suffix = currentRecords.filter((entry) => entry.sequence > original.sequence);
8753
8863
 
8754
8864
  const fresh =
8755
- !lineage.inputWasRecorded && tail.sequence === original.sequence && suffix.length === 0;
8865
+ initialization?.input !== undefined ||
8866
+ (!lineage.inputWasRecorded &&
8867
+ tail.sequence === original.sequence &&
8868
+ suffix.length === 0);
8756
8869
 
8757
8870
  // Every replay stays bounded at the ORIGINAL admission, never the later Thread tail.
8758
8871
  const source: InitialJournalSource = yield* Effect.gen(function* () {
@@ -8795,9 +8908,10 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8795
8908
  }
8796
8909
 
8797
8910
  committedPromptSuffix?.discard();
8798
- committedPromptSuffix = initialJournal?.cacheable
8799
- ? makeCommittedPromptSuffix(initialJournal.original)
8800
- : undefined;
8911
+ committedPromptSuffix =
8912
+ initialJournal?.cacheable && initialization?.input === undefined
8913
+ ? makeCommittedPromptSuffix(initialJournal.original)
8914
+ : undefined;
8801
8915
 
8802
8916
  const outcome = yield* runModel(
8803
8917
  agent,
@@ -8807,7 +8921,7 @@ const make = Effect.fnUntraced(function* (bindings: ReadonlyArray<ResolvedBindin
8807
8921
  currentRecords,
8808
8922
  canonical,
8809
8923
  initialJournal,
8810
- tail.sequence,
8924
+ initialization?.input?.sequence ?? tail.sequence,
8811
8925
  view.progressThrough,
8812
8926
  view.progress,
8813
8927
  view.context,
@@ -52,7 +52,7 @@ import {
52
52
  subagentRequestedRecordId,
53
53
  subagentStartedRecordId,
54
54
  } from "./RunJournal.ts";
55
- import type { RunStorageSession, RunWriter } from "./RunStorage.ts";
55
+ import type { RunStorageSession } from "./RunStorage.ts";
56
56
  import {
57
57
  submissionAbortRecordId,
58
58
  submissionInputRecordId,
@@ -74,7 +74,7 @@ export { RunContinuation, RunContextRecorded, EvidenceReference } from "./Record
74
74
  /** Writer and progress sequencer selected at the local owner's resource boundary. */
75
75
  export class CurrentRunWriter extends Context.Service<
76
76
  CurrentRunWriter,
77
- Pick<RunWriter, "threadId" | "tail" | "append" | "withProgress">
77
+ Pick<RunStorageSession, "threadId" | "tail" | "append" | "appendInput" | "withProgress">
78
78
  >()("@effect-agent/thread/CurrentRunWriter") {}
79
79
 
80
80
  /** Settlement publication keeps its original atomic authority and claim-scoped implementation. */
@@ -1736,12 +1736,12 @@ const modelResponseRecord = Effect.fnUntraced(function* (
1736
1736
  }
1737
1737
 
1738
1738
  const encodedMessages = yield* encodePrompt(Prompt.fromMessages([...promptMessages])).pipe(
1739
- Effect.mapError((cause) => journalError("Turn Prompt messages failed to encode", cause)),
1739
+ Effect.mapErrorEager((cause) => journalError("Turn Prompt messages failed to encode", cause)),
1740
1740
  );
1741
1741
 
1742
1742
  const messages = yield* decodePersistedJson(encodedMessages).pipe(
1743
- Effect.map(copyJson),
1744
- Effect.mapError((cause) =>
1743
+ Effect.mapEager(copyJson),
1744
+ Effect.mapErrorEager((cause) =>
1745
1745
  journalError("Turn Prompt messages exceed canonical persistence bounds", cause),
1746
1746
  ),
1747
1747
  );
@@ -1783,7 +1783,9 @@ const modelResponseRecord = Effect.fnUntraced(function* (
1783
1783
  }),
1784
1784
  }),
1785
1785
  }).pipe(
1786
- Effect.mapError((cause) => journalError("Invalid model response operation evidence", cause)),
1786
+ Effect.mapErrorEager((cause) =>
1787
+ journalError("Invalid model response operation evidence", cause),
1788
+ ),
1787
1789
  );
1788
1790
 
1789
1791
  if (payload.modelUsage !== undefined) {
@@ -1791,7 +1793,9 @@ const modelResponseRecord = Effect.fnUntraced(function* (
1791
1793
  return yield* journalError("Turn model usage must contain at least one completed call");
1792
1794
 
1793
1795
  const summary = yield* continuationUsage(payload.modelUsage).pipe(
1794
- Effect.mapError((cause) => journalError("Turn model usage exceeds accounting bounds", cause)),
1796
+ Effect.mapErrorEager((cause) =>
1797
+ journalError("Turn model usage exceeds accounting bounds", cause),
1798
+ ),
1795
1799
  );
1796
1800
 
1797
1801
  if (
@@ -113,6 +113,8 @@ export interface RunWriter {
113
113
 
114
114
  export interface RunStorageSession extends RunWriter, RunOwnership {
115
115
  readonly claim: Claim;
116
+ /** Atomic original input/context plus the ledger marker, when the adapter supports it. */
117
+ readonly appendInput?: RunWriter["append"];
116
118
  /** Library-only initialization, before application/model IO. Hold progress before mutation
117
119
  * so the captured controls and this owner's acknowledged input remain one read view. */
118
120
  readonly withInitialization?: <A, E, R>(
@@ -176,69 +178,75 @@ const makeWriter = Effect.fnUntraced(function* (threadId: ThreadId) {
176
178
  }),
177
179
  );
178
180
 
179
- const append: RunWriter["append"] = Effect.fnUntraced(function* (batch, executions) {
180
- const captured = yield* PreparedAppend.capture(
181
- AppendRequest.make({
182
- threadId,
183
- expectedTailSequence: tail.sequence,
184
- batch,
185
- ...(executions === undefined ? {} : { executions }),
186
- }),
187
- ).pipe(
188
- Effect.mapError((cause) =>
189
- ThreadStoreError.make({
190
- operation: "capture append",
191
- message: cause.message,
192
- cause,
181
+ const makeAppend = Effect.gen(function* () {
182
+ const publisher = yield* ThreadStore;
183
+
184
+ const append: RunWriter["append"] = Effect.fnUntraced(function* (batch, executions) {
185
+ const captured = yield* PreparedAppend.capture(
186
+ AppendRequest.make({
187
+ threadId,
188
+ expectedTailSequence: tail.sequence,
189
+ batch,
190
+ ...(executions === undefined ? {} : { executions }),
193
191
  }),
194
- ),
195
- );
192
+ ).pipe(
193
+ Effect.mapError((cause) =>
194
+ ThreadStoreError.make({
195
+ operation: "capture append",
196
+ message: cause.message,
197
+ cause,
198
+ }),
199
+ ),
200
+ );
196
201
 
197
- return yield* owner.withProgress(
198
- threadId,
199
- owner.withMutation(
200
- Effect.gen(function* () {
201
- yield* checkActive;
202
- for (let retries = 0; ; retries++) {
203
- const result = yield* store
204
- .append(
205
- captured.expectedTailSequence === tail.sequence
206
- ? captured
207
- : AppendRequest.make({
208
- ...captured,
209
- expectedTailSequence: tail.sequence,
210
- }),
211
- )
212
- .pipe(
213
- Effect.catchTag("AppendConflict", (conflict) => {
214
- if (
215
- conflict.reason !== "tail" ||
216
- conflict.actualTailSequence === undefined ||
217
- retries >= 8
218
- )
219
- return Effect.fail(conflict);
220
- tail = { sequence: conflict.actualTailSequence };
221
- if (captured.executions !== undefined) return Effect.fail(conflict);
222
-
223
- return Effect.succeed(undefined);
224
- }),
225
- );
226
-
227
- if (result === undefined) continue;
228
- // A historical replay returns its original coordinates, not today's canonical tail.
229
- if (result.replayed) yield* refresh;
230
- else tail = { sequence: result.lastSequence };
231
-
232
- return result;
233
- }
234
- }).pipe(
235
- // Revoke uncertain writes before releasing the writer's mutation gate.
236
- Effect.onError((cause) =>
237
- isDefiniteWriteFailure(cause) ? Effect.void : ownership.release,
202
+ return yield* owner.withProgress(
203
+ threadId,
204
+ owner.withMutation(
205
+ Effect.gen(function* () {
206
+ yield* checkActive;
207
+ for (let retries = 0; ; retries++) {
208
+ const result = yield* publisher
209
+ .append(
210
+ captured.expectedTailSequence === tail.sequence
211
+ ? captured
212
+ : AppendRequest.make({
213
+ ...captured,
214
+ expectedTailSequence: tail.sequence,
215
+ }),
216
+ )
217
+ .pipe(
218
+ Effect.catchTag("AppendConflict", (conflict) => {
219
+ if (
220
+ conflict.reason !== "tail" ||
221
+ conflict.actualTailSequence === undefined ||
222
+ retries >= 8
223
+ )
224
+ return Effect.fail(conflict);
225
+ tail = { sequence: conflict.actualTailSequence };
226
+ if (captured.executions !== undefined) return Effect.fail(conflict);
227
+
228
+ return Effect.succeed(undefined);
229
+ }),
230
+ );
231
+
232
+ if (result === undefined) continue;
233
+ // A historical replay returns its original coordinates, not today's canonical tail.
234
+ if (result.replayed) yield* refresh;
235
+ else tail = { sequence: result.lastSequence };
236
+
237
+ return result;
238
+ }
239
+ }).pipe(
240
+ // Revoke uncertain writes before releasing the writer's mutation gate.
241
+ Effect.onError((cause) =>
242
+ isDefiniteWriteFailure(cause) ? Effect.void : ownership.release,
243
+ ),
238
244
  ),
239
245
  ),
240
- ),
241
- );
246
+ );
247
+ });
248
+
249
+ return append;
242
250
  });
243
251
 
244
252
  const writer = {
@@ -247,13 +255,14 @@ const makeWriter = Effect.fnUntraced(function* (threadId: ThreadId) {
247
255
  withProgress: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
248
256
  owner.withProgress(threadId, Effect.andThen(checkActive, effect)),
249
257
  tail: Effect.sync(() => tail),
250
- append,
258
+ append: yield* makeAppend,
251
259
  checkActive: owner.withMutation(checkActive),
252
260
  refresh,
253
261
  };
254
262
 
255
263
  return {
256
264
  writer,
265
+ makeAppend,
257
266
  adoptTail: (sequence: CanonicalSequence) => {
258
267
  tail = { sequence };
259
268
  },
@@ -310,7 +319,7 @@ export const make = Effect.gen(function* () {
310
319
  });
311
320
  yield* store.materialize(ThreadMaterialization.make({ threadId: request.threadId }));
312
321
 
313
- const { writer, adoptTail } = yield* makeWriter(request.threadId).pipe(
322
+ const { writer, makeAppend, adoptTail } = yield* makeWriter(request.threadId).pipe(
314
323
  Effect.provideService(WriterOwnership, generation),
315
324
  Effect.provideService(ThreadStore, store),
316
325
  Effect.provideService(StorageOwner, owner),
@@ -318,9 +327,22 @@ export const make = Effect.gen(function* () {
318
327
 
319
328
  const owned = <A, E>(effect: Effect.Effect<A, E>) => generation.use(effect);
320
329
 
330
+ const inputPublisher = ledger.appendInput;
331
+
332
+ const appendInput =
333
+ inputPublisher === undefined
334
+ ? undefined
335
+ : yield* makeAppend.pipe(
336
+ Effect.provideService(ThreadStore, {
337
+ ...store,
338
+ append: (request) => inputPublisher(claimed.submissionId, request),
339
+ }),
340
+ );
341
+
321
342
  const session: RunStorageSession = {
322
343
  ...writer,
323
344
  claim: claimed,
345
+ ...(appendInput === undefined ? {} : { appendInput }),
324
346
  withInitialization: (effect) =>
325
347
  owner.withProgress(request.threadId, generation.use(effect)),
326
348
  release: generation.release,
@@ -36,6 +36,7 @@ import {
36
36
  ToolCallUnknown,
37
37
  WorkerAdmission,
38
38
  } from "./Records.ts";
39
+ import type { AppendRequest, AppendResult, ThreadStoreFailure } from "./ThreadStore.ts";
39
40
 
40
41
  export { IdempotencyKey, JoinedToHost, QueueSequence, Principal } from "../core/Receipt.ts";
41
42
 
@@ -1113,6 +1114,13 @@ export class SubmissionLedger extends Context.Service<
1113
1114
  readonly selectHead: (
1114
1115
  request: ClaimRequest,
1115
1116
  ) => Effect.Effect<Option.Option<Claim>, LedgerError>;
1117
+ /** Atomically publish a captured original input, active context and applied marker.
1118
+ * Optional adapters retain the ordinary append-then-repair protocol. The captured input
1119
+ * must be this owner's deterministic admission; a moved context boundary is refused. */
1120
+ readonly appendInput?: (
1121
+ submissionId: SubmissionId,
1122
+ request: AppendRequest,
1123
+ ) => Effect.Effect<AppendResult, ThreadStoreFailure>;
1116
1124
  readonly markInputApplied: (
1117
1125
  request: MarkInputAppliedRequest,
1118
1126
  ) => Effect.Effect<void, OwnershipLost | LedgerError>;