effect-agent 0.1.0-beta.140 → 0.1.0-beta.143

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 (77) hide show
  1. package/dist/{Admin-DGZXE5BU.d.mts → Admin-CQMppCcy.d.mts} +986 -3
  2. package/dist/{AgentRuntime-RdmnK_aD.mjs → AgentRuntime-xeCUXutN.mjs} +10 -9
  3. package/dist/AgentRuntime-xeCUXutN.mjs.map +1 -0
  4. package/dist/{DurableAgentRuntime-BmG_UVZG.mjs → DurableAgentRuntime-D8GI66mP.mjs} +21 -3
  5. package/dist/DurableAgentRuntime-D8GI66mP.mjs.map +1 -0
  6. package/dist/{EventSource-Cg3IeJa6.d.mts → EventSource-w9PonN9X.d.mts} +2 -2
  7. package/dist/{PreparedInputAdmission-CLvaruRe.d.mts → PreparedInputAdmission-BfcLy_k4.d.mts} +3 -3
  8. package/dist/{Subagent-Bnc7I-kb.mjs → Subagent-8RzJkqJ9.mjs} +2 -2
  9. package/dist/{Subagent-Bnc7I-kb.mjs.map → Subagent-8RzJkqJ9.mjs.map} +1 -1
  10. package/dist/{Subscription-_VTIVN8o.d.mts → Subscription-ClkC4_T0.d.mts} +2 -2
  11. package/dist/{SubscriptionInput-BIH1f3Th.d.mts → SubscriptionInput-CMjEKL1z.d.mts} +2 -2
  12. package/dist/capabilities/Subagent.mjs +1 -1
  13. package/dist/durable/Admin.d.mts +1 -1
  14. package/dist/durable/AgentRegistration.d.mts +1 -1
  15. package/dist/durable/Certification.d.mts +1 -1
  16. package/dist/durable/CommittedActivity.d.mts +1 -1
  17. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  18. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  19. package/dist/durable/EventSource.d.mts +1 -1
  20. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  21. package/dist/durable/LifecyclePublication.d.mts +2 -0
  22. package/dist/durable/LifecyclePublication.mjs +169 -0
  23. package/dist/durable/LifecyclePublication.mjs.map +1 -0
  24. package/dist/durable/MessageDelivery.d.mts +4 -3
  25. package/dist/durable/MessageDelivery.mjs.map +1 -1
  26. package/dist/durable/MessageDeliveryStoreConformance.d.mts +1 -1
  27. package/dist/durable/PersistentHistory.d.mts +1 -1
  28. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  29. package/dist/durable/Recovery.d.mts +1 -1
  30. package/dist/durable/RunJournal.d.mts +1 -1
  31. package/dist/durable/Scheduling.d.mts +1 -1
  32. package/dist/durable/SubmissionLedger.d.mts +1 -1
  33. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  34. package/dist/durable/SubmissionStatus.d.mts +1 -1
  35. package/dist/durable/Subscription.d.mts +1 -1
  36. package/dist/durable/SubscriptionInput.d.mts +1 -1
  37. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  38. package/dist/durable/SubscriptionTools.d.mts +2 -2
  39. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  40. package/dist/durable/Subscriptions.d.mts +5 -5
  41. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  42. package/dist/durable/ThreadInvariants.d.mts +1 -1
  43. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  44. package/dist/durable/ThreadStore.d.mts +1 -1
  45. package/dist/durable/ThreadStore.mjs.map +1 -1
  46. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  47. package/dist/durable/WorkerHost.d.mts +1 -1
  48. package/dist/engine/AgentRuntime.mjs +1 -1
  49. package/dist/index.d.mts +6 -7
  50. package/dist/index.mjs +5 -5
  51. package/package.json +1 -1
  52. package/src/durable/DurableAgentRuntime.ts +41 -1
  53. package/src/durable/LifecyclePublication.ts +244 -0
  54. package/src/durable/MessageDelivery.ts +2 -0
  55. package/src/durable/ThreadStore.ts +2 -0
  56. package/src/engine/internal/agent-runtime.ts +49 -19
  57. package/src/index.ts +1 -2
  58. package/dist/AgentRuntime-RdmnK_aD.mjs.map +0 -1
  59. package/dist/DurableAgentRuntime-BmG_UVZG.mjs.map +0 -1
  60. package/dist/durable/SqlMessageDeliveryStore.d.mts +0 -42
  61. package/dist/durable/SqlMessageDeliveryStore.mjs +0 -179
  62. package/dist/durable/SqlMessageDeliveryStore.mjs.map +0 -1
  63. package/dist/durable/SqlStorageV2Upgrade.d.mts +0 -21
  64. package/dist/durable/SqlStorageV2Upgrade.mjs +0 -459
  65. package/dist/durable/SqlStorageV2Upgrade.mjs.map +0 -1
  66. package/dist/durable/SqlSubscriptionStore.d.mts +0 -62
  67. package/dist/durable/SqlSubscriptionStore.mjs +0 -751
  68. package/dist/durable/SqlSubscriptionStore.mjs.map +0 -1
  69. package/dist/durable/SqlThreadNativeReads.d.mts +0 -55
  70. package/dist/durable/SqlThreadNativeReads.mjs +0 -196
  71. package/dist/durable/SqlThreadNativeReads.mjs.map +0 -1
  72. package/src/durable/SqlMessageDeliveryStore.ts +0 -335
  73. package/src/durable/SqlStorageV2Upgrade.ts +0 -500
  74. package/src/durable/SqlSubscriptionStore.ts +0 -1440
  75. package/src/durable/SqlThreadNativeReads.ts +0 -343
  76. package/src/durable/internal/storage-v2-layout.ts +0 -163
  77. package/src/durable/internal/storage-v2.ts +0 -73
