stitchkit 0.57.0 → 0.59.0

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 (34) hide show
  1. package/dist/agent-runtime/compaction.d.ts +3 -0
  2. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  3. package/dist/agent-runtime/events.d.ts +40 -0
  4. package/dist/agent-runtime/events.d.ts.map +1 -1
  5. package/dist/agent-runtime/history.d.ts +13 -0
  6. package/dist/agent-runtime/history.d.ts.map +1 -1
  7. package/dist/agent-runtime/managed-tools.d.ts +1 -0
  8. package/dist/agent-runtime/managed-tools.d.ts.map +1 -1
  9. package/dist/agent-runtime/models.d.ts +39 -3
  10. package/dist/agent-runtime/models.d.ts.map +1 -1
  11. package/dist/agent-runtime/observability.d.ts +3 -0
  12. package/dist/agent-runtime/observability.d.ts.map +1 -1
  13. package/dist/agent-runtime/prompt.d.ts +21 -0
  14. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  15. package/dist/agent-runtime/runtime.d.ts +9 -1
  16. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  17. package/dist/agent-runtime/schemas.d.ts +2 -0
  18. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  19. package/dist/agent-runtime/store-driver.d.ts +202 -24
  20. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  21. package/dist/agent-runtime/store.d.ts +229 -0
  22. package/dist/agent-runtime/store.d.ts.map +1 -1
  23. package/dist/agent-runtime/testing.d.ts +10 -1
  24. package/dist/agent-runtime/testing.d.ts.map +1 -1
  25. package/dist/agent-runtime.d.ts +5 -5
  26. package/dist/agent-runtime.d.ts.map +1 -1
  27. package/dist/agent-runtime.js +629 -200
  28. package/dist/{index-1f4fcj0b.js → index-vtjgx3vv.js} +1 -0
  29. package/dist/testing/agent-store-conformance.d.ts.map +1 -1
  30. package/dist/testing.d.ts +1 -0
  31. package/dist/testing.d.ts.map +1 -1
  32. package/dist/testing.js +198 -4
  33. package/llms-full.txt +156 -36
  34. package/package.json +1 -1
@@ -29,7 +29,7 @@ import {
29
29
  AgentToolResultPartSchema,
30
30
  AgentUsageSchema,
31
31
  AgentUsageValueSchema
32
- } from "./index-1f4fcj0b.js";
32
+ } from "./index-vtjgx3vv.js";
33
33
  import"./index-6djpbnda.js";
34
34
  import"./index-cby4ar3v.js";
35
35
  import {
@@ -86,54 +86,66 @@ function eligibleForCompaction(messages, keepRecentTurns) {
86
86
  const eligibleCount = Math.max(0, completeTurns.length - keepRecentTurns);
87
87
  return completeTurns.slice(0, eligibleCount).flatMap((turn) => turn.messages);
88
88
  }
89
- function mutationSnapshot(result, fallback) {
89
+ function mutationSnapshot(result, fallback, attempts) {
90
90
  if (result.outcome === "applied" || result.outcome === "duplicate") {
91
- return { outcome: "applied", snapshot: result.snapshot };
91
+ return { outcome: "applied", snapshot: result.snapshot, attempts };
92
92
  }
93
- return { outcome: result.outcome, snapshot: fallback };
93
+ return { outcome: result.outcome, snapshot: fallback, attempts };
94
94
  }
95
95
  function structuredCompaction(config) {
96
96
  if (!Number.isSafeInteger(config.keepRecentTurns) || config.keepRecentTurns < 1) {
97
97
  throw new TypeError("keepRecentTurns must be a positive safe integer");
98
98
  }
99
+ const maxAttempts = config.maxAttempts ?? 1;
100
+ if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
101
+ throw new TypeError("maxAttempts must be a positive safe integer");
102
+ }
99
103
  return async (input) => {
100
- const snapshot = await input.store.loadSnapshot(input.conversationId);
101
- if (!await config.threshold(snapshot))
102
- return { outcome: "not_needed", snapshot };
103
- const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
104
- if (eligibleMessages.length === 0)
105
- return { outcome: "nothing_eligible", snapshot };
106
- const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
107
- const previousSummary = input.previousSummary ?? (leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : undefined);
108
- const rawSummary = await config.summarize({
109
- conversationId: input.conversationId,
110
- snapshot,
111
- eligibleMessages,
112
- ...previousSummary !== undefined && { previousSummary },
113
- signal: input.signal
114
- });
115
- const summary = config.schema.parse(rawSummary);
116
- if (input.signal.aborted)
117
- throw input.signal.reason;
118
- const summaryMessage = config.createSummaryMessage({
119
- conversationId: input.conversationId,
120
- summary,
121
- compactedMessages: eligibleMessages
122
- });
123
- if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
124
- throw new TypeError("Compaction summary must be one committed summary message");
104
+ let lastSnapshot = await input.store.loadSnapshot(input.conversationId);
105
+ for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
106
+ const snapshot = attempt === 1 ? lastSnapshot : await input.store.loadSnapshot(input.conversationId);
107
+ lastSnapshot = snapshot;
108
+ if (!await config.threshold(snapshot)) {
109
+ return { outcome: "not_needed", snapshot, attempts: attempt };
110
+ }
111
+ const eligibleMessages = eligibleForCompaction(snapshot.messages, config.keepRecentTurns);
112
+ if (eligibleMessages.length === 0) {
113
+ return { outcome: "nothing_eligible", snapshot, attempts: attempt };
114
+ }
115
+ const leadingSummary = snapshot.messages[0]?.role === "summary" ? snapshot.messages[0] : undefined;
116
+ const previousSummary = leadingSummary && config.readPreviousSummary ? config.schema.parse(config.readPreviousSummary(leadingSummary)) : attempt === 1 ? input.previousSummary : undefined;
117
+ const rawSummary = await config.summarize({
118
+ conversationId: input.conversationId,
119
+ snapshot,
120
+ eligibleMessages,
121
+ ...previousSummary !== undefined && { previousSummary },
122
+ signal: input.signal
123
+ });
124
+ const summary = config.schema.parse(rawSummary);
125
+ if (input.signal.aborted)
126
+ throw input.signal.reason;
127
+ const summaryMessage = config.createSummaryMessage({
128
+ conversationId: input.conversationId,
129
+ summary,
130
+ compactedMessages: eligibleMessages
131
+ });
132
+ if (summaryMessage.role !== "summary" || summaryMessage.status !== "committed") {
133
+ throw new TypeError("Compaction summary must be one committed summary message");
134
+ }
135
+ const applied = await input.store.replaceCompactedRange({
136
+ conversationId: input.conversationId,
137
+ expectedVersion: snapshot.version,
138
+ replacedMessageIds: [
139
+ ...leadingSummary ? [leadingSummary.id] : [],
140
+ ...eligibleMessages.map((message) => message.id)
141
+ ],
142
+ summary: summaryMessage
143
+ });
144
+ if (applied.outcome !== "conflict" || attempt === maxAttempts) {
145
+ return mutationSnapshot(applied, snapshot, attempt);
146
+ }
125
147
  }
126
- const previousSummaryMessage = previousSummary !== undefined && leadingSummary ? leadingSummary : undefined;
127
- const applied = await input.store.replaceCompactedRange({
128
- conversationId: input.conversationId,
129
- expectedVersion: snapshot.version,
130
- replacedMessageIds: [
131
- ...previousSummaryMessage ? [previousSummaryMessage.id] : [],
132
- ...eligibleMessages.map((message) => message.id)
133
- ],
134
- summary: summaryMessage
135
- });
136
- return mutationSnapshot(applied, snapshot);
148
+ return { outcome: "conflict", snapshot: lastSnapshot, attempts: maxAttempts };
137
149
  };
138
150
  }
139
151
  // src/agent-runtime/coordinator.ts
@@ -335,6 +347,63 @@ var AgentRuntimeEventSchema = z.discriminatedUnion("type", [
335
347
  AgentToolStatusEventSchema,
336
348
  AgentTerminalEventSchema
337
349
  ]);
