effect-agent 0.1.0-beta.142 → 0.1.0-beta.144

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 (51) hide show
  1. package/dist/{Admin-DGZXE5BU.d.mts → Admin-CQMppCcy.d.mts} +986 -3
  2. package/dist/{DurableAgentRuntime-DQXDcPKQ.mjs → DurableAgentRuntime-D8GI66mP.mjs} +20 -2
  3. package/dist/DurableAgentRuntime-D8GI66mP.mjs.map +1 -0
  4. package/dist/{EventSource-Cg3IeJa6.d.mts → EventSource-w9PonN9X.d.mts} +2 -2
  5. package/dist/{PreparedInputAdmission-CLvaruRe.d.mts → PreparedInputAdmission-BfcLy_k4.d.mts} +3 -3
  6. package/dist/{Subscription-_VTIVN8o.d.mts → Subscription-ClkC4_T0.d.mts} +2 -2
  7. package/dist/{SubscriptionInput-BIH1f3Th.d.mts → SubscriptionInput-CMjEKL1z.d.mts} +2 -2
  8. package/dist/durable/Admin.d.mts +1 -1
  9. package/dist/durable/AgentRegistration.d.mts +1 -1
  10. package/dist/durable/Certification.d.mts +1 -1
  11. package/dist/durable/CommittedActivity.d.mts +1 -1
  12. package/dist/durable/DurableAgentRuntime.d.mts +1 -1
  13. package/dist/durable/DurableAgentRuntime.mjs +1 -1
  14. package/dist/durable/EventSource.d.mts +1 -1
  15. package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
  16. package/dist/durable/LifecyclePublication.d.mts +2 -0
  17. package/dist/durable/LifecyclePublication.mjs +169 -0
  18. package/dist/durable/LifecyclePublication.mjs.map +1 -0
  19. package/dist/durable/MessageDelivery.d.mts +4 -3
  20. package/dist/durable/MessageDelivery.mjs.map +1 -1
  21. package/dist/durable/MessageDeliveryStoreConformance.d.mts +1 -1
  22. package/dist/durable/PersistentHistory.d.mts +1 -1
  23. package/dist/durable/PreparedInputAdmission.d.mts +1 -1
  24. package/dist/durable/Recovery.d.mts +1 -1
  25. package/dist/durable/RunJournal.d.mts +1 -1
  26. package/dist/durable/Scheduling.d.mts +1 -1
  27. package/dist/durable/SubmissionLedger.d.mts +1 -1
  28. package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
  29. package/dist/durable/SubmissionStatus.d.mts +1 -1
  30. package/dist/durable/Subscription.d.mts +1 -1
  31. package/dist/durable/SubscriptionInput.d.mts +1 -1
  32. package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
  33. package/dist/durable/SubscriptionTools.d.mts +2 -2
  34. package/dist/durable/SubscriptionTransition.d.mts +1 -1
  35. package/dist/durable/Subscriptions.d.mts +5 -5
  36. package/dist/durable/ThreadContextHistory.d.mts +1 -1
  37. package/dist/durable/ThreadInvariants.d.mts +1 -1
  38. package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
  39. package/dist/durable/ThreadStore.d.mts +1 -1
  40. package/dist/durable/ThreadStore.mjs.map +1 -1
  41. package/dist/durable/ThreadStoreConformance.d.mts +1 -1
  42. package/dist/durable/WorkerHost.d.mts +1 -1
  43. package/dist/index.d.mts +6 -6
  44. package/dist/index.mjs +3 -2
  45. package/package.json +1 -1
  46. package/src/durable/DurableAgentRuntime.ts +41 -1
  47. package/src/durable/LifecyclePublication.ts +244 -0
  48. package/src/durable/MessageDelivery.ts +2 -0
  49. package/src/durable/ThreadStore.ts +2 -0
  50. package/src/index.ts +1 -0
  51. package/dist/DurableAgentRuntime-DQXDcPKQ.mjs.map +0 -1
@@ -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>;
package/src/index.ts CHANGED
@@ -95,3 +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
+ export * as LifecyclePublication from "./durable/LifecyclePublication.ts";