@@ -1265,6 +1265,21 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
1265
1265
  const ledger = yield* SubmissionLedger;
1266
1266
  const submissionScheduling = yield* SubmissionScheduling;
1267
1267
  const store = yield* ThreadStore;
1268
+
1269
+ const publicationPending = (threadId: ThreadId) =>
1270
+ store.lifecyclePublications === undefined
1271
+ ? Effect.succeed(false)
1272
+ : store.lifecyclePublications.pendingDeadlineFor(threadId).pipe(
1273
+ Effect.map(Option.isSome),
1274
+ Effect.mapError((cause) =>
1275
+ ThreadStoreError.make({
1276
+ operation: "read lifecycle publication gate",
1277
+ message: "Native lifecycle publication state is unavailable",
1278
+ cause,
1279
+ }),
1280
+ ),
1281
+ );
1282
+
1268
1283
  const wake = yield* WakeScheduler;
1269
1284
  const failpoint = yield* DurableRuntimeFailpoint;
1270
1285
  const config = yield* DurableRuntimeConfig;
@@ -4609,6 +4624,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
4609
4624
  ) =>
4610
4625
  Effect.gen(function* () {
4611
4626
  const submissionId = submission.submissionId;
4627
+
4628
+ if (yield* publicationPending(submission.threadId)) return { _tag: "yielded" as const };
4612
4629
  const runId = runIdForSubmission(submissionId);
4613
4630
  const boundaries: Array<JournalBoundary> = [];
4614
4631
 
@@ -6234,6 +6251,19 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
6234
6251
  return yield* renderInputPrompt(inputPrompt, decodedInput, encodedInput);
6235
6252
  });
6236
6253
 
6254
+ const yieldForPublication = Effect.gen(function* () {
6255
+ const pending = yield* Effect.exit(publicationPending(submission.threadId));
6256
+
6257
+ if (Exit.isSuccess(pending) && !pending.value) return;
6258
+ // A completed plain-text response may not yet have reached beforeTurn. Retain it
6259
+ // before either a publication wait or its failed state read closes the Attempt.
6260
+ yield* recordHalt(commitPendingTurn);
6261
+ if (Exit.isFailure(pending)) return yield* recordHalt(Effect.failCause(pending.cause));
6262
+ yield* Deferred.succeed(yieldSignal, undefined);
6263
+
6264
+ return yield* Effect.never;
6265
+ });
6266
+
6237
6267
  const input: RunInputHook<