350
+ var AgentRuntimeEventCursorSchema = z.object({
351
+ snapshotVersion: AgentRecordVersionSchema.optional(),
352
+ durableEventIds: z.array(AgentRecordIdSchema).optional(),
353
+ runtimeEpoch: z.string().min(1).optional(),
354
+ sequence: z.int().nonnegative().optional()
355
+ });
356
+ function isDurableEvent(event) {
357
+ return event.type === "admission" || event.type === "assistant-checkpoint" || event.type === "run-state" || event.type === "terminal";
358
+ }
359
+ function advanceAgentRuntimeEventCursor(rawCursor, event) {
360
+ const cursor = AgentRuntimeEventCursorSchema.parse(rawCursor);
361
+ if (isDurableEvent(event)) {
362
+ const previous = cursor.snapshotVersion;
363
+ const durableEventIds = previous === event.snapshotVersion ? cursor.durableEventIds ?? [] : [];
364
+ if (previous !== undefined && event.snapshotVersion < previous || durableEventIds.includes(event.eventId)) {
365
+ return { status: "duplicate", cursor };
366
+ }
367
+ return {
368
+ status: previous !== undefined && event.snapshotVersion > previous + 1 ? "gap" : "accepted",
369
+ cursor: {
370
+ ...cursor,
371
+ snapshotVersion: event.snapshotVersion,
372
+ durableEventIds: [...durableEventIds, event.eventId]
373
+ }
374
+ };
375
+ }
376
+ const previousSequence = cursor.runtimeEpoch === event.runtimeEpoch ? cursor.sequence : undefined;
377
+ if (previousSequence !== undefined && event.sequence <= previousSequence) {
378
+ return { status: "duplicate", cursor };
379
+ }
380
+ return {
381
+ status: previousSequence !== undefined && event.sequence > previousSequence + 1 ? "gap" : "accepted",
382
+ cursor: { ...cursor, runtimeEpoch: event.runtimeEpoch, sequence: event.sequence }
383
+ };
384
+ }
385
+ function createAgentRuntimeEventSink(config) {
386
+ const manager = createBoundedSinkManager({
387
+ write: config.write,
388
+ ...config.maxPending !== undefined && { maxPending: config.maxPending },
389
+ ...config.onSinkError && { onSinkError: config.onSinkError },
390
+ ...config.onDrop && { onDrop: config.onDrop }
391
+ });
392
+ return {
393
+ publish(rawEvent) {
394
+ const event = AgentRuntimeEventSchema.parse(rawEvent);
395
+ const projected = config.project?.(event) ?? (config.project ? undefined : event);
396
+ if (projected)
397
+ manager.submit(() => AgentRuntimeEventSchema.parse(projected));
398
+ },
399
+ flush: () => manager.flush(),
400
+ getStatus: () => manager.getStatus(),
401
+ close: () => manager.close()
402
+ };
403
+ }
404
+ function agentDurableEventId(type, runId, snapshotVersion) {
405
+ return `${runId}:${type}:${snapshotVersion}`;
406
+ }
338
407
  // src/agent-runtime/history.ts
339
408
  import { modelMessageSchema } from "ai";
340
409
  function providerOptions(envelope) {
@@ -416,26 +485,75 @@ function assistantMessages(message) {
416
485
  }
417
486
  return messages;
418
487
  }
419
- async function projectAgentHistory(messages, options = {}) {
488
+ function completeToolChronology(message) {
489
+ const calls = new Set(message.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
490
+ const results = new Set(message.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
491
+ return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
492
+ }
493
+ async function projectAgentHistoryDetailed(messages, options = {}) {
420
494
  const projected = [];
495
+ const decisions = [];
496
+ let observedUser = false;
421
497
  for (const message of messages) {
422
- if (message.status === "streaming" || message.status === "failed")
498
+ if (message.status === "streaming" || message.status === "failed") {
499
+ decisions.push({ messageId: message.id, action: "omitted", reason: "draft-or-failed" });
423
500
  continue;
501
+ }
424
502
  if (message.role === "user") {
425
503
  const user = await userMessage(message, options);
426
- if (user)
504
+ observedUser = true;
505
+ if (user) {
427
506
  projected.push(user);
507
+ decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
508
+ } else {
509
+ decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
510
+ }
428
511
  continue;
429
512
  }
430
513
  if (message.role === "system" || message.role === "summary") {
431
514
  const content = textContent(message.parts);
432
- if (content)
515
+ if (content) {
433
516
  projected.push(modelMessageSchema.parse({ role: "system", content }));
517
+ decisions.push({ messageId: message.id, action: "projected", reason: "projected" });
518
+ } else {
519
+ decisions.push({ messageId: message.id, action: "omitted", reason: "empty" });
520
+ }
434
521
  continue;
435
522
  }
436
- projected.push(...assistantMessages(message));
523
+ if (!observedUser && options.leadingAssistant !== "allow") {
524
+ if (options.leadingAssistant === "error") {
525
+ throw new Error(`Assistant message ${message.id} precedes the first user message`);
526
+ }
527
+ decisions.push({
528
+ messageId: message.id,
529
+ action: "omitted",
530
+ reason: "leading-assistant"
531
+ });
532
+ continue;
533
+ }
534
+ if (!completeToolChronology(message)) {
535
+ if (options.incompleteToolTurn === "error") {
536
+ throw new Error(`Assistant message ${message.id} has incomplete tool chronology`);
537
+ }
538
+ decisions.push({
539
+ messageId: message.id,
540
+ action: "omitted",
541
+ reason: "incomplete-tool-turn"
542
+ });
543
+ continue;
544
+ }
545
+ const assistant = assistantMessages(message);
546
+ projected.push(...assistant);
547
+ decisions.push({
548
+ messageId: message.id,
549
+ action: assistant.length > 0 ? "projected" : "omitted",
550
+ reason: assistant.length > 0 ? "projected" : "empty"
551
+ });
437
552
  }
438
- return projected;
553
+ return { messages: projected, decisions };
554
+ }
555
+ async function projectAgentHistory(messages, options = {}) {
556
+ return [...(await projectAgentHistoryDetailed(messages, options)).messages];
439
557
  }
440
558
  // src/agent-runtime/managed-tools.ts
441
559
  async function assertFence(config, input) {
@@ -469,7 +587,14 @@ var AgentModelDescriptorSchema = z2.object({
469
587
  contextWindow: z2.int().positive(),
470
588
  capabilities: z2.array(AgentModelCapabilitySchema),
471
589
  observedAt: z2.iso.datetime({ offset: true }).optional(),
472
- source: z2.string().min(1).optional()
590
+ source: z2.string().min(1).optional(),
591
+ availability: z2.enum(["available", "unavailable"]).optional()
592
+ });
593
+ var AgentModelRegistrySnapshotSchema = z2.object({
594
+ schemaVersion: z2.literal(1),
595
+ source: z2.string().min(1),
596
+ observedAt: z2.iso.datetime({ offset: true }),
597
+ models: z2.record(z2.string().min(1), AgentModelDescriptorSchema)
473
598
  });
474
599
  function defineModelRegistry(config) {
475
600
  const descriptors = new Map;
@@ -488,15 +613,26 @@ function defineModelRegistry(config) {
488
613
  const available = new Set(descriptor(key).capabilities);
489
614
  return capabilities.every((capability) => available.has(capability));
490
615
  };
616
+ const preflight = (key, required = []) => {
617
+ const selected = descriptor(key);
618
+ if (selected.availability === "unavailable") {
619
+ throw new Error(`Agent model ${key} is unavailable`);
620
+ }
621
+ if (!supports(key, required)) {
622
+ throw new Error(`Agent model ${key} does not satisfy required capabilities`);
623
+ }
624
+ if (!config.providers[selected.provider]) {
625
+ throw new Error(`Unknown agent model provider: ${selected.provider}`);
626
+ }
627
+ return selected;
628
+ };
491
629
  return {
492
630
  keys: () => [...descriptors.keys()],
493
631
  descriptor,
494
632
  supports,
633
+ preflight,
495
634
  resolve(key, required = []) {
496
- const selected = descriptor(key);
497
- if (!supports(key, required)) {
498
- throw new Error(`Agent model ${key} does not satisfy required capabilities`);
499
- }
635
+ const selected = preflight(key, required);
500
636
  const provider = config.providers[selected.provider];
501
637
  if (!provider)
502
638
  throw new Error(`Unknown agent model provider: ${selected.provider}`);
@@ -505,9 +641,29 @@ function defineModelRegistry(config) {
505
641
  model: provider.create(selected.modelId),
506
642
  ...provider.normalizeUsage && { normalizeUsage: provider.normalizeUsage }
507
643
  };
644
+ },
645
+ snapshot(input) {
646
+ return AgentModelRegistrySnapshotSchema.parse({
647
+ schemaVersion: 1,
648
+ source: input.source,
649
+ observedAt: input.observedAt,
650
+ models: Object.fromEntries(descriptors.entries())
651
+ });
508
652
  }
509
653
  };
510
654
  }
655
+ function validateAgentModelSnapshot(input, policy) {
656
+ if (!Number.isSafeInteger(policy.maxAgeMs) || policy.maxAgeMs < 0) {
657
+ throw new TypeError("maxAgeMs must be a non-negative safe integer");
658
+ }
659
+ const snapshot = AgentModelRegistrySnapshotSchema.parse(input);
660
+ const now = policy.now?.() ?? new Date;
661
+ const age = now.getTime() - new Date(snapshot.observedAt).getTime();
662
+ if (age < 0 || age > policy.maxAgeMs) {
663
+ throw new Error(`Agent model snapshot from ${snapshot.source} is stale`);
664
+ }
665
+ return snapshot;
666
+ }
511
667
  // src/agent-runtime/observability.ts
512
668
  import { z as z3 } from "zod";
513
669
  var AgentRunEventSchema = z3.object({
@@ -538,13 +694,18 @@ function createAgentObservability(config) {
538
694
  ...config.onSinkError && { onSinkError: config.onSinkError },
539
695
  ...config.onDrop && { onDrop: config.onDrop }
540
696
  });
697
+ const emitted = new Set;
541
698
  return {
542
699
  rootTrace(parent) {
543
700
  const trace = parent ? childSpan(parent) : createTraceContext();
544
701
  return trace;
545
702
  },
546
703
  emit(rawEvent) {
547
- manager.submit(() => AgentRunEventSchema.parse(rawEvent));
704
+ const parsed = AgentRunEventSchema.parse(rawEvent);
705
+ if ((config.deduplicate ?? true) && emitted.has(parsed.eventId))
706
+ return;
707
+ emitted.add(parsed.eventId);
708
+ manager.submit(() => config.includeInternalCause ? parsed : AgentRunEventSchema.omit({ internalCause: true }).parse(parsed));
548
709
  },
549
710
  flush: () => manager.flush(),
550
711
  getStatus: () => manager.getStatus(),
@@ -557,6 +718,110 @@ var AgentTokenCountSchema = z4.object({
557
718
  value: z4.int().nonnegative().optional(),
558
719
  provenance: z4.enum(["measured", "estimated", "unavailable"])
559
720
  });
721
+ function completeTurn(messages) {
722
+ if (messages[0]?.role !== "user")
723
+ return false;
724
+ const assistant = messages.find((message) => message.role === "assistant");
725
+ if (assistant?.status !== "completed")
726
+ return false;
727
+ const calls = new Set(assistant.parts.filter((part) => part.type === "tool-call").map((part) => part.callId));
728
+ const results = new Set(assistant.parts.filter((part) => part.type === "tool-result").map((part) => part.callId));
729
+ return [...calls].every((callId) => results.has(callId)) && [...results].every((callId) => calls.has(callId));
730
+ }
731
+ function budgetTurns(messages) {
732
+ const turns = [];
733
+ let current = [];
734
+ const flush = () => {
735
+ if (current.length === 0)
736
+ return;
737
+ turns.push({ messages: current, complete: completeTurn(current), protectedSystem: false });
738
+ current = [];
739
+ };
740
+ for (const message of messages) {
741
+ if (message.role === "system" || message.role === "summary") {
742
+ flush();
743
+ turns.push({ messages: [message], complete: true, protectedSystem: true });
744
+ continue;
745
+ }
746
+ if (message.role === "user")
747
+ flush();
748
+ current.push(message);
749
+ }
750
+ flush();
751
+ return turns;
752
+ }
753
+ async function selectAgentHistory(options) {
754
+ if (!Number.isSafeInteger(options.availableTokens) || options.availableTokens < 0) {
755
+ throw new TypeError("availableTokens must be a non-negative safe integer");
756
+ }
757
+ const keepRecentTurns = options.keepRecentTurns ?? 1;
758
+ if (!Number.isSafeInteger(keepRecentTurns) || keepRecentTurns < 0) {
759
+ throw new TypeError("keepRecentTurns must be a non-negative safe integer");
760
+ }
761
+ const counts = new Map;
762
+ let total = 0;
763
+ let estimated = false;
764
+ for (const message of options.messages) {
765
+ const count = AgentTokenCountSchema.parse(await options.estimateMessage(message));
766
+ counts.set(message.id, count);
767
+ const value = knownValue(count);
768
+ if (value === undefined) {
769
+ return {
770
+ messages: [...options.messages],
771
+ decisions: options.messages.map((candidate) => ({
772
+ messageId: candidate.id,
773
+ action: "kept",
774
+ reason: "token-count-unavailable",
775
+ tokens: counts.get(candidate.id) ?? { provenance: "unavailable" }
776
+ })),
777
+ totalTokens: { provenance: "unavailable" },
778
+ outcome: "unavailable"
779
+ };
780
+ }
781
+ total += value;
782
+ if (count.provenance === "estimated")
783
+ estimated = true;
784
+ }
785
+ const turns = budgetTurns(options.messages);
786
+ const completeIndexes = turns.map((turn, index) => ({ turn, index })).filter(({ turn }) => turn.complete && !turn.protectedSystem).map(({ index }) => index);
787
+ const protectedRecent = new Set(completeIndexes.slice(-keepRecentTurns));
788
+ const removed = new Set;
789
+ for (let index = 0;index < turns.length && total > options.availableTokens; index += 1) {
790
+ const turn = turns[index];
791
+ if (!turn || turn.protectedSystem || !turn.complete || protectedRecent.has(index))
792
+ continue;
793
+ for (const message of turn.messages) {
794
+ removed.add(message.id);
795
+ total -= knownValue(counts.get(message.id) ?? { provenance: "unavailable" }) ?? 0;
796
+ }
797
+ }
798
+ const messages = options.messages.filter((message) => !removed.has(message.id));
799
+ const decisions = options.messages.map((message) => {
800
+ const turnIndex = turns.findIndex((turn2) => turn2.messages.some((item) => item.id === message.id));
801
+ const turn = turns[turnIndex];
802
+ let reason = "within-budget";
803
+ if (removed.has(message.id))
804
+ reason = "oldest-eligible-turn";
805
+ else if (turn?.protectedSystem)
806
+ reason = "protected-system";
807
+ else if (turn && !turn.complete)
808
+ reason = "protected-incomplete-turn";
809
+ else if (protectedRecent.has(turnIndex))
810
+ reason = "protected-recent-turn";
811
+ return {
812
+ messageId: message.id,
813
+ action: removed.has(message.id) ? "removed" : "kept",
814
+ reason,
815
+ tokens: counts.get(message.id) ?? { provenance: "unavailable" }
816
+ };
817
+ });
818
+ return {
819
+ messages,
820
+ decisions,
821
+ totalTokens: { value: total, provenance: estimated ? "estimated" : "measured" },
822
+ outcome: total > options.availableTokens ? "oversized" : removed.size > 0 ? "truncated" : "fits"
823
+ };
824
+ }
560
825
  function knownValue(value) {
561
826
  return value.provenance === "unavailable" ? undefined : value.value;
562
827
  }
@@ -795,7 +1060,11 @@ function createAgentRuntime(config) {
795
1060
  const publish = async (event) => {
796
1061
  try {
797
1062
  await config.publish?.(event);
798
- } catch {}
1063
+ } catch (error) {
1064
+ try {
1065
+ await config.onPublishError?.({ event, error });
1066
+ } catch {}
1067
+ }
799
1068
  };
800
1069
  const executeRun = async (input) => {
801
1070
  const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
@@ -809,7 +1078,7 @@ function createAgentRuntime(config) {
809
1078
  let run = findRun(acquired.runs, input.acceptedRun.id);
810
1079
  await publish({
811
1080
  type: "run-state",
812
- eventId: generateId(),
1081
+ eventId: agentDurableEventId("run-state", run.id, acquired.version),
813
1082
  conversationId: run.conversationId,
814
1083
  runId: run.id,
815
1084
  snapshotVersion: acquired.version,
@@ -847,6 +1116,7 @@ function createAgentRuntime(config) {
847
1116
  runId: run.id,
848
1117
  expectedRevision: run.revision,
849
1118
  ownerId: runtimeEpoch,
1119
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
850
1120
  assistant
851
1121
  }), "assistant draft");
852
1122
  run = findRun(snapshot.runs, run.id);
@@ -895,6 +1165,7 @@ function createAgentRuntime(config) {
895
1165
  runId: run.id,
896
1166
  expectedRevision: run.revision,
897
1167
  ownerId: runtimeEpoch,
1168
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
898
1169
  assistant
899
1170
  }), "assistant checkpoint");
900
1171
  run = findRun(snapshot.runs, run.id);
@@ -906,7 +1177,7 @@ function createAgentRuntime(config) {
906
1177
  };
907
1178
  await publish({
908
1179
  type: "assistant-checkpoint",
909
- eventId: generateId(),
1180
+ eventId: agentDurableEventId("assistant-checkpoint", run.id, snapshot.version),
910
1181
  conversationId: run.conversationId,
911
1182
  runId: run.id,
912
1183
  snapshotVersion: snapshot.version,
@@ -932,6 +1203,8 @@ function createAgentRuntime(config) {
932
1203
  const currentRun = current.runs.find((candidate) => candidate.id === run.id);
933
1204
  if (!currentRun || currentRun.ownerId !== runtimeEpoch)
934
1205
  return "stale_run";
1206
+ if (currentRun.fencingToken !== run.fencingToken)
1207
+ return "stale_run";
935
1208
  if (currentRun.state === "interrupt_requested")
936
1209
  return "run_interrupted";
937
1210
  if (currentRun.state !== "running")
@@ -940,7 +1213,10 @@ function createAgentRuntime(config) {
940
1213
  };
941
1214
  const toolFenceLifecycle = createAgentToolFenceLifecycle({
942
1215
  runId: run.id,
943
- assertCurrent
1216
+ assertCurrent,
1217
+ context: () => ({
1218
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken }
1219
+ })
944
1220
  });
945
1221
  const runtimeContext = {
946
1222
  context: input.context,
@@ -1276,6 +1552,7 @@ function createAgentRuntime(config) {
1276
1552
  runId: run.id,
1277
1553
  expectedRevision: run.revision,
1278
1554
  ownerId: runtimeEpoch,
1555
+ ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1279
1556
  assistant,
1280
1557
  reason: terminalReason,
1281
1558
  ...terminalPolicyName && { policyName: terminalPolicyName }
@@ -1289,7 +1566,7 @@ function createAgentRuntime(config) {
1289
1566
  };
1290
1567
  config.observe?.emit({
1291
1568
  schemaVersion: 1,
1292
- eventId: generateId(),
1569
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1293
1570
  type: "run-terminal",
1294
1571
  conversationId: run.conversationId,
1295
1572
  runId: run.id,
@@ -1307,7 +1584,7 @@ function createAgentRuntime(config) {
1307
1584
  });
1308
1585
  await publish({
1309
1586
  type: "terminal",
1310
- eventId: generateId(),
1587
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1311
1588
  conversationId: run.conversationId,
1312
1589
  runId: run.id,
1313
1590
  snapshotVersion: snapshot.version,
@@ -1433,6 +1710,10 @@ function createAgentRuntime(config) {
1433
1710
  await previousAcceptance.catch(() => {
1434
1711
  return;
1435
1712
  });
1713
+ await config.models.preflight?.({
1714
+ context,
1715
+ conversationId: input.conversationId
1716
+ });
1436
1717
  const acceptance = await config.store.acceptInputAndAssignRun({
1437
1718
  idempotencyKey: input.idempotencyKey,
1438
1719
  input: userMessage2,
@@ -1443,7 +1724,7 @@ function createAgentRuntime(config) {
1443
1724
  });
1444
1725
  const acceptedSnapshot = appliedSnapshot(acceptance, "input acceptance");
1445
1726
  const assignedRunId = acceptance.outcome === "duplicate" ? acceptance.runId : reservation?.admission.runId ?? runId;
1446
- const acceptedRun = findRun(acceptedSnapshot.runs, assignedRunId);
1727
+ const acceptedRun = acceptance.outcome === "duplicate" ? acceptance.run : findRun(acceptedSnapshot.runs, assignedRunId);
1447
1728
  const actualInputMessageId = acceptance.outcome === "duplicate" ? acceptance.inputMessageId : userMessage2.id;
1448
1729
  const acceptedInput = acceptance.outcome === "duplicate" ? acceptance.input : acceptedSnapshot.messages.find((candidate) => candidate.id === actualInputMessageId);
1449
1730
  if (!acceptedInput) {
@@ -1458,7 +1739,7 @@ function createAgentRuntime(config) {
1458
1739
  createdAt: acceptedRun.createdAt,
1459
1740
  updatedAt: acceptedRun.updatedAt
1460
1741
  });
1461
- const acceptedAssistant = acceptance.outcome === "duplicate" ? acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId) ?? assistantPlaceholder : assistantPlaceholder;
1742
+ const acceptedAssistant = acceptance.outcome === "duplicate" ? acceptance.assistant ?? assistantPlaceholder : assistantPlaceholder;
1462
1743
  const admission = {
1463
1744
  inputMessageId: acceptedInput.id,
1464
1745
  runId: acceptedRun.id,
@@ -1471,7 +1752,7 @@ function createAgentRuntime(config) {
1471
1752
  outerAdmission.resolve(admission);
1472
1753
  await publish({
1473
1754
  type: "admission",
1474
- eventId: generateId(),
1755
+ eventId: agentDurableEventId("admission", acceptedRun.id, acceptedSnapshot.version),
1475
1756
  conversationId: acceptedRun.conversationId,
1476
1757
  runId: acceptedRun.id,
1477
1758
  snapshotVersion: acceptedSnapshot.version,
@@ -1482,7 +1763,7 @@ function createAgentRuntime(config) {
1482
1763
  });
1483
1764
  await publish({
1484
1765
  type: "run-state",
1485
- eventId: generateId(),
1766
+ eventId: agentDurableEventId("run-state", acceptedRun.id, acceptedSnapshot.version),
1486
1767
  conversationId: acceptedRun.conversationId,
1487
1768
  runId: acceptedRun.id,
1488
1769
  snapshotVersion: acceptedSnapshot.version,
@@ -1500,9 +1781,9 @@ function createAgentRuntime(config) {
1500
1781
  }
1501
1782
  return;
1502
1783
  }
1503
- const message = acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId);
1784
+ const message = acceptance.assistant;
1504
1785
  if (!message) {
1505
- const error = new Error("Duplicate terminal run has no assistant message");
1786
+ const error = new Error("Duplicate terminal admission has no retained canonical assistant");
1506
1787
  outerResult.reject(error);
1507
1788
  if (reservation?.shouldSchedule) {
1508
1789
  reservation.admission.completion.reject(error);
@@ -1580,7 +1861,7 @@ function createAgentRuntime(config) {
1580
1861
  const interruptedRun = findRun(requested.snapshot.runs, input.runId);
1581
1862
  await publish({
1582
1863
  type: "run-state",
1583
- eventId: generateId(),
1864
+ eventId: agentDurableEventId("run-state", interruptedRun.id, requested.snapshot.version),
1584
1865
  conversationId: interruptedRun.conversationId,
1585
1866
  runId: interruptedRun.id,
1586
1867
  snapshotVersion: requested.snapshot.version,
@@ -1715,6 +1996,8 @@ var AgentStoreDuplicateSchema = z6.object({
1715
1996
  inputMessageId: AgentRecordIdSchema,
1716
1997
  runId: AgentRecordIdSchema,
1717
1998
  assistantMessageId: AgentRecordIdSchema,
1999
+ run: AgentRunSchema,
2000
+ assistant: AgentMessageSchema.optional(),
1718
2001
  snapshot: AgentSnapshotSchema
1719
2002
  });
1720
2003
  var AgentStoreMutationResultSchema = z6.discriminatedUnion("outcome", [
@@ -1741,6 +2024,7 @@ var CheckpointRunAssistantSchema = z6.object({
1741
2024
  runId: AgentRecordIdSchema,
1742
2025
  expectedRevision: AgentRecordVersionSchema,
1743
2026
  ownerId: z6.string().min(1),
2027
+ fencingToken: AgentRecordVersionSchema.optional(),
1744
2028
  assistant: AgentMessageSchema
1745
2029
  });
1746
2030
  var CommitRunTerminalSchema = z6.object({
@@ -1748,6 +2032,7 @@ var CommitRunTerminalSchema = z6.object({
1748
2032
  runId: AgentRecordIdSchema,
1749
2033
  expectedRevision: AgentRecordVersionSchema,
1750
2034
  ownerId: z6.string().min(1),
2035
+ fencingToken: AgentRecordVersionSchema.optional(),
1751
2036
  assistant: AgentMessageSchema,
1752
2037
  reason: AgentTerminalReasonSchema,
1753
2038
  policyName: z6.string().min(1).optional()
@@ -1772,18 +2057,23 @@ var ReplaceCompactedRangeSchema = z6.object({
1772
2057
  });
1773
2058
  // src/agent-runtime/store-driver.ts
1774
2059
  import { z as z7 } from "zod";
1775
- var AgentAdmissionIdentitySchema = z7.object({
1776
- idempotencyKey: z7.string().min(1),
1777
- inputMessageId: AgentRecordIdSchema,
1778
- runId: AgentRecordIdSchema,
1779
- assistantMessageId: AgentRecordIdSchema
2060
+ var AgentRuntimeHeadSchema = z7.object({
2061
+ schemaVersion: z7.literal(1),
2062
+ conversationId: AgentRecordIdSchema,
2063
+ version: AgentRecordVersionSchema
2064
+ });
2065
+ var AgentStoredRunSchema = z7.object({
2066
+ schemaVersion: z7.literal(1),
2067
+ run: AgentRunSchema,
2068
+ terminalAssistant: AgentMessageSchema.optional()
1780
2069
  });
1781
- var AgentStoredStateSchema = z7.object({
2070
+ var AgentAdmissionReceiptSchema = z7.object({
1782
2071
  schemaVersion: z7.literal(1),
1783
2072
  conversationId: AgentRecordIdSchema,
1784
- version: AgentRecordVersionSchema,
1785
- runs: z7.array(AgentRunSchema),
1786
- admissions: z7.array(AgentAdmissionIdentitySchema)
2073
+ idempotencyKey: z7.string().min(1),
2074
+ input: AgentMessageSchema,
2075
+ runId: AgentRecordIdSchema,
2076
+ assistantMessageId: AgentRecordIdSchema
1787
2077
  });
1788
2078
  var AgentHistoryMutationSchema = z7.discriminatedUnion("type", [
1789
2079
  z7.object({ type: z7.literal("admit"), input: AgentMessageSchema }),
@@ -1809,40 +2099,40 @@ var AgentRecoverableScanInputSchema = z7.object({
1809
2099
  cursor: z7.string().min(1).optional(),
1810
2100
  limit: z7.number().int().min(1).max(1000)
1811
2101
  });
1812
- function emptyState(conversationId) {
1813
- return AgentStoredStateSchema.parse({
2102
+ function emptyHead(conversationId) {
2103
+ return AgentRuntimeHeadSchema.parse({
1814
2104
  schemaVersion: 1,
1815
2105
  conversationId,
1816
- version: 0,
1817
- runs: [],
1818
- admissions: []
2106
+ version: 0
1819
2107
  });
1820
2108
  }
1821
- function snapshotOf(state, messages) {
1822
- validateAggregate(state, messages);
2109
+ function snapshotOf(head, messages, records) {
2110
+ validateSnapshot(head, messages, records);
1823
2111
  return AgentSnapshotSchema.parse({
1824
2112
  schemaVersion: 1,
1825
- conversationId: state.conversationId,
1826
- version: state.version,
2113
+ conversationId: head.conversationId,
2114
+ version: head.version,
1827
2115
  messages,
1828
- runs: state.runs
2116
+ runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
1829
2117
  });
1830
2118
  }
1831
- function validateAggregate(state, messages) {
2119
+ function validateSnapshot(head, messages, records) {
1832
2120
  const runIds = new Set;
1833
2121
  const assistantIds = new Set;
1834
2122
  const messageIds = new Set;
1835
- const idempotencyKeys = new Set;
1836
- const admittedInputIds = new Set;
1837
- for (const run of state.runs) {
1838
- if (run.conversationId !== state.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
1839
- throw new TypeError("Stored agent state contains inconsistent run identities");
2123
+ for (const record of records) {
2124
+ const run = record.run;
2125
+ if (run.conversationId !== head.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
2126
+ throw new TypeError("Stored agent runs contain inconsistent identities");
2127
+ }
2128
+ if (record.terminalAssistant && (record.terminalAssistant.id !== run.assistantMessageId || record.terminalAssistant.conversationId !== run.conversationId || record.terminalAssistant.runId !== run.id || record.terminalAssistant.role !== "assistant" || run.terminalReason === undefined)) {
2129
+ throw new TypeError("Retained terminal assistant does not match its run");
1840
2130
  }
1841
2131
  runIds.add(run.id);
1842
2132
  assistantIds.add(run.assistantMessageId);
1843
2133
  }
1844
2134
  for (const message of messages) {
1845
- if (message.conversationId !== state.conversationId || messageIds.has(message.id)) {
2135
+ if (message.conversationId !== head.conversationId || messageIds.has(message.id)) {
1846
2136
  throw new TypeError("Stored agent history contains inconsistent message identities");
1847
2137
  }
1848
2138
  messageIds.add(message.id);
@@ -1850,23 +2140,12 @@ function validateAggregate(state, messages) {
1850
2140
  throw new TypeError("Stored history occupies a reserved assistant identity");
1851
2141
  }
1852
2142
  if (message.runId !== undefined) {
1853
- const run = state.runs.find((candidate) => candidate.id === message.runId);
2143
+ const run = records.find((candidate) => candidate.run.id === message.runId)?.run;
1854
2144
  if (!run || message.role !== "assistant" || run.assistantMessageId !== message.id) {
1855
2145
  throw new TypeError("Stored assistant history does not match its reserved run identity");
1856
2146
  }
1857
2147
  }
1858
2148
  }
1859
- for (const admission of state.admissions) {
1860
- const run = state.runs.find((candidate) => candidate.id === admission.runId);
1861
- if (idempotencyKeys.has(admission.idempotencyKey) || admittedInputIds.has(admission.inputMessageId) || !run || run.assistantMessageId !== admission.assistantMessageId || !run.inputMessageIds.includes(admission.inputMessageId)) {
1862
- throw new TypeError("Stored admission identity is inconsistent with its assigned run");
1863
- }
1864
- idempotencyKeys.add(admission.idempotencyKey);
1865
- admittedInputIds.add(admission.inputMessageId);
1866
- }
1867
- }
1868
- function recoverableDescriptors(state) {
1869
- return state.runs.filter((run) => ["queued", "running", "interrupt_requested"].includes(run.state)).map((run) => ({ conversationId: state.conversationId, run }));
1870
2149
  }
1871
2150
  var RecoverableCursorSchema = z7.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
1872
2151
  function recoverableCursor(input) {
@@ -1904,7 +2183,7 @@ function terminalMessageStatus(reason) {
1904
2183
  }
1905
2184
  return "failed";
1906
2185
  }
1907
- function applied(current, admissions, input, historyMutation) {
2186
+ function applied(current, input, effects) {
1908
2187
  return {
1909
2188
  outcome: "applied",
1910
2189
  snapshot: AgentSnapshotSchema.parse({
@@ -1913,27 +2192,14 @@ function applied(current, admissions, input, historyMutation) {
1913
2192
  runs: input.runs ?? current.runs,
1914
2193
  messages: input.messages ?? current.messages
1915
2194
  }),
1916
- admissions,
1917
- ...historyMutation && { historyMutation }
2195
+ ...effects?.runRecord && { runRecord: effects.runRecord },
2196
+ ...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
2197
+ ...effects?.historyMutation && { historyMutation: effects.historyMutation }
1918
2198
  };
1919
2199
  }
1920
- function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2200
+ function reduceStore(current, operation) {
1921
2201
  if (operation.type === "accept") {
1922
2202
  const input = operation.input;
1923
- const duplicate = currentAdmissions.find((candidate) => candidate.idempotencyKey === input.idempotencyKey);
1924
- if (duplicate) {
1925
- if (!duplicateInput) {
1926
- throw new Error("Duplicate admission input is unavailable from canonical history");
1927
- }
1928
- return {
1929
- outcome: "duplicate",
1930
- input: duplicateInput,
1931
- inputMessageId: duplicate.inputMessageId,
1932
- runId: duplicate.runId,
1933
- assistantMessageId: duplicate.assistantMessageId,
1934
- snapshot: current
1935
- };
1936
- }
1937
2203
  if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
1938
2204
  return conflict(current.version);
1939
2205
  }
@@ -1950,16 +2216,25 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
1950
2216
  revision: coalescedRun.revision + 1,
1951
2217
  updatedAt: new Date().toISOString()
1952
2218
  }) : input.run;
1953
- const admission = AgentAdmissionIdentitySchema.parse({
2219
+ const admissionReceipt = AgentAdmissionReceiptSchema.parse({
2220
+ schemaVersion: 1,
2221
+ conversationId: input.input.conversationId,
1954
2222
  idempotencyKey: input.idempotencyKey,
1955
- inputMessageId: input.input.id,
2223
+ input: input.input,
1956
2224
  runId: assignedRun.id,
1957
2225
  assistantMessageId: assignedRun.assistantMessageId
1958
2226
  });
1959
- return applied(current, [...currentAdmissions, admission], {
2227
+ return applied(current, {
1960
2228
  messages: [...current.messages, input.input],
1961
2229
  runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
1962
- }, { type: "admit", input: input.input });
2230
+ }, {
2231
+ runRecord: AgentStoredRunSchema.parse({
2232
+ schemaVersion: 1,
2233
+ run: assignedRun
2234
+ }),
2235
+ admissionReceipt,
2236
+ historyMutation: { type: "admit", input: input.input }
2237
+ });
1963
2238
  }
1964
2239
  const conversationId = operation.input.conversationId;
1965
2240
  const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
@@ -1975,16 +2250,17 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
1975
2250
  ...run,
1976
2251
  state: "running",
1977
2252
  ownerId: operation.input.ownerId,
2253
+ fencingToken: (run.fencingToken ?? 0) + 1,
1978
2254
  revision: run.revision + 1,
1979
2255
  updatedAt: new Date().toISOString()
1980
2256
  });
1981
- return applied(current, currentAdmissions, {
1982
- runs: replaceRun(current.runs, next)
2257
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2258
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
1983
2259
  });
1984
2260
  }
1985
2261
  if (operation.type === "checkpoint" && run) {
1986
2262
  const input = operation.input;
1987
- if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
2263
+ if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
1988
2264
  return conflict(run.revision);
1989
2265
  }
1990
2266
  const next = AgentRunSchema.parse({
@@ -1992,10 +2268,13 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
1992
2268
  revision: run.revision + 1,
1993
2269
  updatedAt: new Date().toISOString()
1994
2270
  });
1995
- return applied(current, currentAdmissions, {
2271
+ return applied(current, {
1996
2272
  runs: replaceRun(current.runs, next),
1997
2273
  messages: replaceMessage(current.messages, input.assistant)
1998
- }, { type: "upsert-assistant", message: input.assistant });
2274
+ }, {
2275
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next }),
2276
+ historyMutation: { type: "upsert-assistant", message: input.assistant }
2277
+ });
1999
2278
  }
2000
2279
  if (operation.type === "interrupt" && run) {
2001
2280
  if (run.revision !== operation.input.expectedRevision || run.state !== "running") {
@@ -2007,8 +2286,8 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2007
2286
  revision: run.revision + 1,
2008
2287
  updatedAt: new Date().toISOString()
2009
2288
  });
2010
- return applied(current, currentAdmissions, {
2011
- runs: replaceRun(current.runs, next)
2289
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2290
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
2012
2291
  });
2013
2292
  }
2014
2293
  if (operation.type === "recover" && run) {
@@ -2046,18 +2325,25 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2046
2325
  status: "failed",
2047
2326
  updatedAt: new Date().toISOString()
2048
2327
  });
2049
- return applied(current, currentAdmissions, {
2328
+ return applied(current, {
2050
2329
  runs: replaceRun(current.runs, next),
2051
2330
  messages: replaceMessage(current.messages, assistant)
2052
- }, { type: "upsert-assistant", message: assistant });
2331
+ }, {
2332
+ runRecord: AgentStoredRunSchema.parse({
2333
+ schemaVersion: 1,
2334
+ run: next,
2335
+ terminalAssistant: assistant
2336
+ }),
2337
+ historyMutation: { type: "upsert-assistant", message: assistant }
2338
+ });
2053
2339
  }
2054
- return applied(current, currentAdmissions, {
2055
- runs: replaceRun(current.runs, next)
2340
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2341
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
2056
2342
  });
2057
2343
  }
2058
2344
  if (operation.type === "terminal" && run) {
2059
2345
  const input = operation.input;
2060
- if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus(input.reason)) {
2346
+ if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus(input.reason)) {
2061
2347
  return conflict(run.revision);
2062
2348
  }
2063
2349
  const next = AgentRunSchema.parse({
@@ -2068,10 +2354,17 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2068
2354
  revision: run.revision + 1,
2069
2355
  updatedAt: new Date().toISOString()
2070
2356
  });
2071
- return applied(current, currentAdmissions, {
2357
+ return applied(current, {
2072
2358
  runs: replaceRun(current.runs, next),
2073
2359
  messages: replaceMessage(current.messages, input.assistant)
2074
- }, { type: "upsert-assistant", message: input.assistant });
2360
+ }, {
2361
+ runRecord: AgentStoredRunSchema.parse({
2362
+ schemaVersion: 1,
2363
+ run: next,
2364
+ terminalAssistant: input.assistant
2365
+ }),
2366
+ historyMutation: { type: "upsert-assistant", message: input.assistant }
2367
+ });
2075
2368
  }
2076
2369
  if (operation.type === "compact") {
2077
2370
  const input = operation.input;
@@ -2093,10 +2386,12 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2093
2386
  input.summary,
2094
2387
  ...current.messages.slice(first + positions.length)
2095
2388
  ];
2096
- return applied(current, currentAdmissions, { messages }, {
2097
- type: "replace-compacted-range",
2098
- replacedMessageIds: input.replacedMessageIds,
2099
- summary: input.summary
2389
+ return applied(current, { messages }, {
2390
+ historyMutation: {
2391
+ type: "replace-compacted-range",
2392
+ replacedMessageIds: input.replacedMessageIds,
2393
+ summary: input.summary
2394
+ }
2100
2395
  });
2101
2396
  }
2102
2397
  return { outcome: "not_found" };
@@ -2104,48 +2399,130 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2104
2399
  function operationConversationId(operation) {
2105
2400
  return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
2106
2401
  }
2402
+ function mergeRunRecords(...groups) {
2403
+ const records = new Map;
2404
+ for (const group of groups) {
2405
+ for (const rawRecord of group) {
2406
+ const record = AgentStoredRunSchema.parse(rawRecord);
2407
+ const previous = records.get(record.run.id);
2408
+ if (previous && previous.run.assistantMessageId !== record.run.assistantMessageId) {
2409
+ throw new TypeError("Stored agent run identity changed across normalized records");
2410
+ }
2411
+ records.set(record.run.id, record);
2412
+ }
2413
+ }
2414
+ return [...records.values()];
2415
+ }
2416
+ function referencedRunIds(messages) {
2417
+ return [...new Set(messages.flatMap((message) => message.runId ? [message.runId] : []))];
2418
+ }
2419
+ function validateAdmissionReceipt(receipt, record, conversationId) {
2420
+ const input = receipt.input;
2421
+ const run = record.run;
2422
+ if (receipt.conversationId !== conversationId || input.conversationId !== conversationId || input.role !== "user" || input.status !== "committed" || input.runId !== undefined || receipt.runId !== run.id || receipt.assistantMessageId !== run.assistantMessageId || !run.inputMessageIds.includes(input.id)) {
2423
+ throw new TypeError("Admission receipt does not match its canonical run assignment");
2424
+ }
2425
+ }
2107
2426
  function createAgentRuntimeStore(driver) {
2108
2427
  const loadSnapshot = (conversationId) => driver.transaction(async (transaction) => {
2109
- const [stored, messages] = await Promise.all([
2110
- driver.state.load(transaction, conversationId),
2111
- driver.history.load(transaction, conversationId)
2428
+ const [stored, messages, activeRecords] = await Promise.all([
2429
+ driver.head.load(transaction, conversationId),
2430
+ driver.history.load(transaction, conversationId),
2431
+ driver.runs.listActive(transaction, conversationId)
2112
2432
  ]);
2113
- return snapshotOf(stored ?? emptyState(conversationId), messages);
2433
+ const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
2434
+ const referencedRecords = await driver.runs.loadMany(transaction, {
2435
+ conversationId,
2436
+ runIds: referencedRunIds(messages)
2437
+ });
2438
+ return snapshotOf(head, messages, mergeRunRecords(activeRecords, referencedRecords));
2114
2439
  });
2115
2440
  const mutate = (operation) => driver.transaction(async (transaction) => {
2116
2441
  const conversationId = operationConversationId(operation);
2117
- const [stored, messages] = await Promise.all([
2118
- driver.state.load(transaction, conversationId),
2119
- driver.history.load(transaction, conversationId)
2442
+ const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.input.runId;
2443
+ const [stored, messages, activeRecords, operationRecord, duplicateReceipt] = await Promise.all([
2444
+ driver.head.load(transaction, conversationId),
2445
+ driver.history.load(transaction, conversationId),
2446
+ driver.runs.listActive(transaction, conversationId),
2447
+ operationRunId ? driver.runs.load(transaction, { conversationId, runId: operationRunId }) : undefined,
2448
+ operation.type === "accept" ? driver.admissions.load(transaction, {
2449
+ conversationId,
2450
+ idempotencyKey: operation.input.idempotencyKey
2451
+ }) : undefined
2120
2452
  ]);
2121
- const state = AgentStoredStateSchema.parse(stored ?? emptyState(conversationId));
2122
- const current = snapshotOf(state, messages);
2123
- const duplicateIdentity = operation.type === "accept" ? state.admissions.find((candidate) => candidate.idempotencyKey === operation.input.idempotencyKey) : undefined;
2124
- const duplicateInput = duplicateIdentity ? await driver.history.loadById(transaction, {
2453
+ const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
2454
+ const referencedRecords = await driver.runs.loadMany(transaction, {
2125
2455
  conversationId,
2126
- messageId: duplicateIdentity.inputMessageId
2127
- }) : undefined;
2128
- if (duplicateIdentity && duplicateInput && (duplicateInput.id !== duplicateIdentity.inputMessageId || duplicateInput.conversationId !== conversationId || duplicateInput.role !== "user" || duplicateInput.status !== "committed" || duplicateInput.runId !== undefined)) {
2129
- throw new TypeError("Canonical duplicate input does not match its admission identity");
2456
+ runIds: referencedRunIds(messages)
2457
+ });
2458
+ const records = mergeRunRecords(activeRecords, referencedRecords, operationRecord ? [operationRecord] : []);
2459
+ const current = snapshotOf(head, messages, records);
2460
+ if (duplicateReceipt) {
2461
+ const duplicateRecord = await driver.runs.load(transaction, {
2462
+ conversationId,
2463
+ runId: duplicateReceipt.runId
2464
+ });
2465
+ if (!duplicateRecord) {
2466
+ throw new TypeError("Admission receipt points to a missing canonical run");
2467
+ }
2468
+ validateAdmissionReceipt(duplicateReceipt, duplicateRecord, conversationId);
2469
+ return {
2470
+ outcome: "duplicate",
2471
+ input: duplicateReceipt.input,
2472
+ inputMessageId: duplicateReceipt.input.id,
2473
+ runId: duplicateReceipt.runId,
2474
+ assistantMessageId: duplicateReceipt.assistantMessageId,
2475
+ run: duplicateRecord.run,
2476
+ ...duplicateRecord.terminalAssistant && {
2477
+ assistant: duplicateRecord.terminalAssistant
2478
+ },
2479
+ snapshot: snapshotOf(head, messages, mergeRunRecords(records, [duplicateRecord]))
2480
+ };
2130
2481
  }
2131
- const reduced = reduceStore(current, state.admissions, operation, duplicateInput);
2482
+ if (operation.type === "accept") {
2483
+ const inputCollision = await driver.admissions.loadByInputMessageId(transaction, {
2484
+ conversationId,
2485
+ inputMessageId: operation.input.input.id
2486
+ });
2487
+ if (inputCollision) {
2488
+ throw new TypeError("Input message identity is already assigned to an admission");
2489
+ }
2490
+ if (!operation.input.coalesceIntoRunId) {
2491
+ const [runCollision, assistantCollision] = await Promise.all([
2492
+ driver.runs.load(transaction, {
2493
+ conversationId,
2494
+ runId: operation.input.run.id
2495
+ }),
2496
+ driver.runs.loadByAssistantMessageId(transaction, {
2497
+ conversationId,
2498
+ assistantMessageId: operation.input.run.assistantMessageId
2499
+ })
2500
+ ]);
2501
+ if (runCollision || assistantCollision) {
2502
+ throw new TypeError("Queued run identities are already reserved");
2503
+ }
2504
+ }
2505
+ }
2506
+ const reduced = reduceStore(current, operation);
2132
2507
  if (reduced.outcome !== "applied")
2133
2508
  return reduced;
2134
- const nextState = AgentStoredStateSchema.parse({
2509
+ const nextHead = AgentRuntimeHeadSchema.parse({
2135
2510
  schemaVersion: 1,
2136
2511
  conversationId,
2137
- version: reduced.snapshot.version,
2138
- runs: reduced.snapshot.runs,
2139
- admissions: reduced.admissions
2512
+ version: reduced.snapshot.version
2140
2513
  });
2141
- const outcome = await driver.state.compareAndSwap(transaction, {
2514
+ const outcome = await driver.head.compareAndSwap(transaction, {
2142
2515
  conversationId,
2143
2516
  expectedVersion: current.version,
2144
- next: nextState,
2145
- recoverable: recoverableDescriptors(nextState)
2517
+ next: nextHead
2146
2518
  });
2147
2519
  if (outcome.outcome === "conflict")
2148
2520
  return conflict(outcome.actualVersion);
2521
+ if (reduced.runRecord)
2522
+ await driver.runs.save(transaction, reduced.runRecord);
2523
+ if (reduced.admissionReceipt) {
2524
+ await driver.admissions.create(transaction, reduced.admissionReceipt);
2525
+ }
2149
2526
  if (reduced.historyMutation) {
2150
2527
  await driver.history.apply(transaction, reduced.historyMutation);
2151
2528
  }
@@ -2197,10 +2574,16 @@ function createAgentRuntimeStore(driver) {
2197
2574
  }
2198
2575
  };
2199
2576
  }
2200
- function cloneStateMap(source) {
2577
+ function cloneHeadMap(source) {
2201
2578
  return new Map([...source].map(([key, value]) => [
2202
2579
  key,
2203
- AgentStoredStateSchema.parse(structuredClone(value))
2580
+ AgentRuntimeHeadSchema.parse(structuredClone(value))
2581
+ ]));
2582
+ }
2583
+ function cloneNestedMap(source, clone) {
2584
+ return new Map([...source].map(([outerKey, values]) => [
2585
+ outerKey,
2586
+ new Map([...values].map(([innerKey, value]) => [innerKey, clone(value)]))
2204
2587
  ]));
2205
2588
  }
2206
2589
  function cloneHistoryMap(source) {
@@ -2210,9 +2593,10 @@ function cloneHistoryMap(source) {
2210
2593
  ]));
2211
2594
  }
2212
2595
  function createMemoryAgentRuntimeStore() {
2213
- let states = new Map;
2596
+ let heads = new Map;
2597
+ let runs = new Map;
2598
+ let admissions = new Map;
2214
2599
  let histories = new Map;
2215
- let archivedMessages = new Map;
2216
2600
  let transactionTail = Promise.resolve();
2217
2601
  const driver = {
2218
2602
  async transaction(work) {
@@ -2225,50 +2609,91 @@ function createMemoryAgentRuntimeStore() {
2225
2609
  return;
2226
2610
  });
2227
2611
  const transaction = {
2228
- states: cloneStateMap(states),
2229
- histories: cloneHistoryMap(histories),
2230
- archivedMessages: new Map([...archivedMessages].map(([conversationId, messages]) => [
2231
- conversationId,
2232
- new Map([...messages].map(([messageId, message]) => [
2233
- messageId,
2234
- AgentMessageSchema.parse(structuredClone(message))
2235
- ]))
2236
- ]))
2612
+ heads: cloneHeadMap(heads),
2613
+ runs: cloneNestedMap(runs, (record) => AgentStoredRunSchema.parse(structuredClone(record))),
2614
+ admissions: cloneNestedMap(admissions, (receipt) => AgentAdmissionReceiptSchema.parse(structuredClone(receipt))),
2615
+ histories: cloneHistoryMap(histories)
2237
2616
  };
2238
2617
  try {
2239
2618
  const result = await work(transaction);
2240
- states = transaction.states;
2619
+ heads = transaction.heads;
2620
+ runs = transaction.runs;
2621
+ admissions = transaction.admissions;
2241
2622
  histories = transaction.histories;
2242
- archivedMessages = transaction.archivedMessages;
2243
2623
  return result;
2244
2624
  } finally {
2245
2625
  release.resolve();
2246
2626
  }
2247
2627
  },
2248
- state: {
2628
+ head: {
2249
2629
  async load(transaction, conversationId) {
2250
- const state = transaction.states.get(conversationId);
2251
- return state ? AgentStoredStateSchema.parse(structuredClone(state)) : undefined;
2630
+ const head = transaction.heads.get(conversationId);
2631
+ return head ? AgentRuntimeHeadSchema.parse(structuredClone(head)) : undefined;
2252
2632
  },
2253
2633
  async compareAndSwap(transaction, input) {
2254
- const current = transaction.states.get(input.conversationId);
2634
+ const current = transaction.heads.get(input.conversationId);
2255
2635
  const actualVersion = current?.version ?? 0;
2256
2636
  if (actualVersion !== input.expectedVersion) {
2257
2637
  return { outcome: "conflict", actualVersion };
2258
2638
  }
2259
- transaction.states.set(input.conversationId, AgentStoredStateSchema.parse(structuredClone(input.next)));
2639
+ transaction.heads.set(input.conversationId, AgentRuntimeHeadSchema.parse(structuredClone(input.next)));
2260
2640
  return { outcome: "applied" };
2261
2641
  }
2262
2642
  },
2643
+ runs: {
2644
+ async load(transaction, input) {
2645
+ const record = transaction.runs.get(input.conversationId)?.get(input.runId);
2646
+ return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
2647
+ },
2648
+ async loadByAssistantMessageId(transaction, input) {
2649
+ const record = [...transaction.runs.get(input.conversationId)?.values() ?? []].find((candidate) => candidate.run.assistantMessageId === input.assistantMessageId);
2650
+ return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
2651
+ },
2652
+ async loadMany(transaction, input) {
2653
+ const records = transaction.runs.get(input.conversationId);
2654
+ return input.runIds.flatMap((runId) => {
2655
+ const record = records?.get(runId);
2656
+ return record ? [AgentStoredRunSchema.parse(structuredClone(record))] : [];
2657
+ });
2658
+ },
2659
+ async listActive(transaction, conversationId) {
2660
+ return [...transaction.runs.get(conversationId)?.values() ?? []].filter((record) => ["queued", "running", "interrupt_requested"].includes(record.run.state)).map((record) => AgentStoredRunSchema.parse(structuredClone(record)));
2661
+ },
2662
+ async save(transaction, rawRecord) {
2663
+ const record = AgentStoredRunSchema.parse(structuredClone(rawRecord));
2664
+ const conversationRuns = transaction.runs.get(record.run.conversationId) ?? new Map;
2665
+ const collision = [...conversationRuns.values()].find((candidate) => candidate.run.id !== record.run.id && candidate.run.assistantMessageId === record.run.assistantMessageId);
2666
+ if (collision)
2667
+ throw new TypeError("Assistant message identity is already reserved");
2668
+ conversationRuns.set(record.run.id, record);
2669
+ transaction.runs.set(record.run.conversationId, conversationRuns);
2670
+ }
2671
+ },
2672
+ admissions: {
2673
+ async load(transaction, input) {
2674
+ const receipt = transaction.admissions.get(input.conversationId)?.get(input.idempotencyKey);
2675
+ return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
2676
+ },
2677
+ async loadByInputMessageId(transaction, input) {
2678
+ const receipt = [
2679
+ ...transaction.admissions.get(input.conversationId)?.values() ?? []
2680
+ ].find((candidate) => candidate.input.id === input.inputMessageId);
2681
+ return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
2682
+ },
2683
+ async create(transaction, rawReceipt) {
2684
+ const receipt = AgentAdmissionReceiptSchema.parse(structuredClone(rawReceipt));
2685
+ const conversationAdmissions = transaction.admissions.get(receipt.conversationId) ?? new Map;
2686
+ if (conversationAdmissions.has(receipt.idempotencyKey) || [...conversationAdmissions.values()].some((candidate) => candidate.input.id === receipt.input.id)) {
2687
+ throw new TypeError("Admission identity is already reserved");
2688
+ }
2689
+ conversationAdmissions.set(receipt.idempotencyKey, receipt);
2690
+ transaction.admissions.set(receipt.conversationId, conversationAdmissions);
2691
+ }
2692
+ },
2263
2693
  history: {
2264
2694
  async load(transaction, conversationId) {
2265
2695
  return (transaction.histories.get(conversationId) ?? []).map((message) => AgentMessageSchema.parse(structuredClone(message)));
2266
2696
  },
2267
- async loadById(transaction, input) {
2268
- const active = (transaction.histories.get(input.conversationId) ?? []).find((message2) => message2.id === input.messageId);
2269
- const message = active ?? transaction.archivedMessages.get(input.conversationId)?.get(input.messageId);
2270
- return message ? AgentMessageSchema.parse(structuredClone(message)) : undefined;
2271
- },
2272
2697
  async apply(transaction, rawMutation) {
2273
2698
  const mutation = AgentHistoryMutationSchema.parse(rawMutation);
2274
2699
  const conversationId = mutation.type === "admit" ? mutation.input.conversationId : mutation.type === "upsert-assistant" ? mutation.message.conversationId : mutation.summary.conversationId;
@@ -2286,11 +2711,6 @@ function createMemoryAgentRuntimeStore() {
2286
2711
  const first = positions[0];
2287
2712
  if (first === undefined)
2288
2713
  throw new Error("Compaction history range disappeared");
2289
- const archive = transaction.archivedMessages.get(conversationId) ?? new Map;
2290
- for (const message of current.filter((candidate) => replaced.has(candidate.id))) {
2291
- archive.set(message.id, AgentMessageSchema.parse(structuredClone(message)));
2292
- }
2293
- transaction.archivedMessages.set(conversationId, archive);
2294
2714
  transaction.histories.set(conversationId, [
2295
2715
  ...current.slice(0, first),
2296
2716
  mutation.summary,
@@ -2299,7 +2719,7 @@ function createMemoryAgentRuntimeStore() {
2299
2719
  }
2300
2720
  },
2301
2721
  async scanRecoverable(input) {
2302
- const descriptors = [...states.values()].flatMap((state) => state.runs.filter((run) => ["queued", "running", "interrupt_requested"].includes(run.state)).map((run) => ({ conversationId: state.conversationId, run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
2722
+ const descriptors = [...runs].flatMap(([conversationId, conversationRuns]) => [...conversationRuns.values()].filter((record) => ["queued", "running", "interrupt_requested"].includes(record.run.state)).map((record) => ({ conversationId, run: record.run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
2303
2723
  const cursorTuple = input.cursor ? parseRecoverableCursor(input.cursor) : undefined;
2304
2724
  const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId === cursorTuple[0] && item.run.id === cursorTuple[1]) + 1 : 0;
2305
2725
  const items = descriptors.slice(start, start + input.limit);
@@ -2317,7 +2737,7 @@ export {
2317
2737
  AcceptInputAndAssignRunSchema,
2318
2738
  AcquireAgentRunSchema,
2319
2739
  AgentAdmissionEventSchema,
2320
- AgentAdmissionIdentitySchema,
2740
+ AgentAdmissionReceiptSchema,
2321
2741
  AgentAssistantPlaceholderSchema,
2322
2742
  AgentCheckpointEventSchema,
2323
2743
  AgentControlPartSchema,
@@ -2331,6 +2751,7 @@ export {
2331
2751
  AgentMessageStatusSchema,
2332
2752
  AgentModelCapabilitySchema,
2333
2753
  AgentModelDescriptorSchema,
2754
+ AgentModelRegistrySnapshotSchema,
2334
2755
  AgentOpaquePartSchema,
2335
2756
  AgentProviderEnvelopeSchema,
2336
2757
  AgentReasoningDeltaEventSchema,
@@ -2346,7 +2767,9 @@ export {
2346
2767
  AgentRunSchema,
2347
2768
  AgentRunStateEventSchema,
2348
2769
  AgentRunStateSchema,
2770
+ AgentRuntimeEventCursorSchema,
2349
2771
  AgentRuntimeEventSchema,
2772
+ AgentRuntimeHeadSchema,
2350
2773
  AgentSnapshotSchema,
2351
2774
  AgentSourcePartSchema,
2352
2775
  AgentStoreAppliedSchema,
@@ -2354,7 +2777,7 @@ export {
2354
2777
  AgentStoreDuplicateSchema,
2355
2778
  AgentStoreMutationResultSchema,
2356
2779
  AgentStoreNotFoundSchema,
2357
- AgentStoredStateSchema,
2780
+ AgentStoredRunSchema,
2358
2781
  AgentTerminalEventSchema,
2359
2782
  AgentTerminalReasonSchema,
2360
2783
  AgentTextPartSchema,
@@ -2371,9 +2794,12 @@ export {
2371
2794
  RecoverAgentRunSchema,
2372
2795
  ReplaceCompactedRangeSchema,
2373
2796
  RequestRunInterruptSchema,
2797
+ advanceAgentRuntimeEventCursor,
2798
+ agentDurableEventId,
2374
2799
  composeAgentPrompt,
2375
2800
  createAgentObservability,
2376
2801
  createAgentRuntime,
2802
+ createAgentRuntimeEventSink,
2377
2803
  createAgentRuntimeStore,
2378
2804
  createAgentSessionCoordinator,
2379
2805
  createAgentToolFenceLifecycle,
@@ -2381,5 +2807,8 @@ export {
2381
2807
  defineAgentProtocol,
2382
2808
  defineModelRegistry,
2383
2809
  projectAgentHistory,
2384
- structuredCompaction
2810
+ projectAgentHistoryDetailed,
2811
+ selectAgentHistory,
2812
+ structuredCompaction,
2813
+ validateAgentModelSnapshot
2385
2814
  };