6238
6268
  CoordinatorHalt | Agent.Failure<typeof agent>,
6239
6269
  Agent.DefinitionRequirements<(typeof agent)["definition"]>
@@ -6252,6 +6282,8 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
6252
6282
  )
6253
6283
  yield* recordHalt(commitPendingTurn);
6254
6284
 
6285
+ yield* yieldForPublication;
6286
+
6255
6287
  const joinedInputs = yield* recordHalt(
6256
6288
  Effect.gen(function* () {
6257
6289
  const limit = policy === "one" ? 1 : MAX_JOIN_DRAIN;
@@ -6390,6 +6422,10 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
6390
6422
  }),
6391
6423
  );
6392
6424
 
6425
+ // Appending a canonical joined input may retain a new publication. Its native
6426
+ // receipt remains successful; recovery reattaches it after acknowledgement.
6427
+ yield* yieldForPublication;
6428
+
6393
6429
  return yield* Effect.forEach(joinedInputs, (joinedInput) =>
6394
6430
  renderJoinedInput(joinedInput).pipe(
6395
6431
  Effect.map((input): RunInputCommand => ({
@@ -7072,6 +7108,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7072
7108
  // Preparation may resolve the next Model from canonical Tool results or invoke
7073
7109
  // a compaction Model. Publish the completed Turn before either can observe it.
7074
7110
  yield* recordHalt(commitPendingTurn);
7111
+ yield* yieldForPublication;
7075
7112
 
7076
7113
  if (
7077
7114
  yieldAfter !== undefined &&
@@ -7655,7 +7692,9 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
7655
7692
  const execution = Effect.raceFirst(consume, Effect.raceFirst(abortWatcher, renewal));
7656
7693
 
7657
7694
  const raced = (
7658
- yieldAfter === undefined && submissionScheduling.yieldTo === undefined
7695
+ yieldAfter === undefined &&
7696
+ submissionScheduling.yieldTo === undefined &&
7697
+ store.lifecyclePublications === undefined
7659
7698
  ? execution
7660
7699
  : Effect.raceFirst(
7661
7700
  execution,
@@ -8533,6 +8572,7 @@ const make = Effect.fn("DurableAgentRuntime.make")(function* (
8533
8572
 
8534
8573
  const attempt = Effect.scoped(
8535
8574
  Effect.gen(function* () {
8575
+ if (yield* publicationPending(threadId)) return Option.none();
8536
8576
  const claimed = yield* acquireClaim(threadId, handoff);
8537
8577
 
8538
8578
  if (Option.isNone(claimed)) return Option.none();
@@ -0,0 +1,244 @@
1
+ import { Clock, Context, Crypto, Effect, Layer, Option, Schema, type Scope } from "effect";
2
+
3
+ import { SubmissionId, ThreadId } from "../core/Identifiers.ts";
4
+ import { Receipt } from "../core/Receipt.ts";
5
+ import { AssignmentTerminal } from "../core/Worker.ts";
6
+ import { MessageDeliveryKey } from "./MessageDelivery.ts";
7
+ import {
8
+ AbortRequested,
9
+ AgentUpdateEmitted,
10
+ CanonicalSequence,
11
+ RunStartedRecord,
12
+ SubagentJoined,
13
+ SubagentRequested,
14
+ SubagentStarted,
15
+ SubmissionSettledRecord,
16
+ ToolApprovalDecided,
17
+ ToolApprovalRequested,
18
+ UserInputRecorded,
19
+ WorkerInputCompleted,
20
+ WorkerInputRequested,
21
+ WorkerStopRequested,
22
+ } from "./Records.ts";
23
+ import {
24
+ AbortIntent,
25
+ Settlement,
26
+ SubmissionLedger,
27
+ SubmissionLookupById,
28
+ SubmissionSnapshot,
29
+ SuspensionReason,
30
+ } from "./SubmissionLedger.ts";
31
+ import { PreparedInput } from "./Subscription.ts";
32
+ import { getRunInput } from "./ThreadStore.ts";
33
+
34
+ /** Immutable native admission evidence. It is private and grants no application authority. */
35
+ export const LifecyclePublicationSource = SubmissionSnapshot.mapFields((fields) => ({
36
+ submissionId: fields.submissionId,
37
+ threadId: fields.threadId,
38
+ queueSequence: fields.queueSequence,
39
+ principal: fields.principal,
40
+ idempotencyKey: fields.idempotencyKey,
41
+ agentId: fields.agentId,
42
+ agentDigests: fields.agentDigests,
43
+ inputPayload: fields.inputPayload,
44
+ inputDigest: fields.inputDigest,
45
+ receiptId: fields.receiptId,
46
+ parentLinkage: fields.parentLinkage,
47
+ workerAdmission: fields.workerAdmission,
48
+ messageAdmission: fields.messageAdmission,
49
+ createdAt: fields.createdAt,
50
+ }));
51
+
52
+ export type LifecyclePublicationSource = typeof LifecyclePublicationSource.Type;
53
+
54
+ /** Closed producer facts; model history, Tool payloads and diagnostics are not a publication API. */
55
+ export const LifecyclePublicationFact = Schema.Union([
56
+ Schema.TaggedStruct("DeliveryRetained", {
57
+ key: MessageDeliveryKey,
58
+ envelope: PreparedInput,
59
+ createdAtMillis: Schema.Natural,
60
+ }),
61
+ Schema.TaggedStruct("DeliveryChanged", {
62
+ key: MessageDeliveryKey,
63
+ envelope: PreparedInput,
64
+ status: Schema.Literals(["pending", "accepted", "processed", "refused", "parked"]),
65
+ receipt: Schema.NullOr(Receipt),
66
+ settlement: Schema.NullOr(Settlement),
67
+ version: Schema.Natural,
68
+ }),
69
+ Schema.TaggedStruct("WorkerInboxSealed", {
70
+ threadId: ThreadId,
71
+ activeSubmissionIds: Schema.Array(SubmissionId),
72
+ /** First native seal winner; null is an explicit stop rather than assignment completion. */
73
+ terminal: Schema.NullOr(AssignmentTerminal),
74
+ }),
75
+ Schema.TaggedStruct("SubmissionReady", { submissionId: SubmissionId }),
76
+ Schema.TaggedStruct("SubmissionSuspended", {
77
+ submissionId: SubmissionId,
78
+ reason: SuspensionReason,
79
+ }),
80
+ Schema.TaggedStruct("SubmissionUnknown", { submissionId: SubmissionId }),
81
+ Schema.TaggedStruct("SubmissionResumed", { submissionId: SubmissionId }),
82
+ Schema.TaggedStruct("AbortIntentRecorded", { intent: AbortIntent }),
83
+ UserInputRecorded,
84
+ RunStartedRecord,
85
+ AgentUpdateEmitted,
86
+ ToolApprovalRequested,
87
+ ToolApprovalDecided,
88
+ AbortRequested,
89
+ WorkerInputCompleted,
90
+ WorkerInputRequested,
91
+ WorkerStopRequested,
92
+ SubagentRequested,
93
+ SubagentStarted,
94
+ SubagentJoined,
95
+ SubmissionSettledRecord,
96
+ ]);
97
+
98
+ export type LifecyclePublicationFact = typeof LifecyclePublicationFact.Type;
99
+
100
+ /**
101
+ * An exact undelivered native fact. `id` is stable across retries and lost acknowledgements.
102
+ * Ordinals order facts only within ownerThreadId. Accepted input order is source.queueSequence;
103
+ * never compare ordinals from the launching Thread and its worker Thread.
104
+ */
105
+ export class LifecyclePublication extends Schema.Class<LifecyclePublication>(
106
+ "@effect-agent/thread/LifecyclePublication",
107
+ )({
108
+ id: Schema.NonEmptyString.check(Schema.isMaxLength(4096)),
109
+ ownerThreadId: ThreadId,
110
+ ordinal: Schema.Int.check(Schema.isGreaterThan(0)),
111
+ createdAt: Schema.DateTimeUtcFromString,
112
+ canonicalSequence: Schema.optionalKey(CanonicalSequence),
113
+ source: Schema.optionalKey(LifecyclePublicationSource),
114
+ fact: LifecyclePublicationFact,
115
+ }) {}
116
+
117
+ export class LifecyclePublicationError extends Schema.TaggedError<LifecyclePublicationError>()(
118
+ "LifecyclePublicationError",
119
+ {
120
+ reason: Schema.Literals(["unavailable", "conflict", "corrupt", "capacity"]),
121
+ cause: Schema.optionalKey(Schema.Defect()),
122
+ },
123
+ ) {}
124
+
125
+ /** Native recovery state, not a second application record or launch queue. */
126
+ export interface LifecyclePublicationStorage {
127
+ readonly pending: (
128
+ nowMillis: number,
129
+ limit: number,
130
+ ) => Effect.Effect<ReadonlyArray<LifecyclePublication>, LifecyclePublicationError>;
131
+ readonly acknowledge: (
132
+ publication: LifecyclePublication,
133
+ ) => Effect.Effect<void, LifecyclePublicationError>;
134
+ readonly defer: (
135
+ publication: LifecyclePublication,
136
+ untilMillis: number,
137
+ ) => Effect.Effect<void, LifecyclePublicationError>;
138
+ readonly pendingDeadline: Effect.Effect<Option.Option<number>, LifecyclePublicationError>;
139
+ /** Earliest retained owner obligation, including deferred facts that still gate execution. */
140
+ readonly pendingDeadlineFor: (
141
+ ownerThreadId: ThreadId,
142
+ ) => Effect.Effect<Option.Option<number>, LifecyclePublicationError>;
143
+ }
144
+
145
+ /**
146
+ * Publish an idempotent domain command, including its authorization, exact receipt and delivery
147
+ * intent. Return only after that command commits. Treat private input/update/result fields as
148
+ * private; the application must select its declared public fields. Revocation/deletion is an
149
+ * acknowledged domain decision, not an indefinitely retryable infrastructure failure.
150
+ */
151
+ export class LifecyclePublicationHandler extends Context.Service<
152
+ LifecyclePublicationHandler,
153
+ {
154
+ readonly publish: (
155
+ publication: LifecyclePublication,
156
+ ) => Effect.Effect<void, LifecyclePublicationError, Scope.Scope>;
157
+ }
158
+ >()("@effect-agent/thread/LifecyclePublicationHandler") {}
159
+
160
+ /** Adapter configuration; the opt-in layer captures its explicit Crypto requirement once. */
161
+ export const LifecyclePublicationConfig = Context.Reference<Option.Option<Crypto.Crypto>>(
162
+ "@effect-agent/thread/LifecyclePublicationConfig",
163
+ { defaultValue: () => Option.none() },
164
+ );
165
+
166
+ /** Enable native SQL obligations without changing dependencies of disabled storage assemblies. */
167
+ export const lifecyclePublicationLayer = Layer.effect(LifecyclePublicationConfig)(
168
+ Effect.map(Crypto.Crypto, Option.some),
169
+ );
170
+
171
+ /** Resolve immutable evidence by its exact native identity, never by scanning execution history. */
172
+ const withSource = Effect.fn("LifecyclePublication.withSource")(
173
+ function* (publication: LifecyclePublication) {
174
+ if (
175
+ publication.source !== undefined ||
176
+ (publication.fact._tag === "WorkerInputCompleted" &&
177
+ publication.fact.workerThreadId !== publication.ownerThreadId)
178
+ )
179
+ return publication;
180
+ const fact = publication.fact;
181
+
182
+ let submissionId =
183
+ fact._tag === "AbortIntentRecorded"
184
+ ? fact.intent.submissionId
185
+ : "submissionId" in fact
186
+ ? fact.submissionId
187
+ : undefined;
188
+
189
+ const runId =
190
+ fact._tag === "AgentUpdateEmitted"
191
+ ? fact.update.runId
192
+ : "runId" in fact
193
+ ? fact.runId
194
+ : undefined;
195
+
196
+ if (submissionId === undefined && runId !== undefined) {
197
+ const input = yield* getRunInput({ threadId: publication.ownerThreadId, runId });
198
+
199
+ if (Option.isNone(input) || input.value.record.payload._tag !== "UserInputRecorded")
200
+ return yield* LifecyclePublicationError.make({ reason: "unavailable" });
201
+ submissionId = input.value.record.payload.submissionId;
202
+ }
203
+ if (submissionId === undefined) return publication;
204
+ const ledger = yield* SubmissionLedger;
205
+ const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId }));
206
+
207
+ if (Option.isNone(found))
208
+ return yield* LifecyclePublicationError.make({ reason: "unavailable" });
209
+
210
+ const source = yield* Schema.decodeEffect(Schema.toType(LifecyclePublicationSource))(
211
+ found.value,
212
+ );
213
+
214
+ return LifecyclePublication.make({ ...publication, source });
215
+ },
216
+ Effect.mapError((cause) => LifecyclePublicationError.make({ reason: "unavailable", cause })),
217
+ );
218
+
219
+ /**
220
+ * One finite wave for the host's existing maintenance coordinator. Persist the next deadline
221
+ * before dispatch, so interruption and process loss retain the same fact without another timer.
222
+ * Acknowledgements are exact and idempotent. No producer or external Tool is re-executed here.
223
+ */
224
+ export const drainLifecyclePublications = Effect.fn("LifecyclePublication.drain")(function* (
225
+ storage: LifecyclePublicationStorage,
226
+ timeoutMillis = 10_000,
227
+ limit = 4,
228
+ ) {
229
+ const handler = yield* LifecyclePublicationHandler;
230
+ const pending = yield* storage.pending(yield* Clock.currentTimeMillis, limit);
231
+
232
+ for (const publication of pending) {
233
+ yield* storage.defer(publication, (yield* Clock.currentTimeMillis) + timeoutMillis);
234
+ yield* Effect.scoped(withSource(publication).pipe(Effect.flatMap(handler.publish))).pipe(
235
+ Effect.timeoutOrElse({
236
+ duration: timeoutMillis,
237
+ orElse: () => LifecyclePublicationError.make({ reason: "unavailable" }),
238
+ }),
239
+ );
240
+ yield* storage.acknowledge(publication);
241
+ }
242
+
243
+ return pending.length;
244
+ });
@@ -5,6 +5,7 @@ import { AgentId, DelegationId, ThreadId } from "../core/Identifiers.ts";
5
5
  import { Receipt } from "../core/Receipt.ts";
6
6
  import { WorkerUpdate } from "../core/Worker.ts";
7
7
  import { digestJson } from "./Digest.ts";
8
+ import type { LifecyclePublicationStorage } from "./LifecyclePublication.ts";
8
9
  import { admitPreparedInput, PreparedInputAdmission } from "./PreparedInputAdmission.ts";
9
10
  import { Digest } from "./Records.ts";
10
11
  import { ScheduleInstant, ScheduleRetry, ScheduleRetryReason } from "./Schedule.ts";
@@ -222,6 +223,7 @@ export type MessageDeliveryPage = typeof MessageDeliveryPage.Type;
222
223
  export class MessageDeliveryStore extends Context.Service<
223
224
  MessageDeliveryStore,
224
225
  {
226
+ readonly lifecyclePublications?: LifecyclePublicationStorage;
225
227
  readonly limits: MessageDeliveryStoreLimits;
226
228
  readonly maxStoredValueBytes: number;
227
229
  readonly insert: (
@@ -1,6 +1,7 @@
1
1
  import { Context, Effect, Option, Schema, Stream } from "effect";
2
2
 
3
3
  import { RunId, SubmissionId, ThreadId } from "../core/Identifiers.ts";
4
+ import type { LifecyclePublicationStorage } from "./LifecyclePublication.ts";
4
5
  import type { ToolCallPrepared, WorkerInputRequested } from "./Records.ts";
5
6
  import {
6
7
  BatchId,
@@ -598,6 +599,7 @@ export interface ThreadRecoveryCheckpoints {
598
599
  export class ThreadStore extends Context.Service<
599
600
  ThreadStore,
600
601
  {
602
+ readonly lifecyclePublications?: LifecyclePublicationStorage;
601
603
  readonly materialize: (
602
604
  request: ThreadMaterialization,
603
605
  ) => Effect.Effect<void, ThreadStoreError | FenceRejected>;
@@ -24,7 +24,16 @@ import {
24
24
  Stream,
25
25
  Tracer,
26
26
  } from "effect";
27
- import { Tool, AiError, LanguageModel, Model, Prompt, Response, Toolkit } from "effect/unstable/ai";
27
+ import {
28
+ Tool,
29
+ AiError,
30
+ LanguageModel,
31
+ Model,
32
+ Prompt,
33
+ Response,
34
+ ResponseIdTracker,
35
+ Toolkit,
36
+ } from "effect/unstable/ai";
28
37
 
29
38
  import * as Agent from "../../core/Agent.ts";
30
39
  import {
@@ -3239,7 +3248,8 @@ export const formatRunStatus = (view: RunStatusView): string => {
3239
3248
  /**
3240
3249
  * The run-status message is derived per request and appended only to the
3241
3250
  * OUTGOING prompt: official history and durable commits never carry it, so it
3242
- * can never accumulate or replay.
3251
+ * can never accumulate or replay. OpenAI receives it as trailing system guidance,
3252
+ * leaving the last user/tool result as the implicit cache-write boundary.
3243
3253
  */
3244
3254
  const outgoingModelPrompt = (
3245
3255
  policy: AgentPolicy,
@@ -3247,12 +3257,13 @@ const outgoingModelPrompt = (
3247
3257
  prepared: Prompt.Prompt,
3248
3258
  turn: number,
3249
3259
  declaredToolCalls: number,
3250
- ): Effect.Effect<Prompt.Prompt> =>
3260
+ ): Effect.Effect<Prompt.Prompt, never, Model.ProviderName> =>
3251
3261
  Effect.gen(function* () {
3252
3262
  if (policy.runStatus !== "appended") {
3253
3263
  return prepared;
3254
3264
  }
3255
3265
  const now = yield* Clock.currentTimeMillis;
3266
+ const provider = yield* Model.ProviderName;
3256
3267
 
3257
3268
  const status = formatRunStatus({
3258
3269
  turn,
@@ -3269,9 +3280,9 @@ const outgoingModelPrompt = (
3269
3280
 
3270
3281
  return Prompt.fromMessages([
3271
3282
  ...prepared.content,
3272
- Prompt.makeMessage("user", {
3273
- content: [Prompt.makePart("text", { text: status })],
3274
- }),
3283
+ provider === "openai"
3284
+ ? Prompt.systemMessage({ content: status })
3285
+ : Prompt.userMessage({ content: [Prompt.textPart({ text: status })] }),
3275
3286
  ]);
3276
3287
  });
3277
3288
 
@@ -5679,7 +5690,9 @@ const makeTurn = <
5679
5690
 
5680
5691
  const canonicalDecoration = !admissionRequired
5681
5692
  ? Prompt.empty
5682
- : yield* outgoingModelPrompt(policy, context, Prompt.empty, turn, priorToolCalls);
5693
+ : yield* outgoingModelPrompt(policy, context, Prompt.empty, turn, priorToolCalls).pipe(
5694
+ withCallModel,
5695
+ );
5683
5696
 
5684
5697
  const estimateToolSchemaTokens = Effect.suspend(() => {
5685
5698
  const choice = modelToolChoice();
@@ -5986,7 +5999,13 @@ const makeTurn = <
5986
5999
  const derivedPrompt =
5987
6000
  options.transientContext === undefined
5988
6001
  ? canonicalDecoration
5989
- : yield* outgoingModelPrompt(policy, context, transientContext, turn, priorToolCalls);
6002
+ : yield* outgoingModelPrompt(
6003
+ policy,
6004
+ context,
6005
+ transientContext,
6006
+ turn,
6007
+ priorToolCalls,
6008
+ ).pipe(withCallModel);
5990
6009
 
5991
6010
  const derivedPromptContentTokens = !admissionRequired
5992
6011
  ? 0
@@ -6164,11 +6183,15 @@ const makeTurn = <
6164
6183
  outgoingModelPrompt(
6165
6184
  policy,
6166
6185
  context,
6167
- Prompt.fromMessages([...basis.content, ...transientContext.content]),
6186
+ prepareModelPrompt(
6187
+ Prompt.fromMessages([...basis.content, ...transientContext.content]),
6188
+ outputContract._tag === "rendered" ? outputContract.part : undefined,
6189
+ ),
6168
6190
  turn,
6169
6191
  priorToolCalls,
6170
6192
  ).pipe(
6171
- Effect.flatMap((outgoing) =>
6193
+ withCallModel,
6194
+ Effect.flatMap((providerPrompt) =>
6172
6195
  Effect.gen(function* () {
6173
6196
  const toolChoice = modelToolChoice();
6174
6197
 
@@ -6212,11 +6235,6 @@ const makeTurn = <
6212
6235
  }
6213
6236
  context.toolExposure = snapshot;
6214
6237
 
6215
- const providerPrompt = prepareModelPrompt(
6216
- outgoing,
6217
- outputContract._tag === "rendered" ? outputContract.part : undefined,
6218
- );
6219
-
6220
6238
  // Prepared and transient context can change at every Turn. A
6221
6239
  // final full-prompt check closes the per-call boundary for grace
6222
6240
  // finalization and any future path that bypasses research
@@ -6250,10 +6268,9 @@ const makeTurn = <
6250
6268
  Effect.as(
6251
6269
  guardBudgetStream(
6252
6270
  LanguageModel.streamText({
6253
- // The contract joins the final outgoing prompt (after
6254
- // compaction and the run-status append), so every attempt —
6255
- // including the overflow retry — carries it at the last
6256
- // system block.
6271
+ // Every attempt, including an overflow retry, places the
6272
+ // contract in the leading system block before appending
6273
+ // transient run status.
6257
6274
  prompt: providerPrompt,
6258
6275
  toolkit: deferredToolParameterToolkit(requestToolkit),
6259
6276
  disableToolCallResolution: true,
@@ -6263,6 +6280,19 @@ const makeTurn = <
6263
6280
  }),
6264
6281
  options.budget,
6265
6282
  ).pipe(
6283
+ // Provider-held responses retain prior model-only context.
6284
+ // Isolate tracking when a later request can discard it.
6285
+ (stream) =>
6286
+ policy.runStatus === "appended" ||
6287
+ options.context !== undefined ||
6288
+ options.transientContext !== undefined
6289
+ ? stream.pipe(
6290
+ Stream.provideServiceEffect(
6291
+ ResponseIdTracker.ResponseIdTracker,
6292
+ ResponseIdTracker.make,
6293
+ ),
6294
+ )
6295
+ : stream,
6266
6296
  Stream.provideServiceEffect(
6267
6297
  Tracer.Tracer,
6268
6298
  modelTelemetryTracer(context, turnId),
package/src/index.ts CHANGED
@@ -95,5 +95,4 @@ export * as WakeScheduler from "./durable/WakeScheduler.ts";
95
95
  export * as WorkerHost from "./durable/WorkerHost.ts";
96
96
  export * as ThreadContextHistory from "./durable/ThreadContextHistory.ts";
97
97
  export * as ThreadContextHistoryProjection from "./durable/ThreadContextHistoryProjection.ts";
98
-
99
- export * as SqlThreadNativeReads from "./durable/SqlThreadNativeReads.ts";
98
+ export * as LifecyclePublication from "./durable/LifecyclePublication.ts";