stitchkit 0.58.0 → 0.59.1

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.
@@ -900,6 +900,8 @@ import {
900
900
  streamText
901
901
  } from "ai";
902
902
  import { z as z5 } from "zod";
903
+
904
+ // src/agent-runtime/terminal-commit.ts
903
905
  class AgentRuntimeConflictError extends Error {
904
906
  constructor(operation) {
905
907
  super(`Agent runtime store conflict during ${operation}`);
@@ -911,6 +913,94 @@ function appliedSnapshot(result, operation) {
911
913
  return result.snapshot;
912
914
  throw new AgentRuntimeConflictError(operation);
913
915
  }
916
+ function terminalMessageStatus(reason) {
917
+ if (reason === "success" || reason === "policy_stop")
918
+ return "completed";
919
+ if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
920
+ return "interrupted";
921
+ }
922
+ return "failed";
923
+ }
924
+ function canonicalTerminal(snapshot, runId, retainedAssistant) {
925
+ const run = snapshot.runs.find((candidate) => candidate.id === runId);
926
+ if (!run?.terminalReason)
927
+ return;
928
+ const assistant = snapshot.messages.find((message) => message.id === run.assistantMessageId) ?? retainedAssistant;
929
+ if (snapshot.conversationId !== run.conversationId || !assistant || assistant.id !== run.assistantMessageId || assistant.conversationId !== run.conversationId || assistant.runId !== run.id || assistant.role !== "assistant" || assistant.status !== terminalMessageStatus(run.terminalReason)) {
930
+ throw new AgentRuntimeConflictError("terminal result projection");
931
+ }
932
+ return {
933
+ snapshot,
934
+ run,
935
+ assistant,
936
+ reason: run.terminalReason,
937
+ committedByCaller: false,
938
+ ...run.terminalPolicyName && { policyName: run.terminalPolicyName }
939
+ };
940
+ }
941
+ function interruptedCandidate(candidate, run, now) {
942
+ const hasInterruptControl = candidate.assistant.parts.some((part) => part.type === "control" && part.reason === "run-interrupted");
943
+ const parts = hasInterruptControl ? candidate.assistant.parts : [
944
+ ...candidate.assistant.parts,
945
+ AgentMessagePartSchema.parse({ type: "control", reason: "run-interrupted" })
946
+ ];
947
+ return {
948
+ run,
949
+ assistant: AgentMessageSchema.parse({
950
+ ...candidate.assistant,
951
+ status: "interrupted",
952
+ parts,
953
+ updatedAt: now().toISOString()
954
+ }),
955
+ reason: "interrupted"
956
+ };
957
+ }
958
+ function canRetryTerminal(current, previous, runtimeEpoch) {
959
+ return (current.state === "running" || current.state === "interrupt_requested") && current.ownerId === runtimeEpoch && current.fencingToken === previous.fencingToken;
960
+ }
961
+ async function commitAgentRunTerminal(input) {
962
+ let candidate = input.candidate;
963
+ while (true) {
964
+ const committed = await input.store.commitRunTerminal({
965
+ conversationId: candidate.run.conversationId,
966
+ runId: candidate.run.id,
967
+ expectedRevision: candidate.run.revision,
968
+ ownerId: input.runtimeEpoch,
969
+ ...candidate.run.fencingToken !== undefined && {
970
+ fencingToken: candidate.run.fencingToken
971
+ },
972
+ assistant: candidate.assistant,
973
+ reason: candidate.reason,
974
+ ...candidate.policyName && { policyName: candidate.policyName }
975
+ });
976
+ if (committed.outcome === "applied") {
977
+ const terminal2 = canonicalTerminal(committed.snapshot, candidate.run.id);
978
+ if (!terminal2)
979
+ throw new AgentRuntimeConflictError("terminal result projection");
980
+ return { ...terminal2, committedByCaller: true };
981
+ }
982
+ if (committed.outcome === "duplicate") {
983
+ const terminal2 = canonicalTerminal(committed.snapshot, candidate.run.id, committed.assistant);
984
+ if (!terminal2)
985
+ throw new AgentRuntimeConflictError("terminal result projection");
986
+ return terminal2;
987
+ }
988
+ if (committed.outcome !== "conflict") {
989
+ throw new AgentRuntimeConflictError("terminal commit");
990
+ }
991
+ const latest = await input.store.loadSnapshot(candidate.run.conversationId);
992
+ const terminal = canonicalTerminal(latest, candidate.run.id);
993
+ if (terminal)
994
+ return terminal;
995
+ const current = latest.runs.find((run) => run.id === candidate.run.id);
996
+ if (!current || !canRetryTerminal(current, candidate.run, input.runtimeEpoch)) {
997
+ throw new AgentRuntimeConflictError("terminal commit");
998
+ }
999
+ candidate = current.state === "interrupt_requested" ? interruptedCandidate(candidate, current, input.now) : { ...candidate, run: current };
1000
+ }
1001
+ }
1002
+
1003
+ // src/agent-runtime/runtime.ts
914
1004
  function findRun(runs, runId) {
915
1005
  const run = runs.find((candidate) => candidate.id === runId);
916
1006
  if (!run)
@@ -1547,59 +1637,66 @@ function createAgentRuntime(config) {
1547
1637
  parts,
1548
1638
  updatedAt: now().toISOString()
1549
1639
  });
1550
- snapshot = appliedSnapshot(await config.store.commitRunTerminal({
1551
- conversationId: run.conversationId,
1552
- runId: run.id,
1553
- expectedRevision: run.revision,
1554
- ownerId: runtimeEpoch,
1555
- ...run.fencingToken !== undefined && { fencingToken: run.fencingToken },
1556
- assistant,
1557
- reason: terminalReason,
1558
- ...terminalPolicyName && { policyName: terminalPolicyName }
1559
- }), "terminal commit");
1560
- run = findRun(snapshot.runs, run.id);
1561
- const terminalMetrics = {
1640
+ const terminal = await commitAgentRunTerminal({
1641
+ store: config.store,
1642
+ runtimeEpoch,
1643
+ candidate: {
1644
+ run,
1645
+ assistant,
1646
+ reason: terminalReason,
1647
+ ...terminalPolicyName && { policyName: terminalPolicyName }
1648
+ },
1649
+ now
1650
+ });
1651
+ snapshot = terminal.snapshot;
1652
+ run = terminal.run;
1653
+ assistant = terminal.assistant;
1654
+ terminalReason = terminal.reason;
1655
+ terminalPolicyName = terminal.policyName;
1656
+ const terminalMetrics = terminal.committedByCaller ? {
1562
1657
  partial: false,
1563
1658
  durationMs: performance.now() - runStartedAt,
1564
1659
  ...usage && { usage },
1565
1660
  ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt }
1566
- };
1567
- config.observe?.emit({
1568
- schemaVersion: 1,
1569
- eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1570
- type: "run-terminal",
1571
- conversationId: run.conversationId,
1572
- runId: run.id,
1573
- traceId: trace?.traceId ?? generateId(),
1574
- spanId: trace?.spanId ?? generateId(),
1575
- ...trace?.parentSpanId && { parentSpanId: trace.parentSpanId },
1576
- state: run.state,
1577
- terminalReason,
1578
- ...selectedModel && { modelId: selectedModel.descriptor.modelId },
1579
- durationMs: terminalMetrics.durationMs,
1580
- ...usage && { usage },
1581
- ...internalCause !== undefined && { internalCause },
1582
- ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt },
1583
- emittedAt: now().toISOString()
1584
- });
1585
- await publish({
1586
- type: "terminal",
1587
- eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1588
- conversationId: run.conversationId,
1589
- runId: run.id,
1590
- snapshotVersion: snapshot.version,
1591
- reason: terminalReason,
1592
- ...terminalPolicyName && { policyName: terminalPolicyName },
1593
- message: assistant,
1594
- metrics: terminalMetrics,
1595
- emittedAt: now().toISOString()
1596
- });
1661
+ } : undefined;
1662
+ if (terminalMetrics) {
1663
+ config.observe?.emit({
1664
+ schemaVersion: 1,
1665
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1666
+ type: "run-terminal",
1667
+ conversationId: run.conversationId,
1668
+ runId: run.id,
1669
+ traceId: trace?.traceId ?? generateId(),
1670
+ spanId: trace?.spanId ?? generateId(),
1671
+ ...trace?.parentSpanId && { parentSpanId: trace.parentSpanId },
1672
+ state: run.state,
1673
+ terminalReason,
1674
+ ...selectedModel && { modelId: selectedModel.descriptor.modelId },
1675
+ durationMs: terminalMetrics.durationMs,
1676
+ ...usage && { usage },
1677
+ ...internalCause !== undefined && { internalCause },
1678
+ ...firstOutputAt !== undefined && { ttftMs: firstOutputAt - runStartedAt },
1679
+ emittedAt: now().toISOString()
1680
+ });
1681
+ await publish({
1682
+ type: "terminal",
1683
+ eventId: agentDurableEventId("terminal", run.id, snapshot.version),
1684
+ conversationId: run.conversationId,
1685
+ runId: run.id,
1686
+ snapshotVersion: snapshot.version,
1687
+ reason: terminalReason,
1688
+ ...terminalPolicyName && { policyName: terminalPolicyName },
1689
+ message: assistant,
1690
+ metrics: terminalMetrics,
1691
+ emittedAt: now().toISOString()
1692
+ });
1693
+ }
1597
1694
  return {
1598
1695
  run,
1599
1696
  message: assistant,
1600
1697
  reason: terminalReason,
1601
1698
  snapshotVersion: snapshot.version,
1602
- metrics: terminalMetrics,
1699
+ ...terminalMetrics && { metrics: terminalMetrics },
1603
1700
  ...terminalPolicyName && { policyName: terminalPolicyName }
1604
1701
  };
1605
1702
  };
@@ -1724,7 +1821,7 @@ function createAgentRuntime(config) {
1724
1821
  });
1725
1822
  const acceptedSnapshot = appliedSnapshot(acceptance, "input acceptance");
1726
1823
  const assignedRunId = acceptance.outcome === "duplicate" ? acceptance.runId : reservation?.admission.runId ?? runId;
1727
- const acceptedRun = findRun(acceptedSnapshot.runs, assignedRunId);
1824
+ const acceptedRun = acceptance.outcome === "duplicate" ? acceptance.run : findRun(acceptedSnapshot.runs, assignedRunId);
1728
1825
  const actualInputMessageId = acceptance.outcome === "duplicate" ? acceptance.inputMessageId : userMessage2.id;
1729
1826
  const acceptedInput = acceptance.outcome === "duplicate" ? acceptance.input : acceptedSnapshot.messages.find((candidate) => candidate.id === actualInputMessageId);
1730
1827
  if (!acceptedInput) {
@@ -1739,7 +1836,7 @@ function createAgentRuntime(config) {
1739
1836
  createdAt: acceptedRun.createdAt,
1740
1837
  updatedAt: acceptedRun.updatedAt
1741
1838
  });
1742
- const acceptedAssistant = acceptance.outcome === "duplicate" ? acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId) ?? assistantPlaceholder : assistantPlaceholder;
1839
+ const acceptedAssistant = acceptance.outcome === "duplicate" ? acceptance.assistant ?? assistantPlaceholder : assistantPlaceholder;
1743
1840
  const admission = {
1744
1841
  inputMessageId: acceptedInput.id,
1745
1842
  runId: acceptedRun.id,
@@ -1781,9 +1878,9 @@ function createAgentRuntime(config) {
1781
1878
  }
1782
1879
  return;
1783
1880
  }
1784
- const message = acceptedSnapshot.messages.find((candidate) => candidate.id === acceptedRun.assistantMessageId);
1881
+ const message = acceptance.assistant;
1785
1882
  if (!message) {
1786
- const error = new Error("Duplicate terminal run has no assistant message");
1883
+ const error = new Error("Duplicate terminal admission has no retained canonical assistant");
1787
1884
  outerResult.reject(error);
1788
1885
  if (reservation?.shouldSchedule) {
1789
1886
  reservation.admission.completion.reject(error);
@@ -1996,6 +2093,8 @@ var AgentStoreDuplicateSchema = z6.object({
1996
2093
  inputMessageId: AgentRecordIdSchema,
1997
2094
  runId: AgentRecordIdSchema,
1998
2095
  assistantMessageId: AgentRecordIdSchema,
2096
+ run: AgentRunSchema,
2097
+ assistant: AgentMessageSchema.optional(),
1999
2098
  snapshot: AgentSnapshotSchema
2000
2099
  });
2001
2100
  var AgentStoreMutationResultSchema = z6.discriminatedUnion("outcome", [
@@ -2055,18 +2154,23 @@ var ReplaceCompactedRangeSchema = z6.object({
2055
2154
  });
2056
2155
  // src/agent-runtime/store-driver.ts
2057
2156
  import { z as z7 } from "zod";
2058
- var AgentAdmissionIdentitySchema = z7.object({
2059
- idempotencyKey: z7.string().min(1),
2060
- inputMessageId: AgentRecordIdSchema,
2061
- runId: AgentRecordIdSchema,
2062
- assistantMessageId: AgentRecordIdSchema
2157
+ var AgentRuntimeHeadSchema = z7.object({
2158
+ schemaVersion: z7.literal(1),
2159
+ conversationId: AgentRecordIdSchema,
2160
+ version: AgentRecordVersionSchema
2063
2161
  });
2064
- var AgentStoredStateSchema = z7.object({
2162
+ var AgentStoredRunSchema = z7.object({
2163
+ schemaVersion: z7.literal(1),
2164
+ run: AgentRunSchema,
2165
+ terminalAssistant: AgentMessageSchema.optional()
2166
+ });
2167
+ var AgentAdmissionReceiptSchema = z7.object({
2065
2168
  schemaVersion: z7.literal(1),
2066
2169
  conversationId: AgentRecordIdSchema,
2067
- version: AgentRecordVersionSchema,
2068
- runs: z7.array(AgentRunSchema),
2069
- admissions: z7.array(AgentAdmissionIdentitySchema)
2170
+ idempotencyKey: z7.string().min(1),
2171
+ input: AgentMessageSchema,
2172
+ runId: AgentRecordIdSchema,
2173
+ assistantMessageId: AgentRecordIdSchema
2070
2174
  });
2071
2175
  var AgentHistoryMutationSchema = z7.discriminatedUnion("type", [
2072
2176
  z7.object({ type: z7.literal("admit"), input: AgentMessageSchema }),
@@ -2092,40 +2196,40 @@ var AgentRecoverableScanInputSchema = z7.object({
2092
2196
  cursor: z7.string().min(1).optional(),
2093
2197
  limit: z7.number().int().min(1).max(1000)
2094
2198
  });
2095
- function emptyState(conversationId) {
2096
- return AgentStoredStateSchema.parse({
2199
+ function emptyHead(conversationId) {
2200
+ return AgentRuntimeHeadSchema.parse({
2097
2201
  schemaVersion: 1,
2098
2202
  conversationId,
2099
- version: 0,
2100
- runs: [],
2101
- admissions: []
2203
+ version: 0
2102
2204
  });
2103
2205
  }
2104
- function snapshotOf(state, messages) {
2105
- validateAggregate(state, messages);
2206
+ function snapshotOf(head, messages, records) {
2207
+ validateSnapshot(head, messages, records);
2106
2208
  return AgentSnapshotSchema.parse({
2107
2209
  schemaVersion: 1,
2108
- conversationId: state.conversationId,
2109
- version: state.version,
2210
+ conversationId: head.conversationId,
2211
+ version: head.version,
2110
2212
  messages,
2111
- runs: state.runs
2213
+ runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
2112
2214
  });
2113
2215
  }
2114
- function validateAggregate(state, messages) {
2216
+ function validateSnapshot(head, messages, records) {
2115
2217
  const runIds = new Set;
2116
2218
  const assistantIds = new Set;
2117
2219
  const messageIds = new Set;
2118
- const idempotencyKeys = new Set;
2119
- const admittedInputIds = new Set;
2120
- for (const run of state.runs) {
2121
- if (run.conversationId !== state.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
2122
- throw new TypeError("Stored agent state contains inconsistent run identities");
2220
+ for (const record of records) {
2221
+ const run = record.run;
2222
+ if (run.conversationId !== head.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
2223
+ throw new TypeError("Stored agent runs contain inconsistent identities");
2224
+ }
2225
+ 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)) {
2226
+ throw new TypeError("Retained terminal assistant does not match its run");
2123
2227
  }
2124
2228
  runIds.add(run.id);
2125
2229
  assistantIds.add(run.assistantMessageId);
2126
2230
  }
2127
2231
  for (const message of messages) {
2128
- if (message.conversationId !== state.conversationId || messageIds.has(message.id)) {
2232
+ if (message.conversationId !== head.conversationId || messageIds.has(message.id)) {
2129
2233
  throw new TypeError("Stored agent history contains inconsistent message identities");
2130
2234
  }
2131
2235
  messageIds.add(message.id);
@@ -2133,23 +2237,12 @@ function validateAggregate(state, messages) {
2133
2237
  throw new TypeError("Stored history occupies a reserved assistant identity");
2134
2238
  }
2135
2239
  if (message.runId !== undefined) {
2136
- const run = state.runs.find((candidate) => candidate.id === message.runId);
2240
+ const run = records.find((candidate) => candidate.run.id === message.runId)?.run;
2137
2241
  if (!run || message.role !== "assistant" || run.assistantMessageId !== message.id) {
2138
2242
  throw new TypeError("Stored assistant history does not match its reserved run identity");
2139
2243
  }
2140
2244
  }
2141
2245
  }
2142
- for (const admission of state.admissions) {
2143
- const run = state.runs.find((candidate) => candidate.id === admission.runId);
2144
- if (idempotencyKeys.has(admission.idempotencyKey) || admittedInputIds.has(admission.inputMessageId) || !run || run.assistantMessageId !== admission.assistantMessageId || !run.inputMessageIds.includes(admission.inputMessageId)) {
2145
- throw new TypeError("Stored admission identity is inconsistent with its assigned run");
2146
- }
2147
- idempotencyKeys.add(admission.idempotencyKey);
2148
- admittedInputIds.add(admission.inputMessageId);
2149
- }
2150
- }
2151
- function recoverableDescriptors(state) {
2152
- return state.runs.filter((run) => ["queued", "running", "interrupt_requested"].includes(run.state)).map((run) => ({ conversationId: state.conversationId, run }));
2153
2246
  }
2154
2247
  var RecoverableCursorSchema = z7.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
2155
2248
  function recoverableCursor(input) {
@@ -2179,7 +2272,7 @@ function terminalState(reason) {
2179
2272
  return "abandoned";
2180
2273
  return "failed";
2181
2274
  }
2182
- function terminalMessageStatus(reason) {
2275
+ function terminalMessageStatus2(reason) {
2183
2276
  if (reason === "success" || reason === "policy_stop")
2184
2277
  return "completed";
2185
2278
  if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
@@ -2187,7 +2280,7 @@ function terminalMessageStatus(reason) {
2187
2280
  }
2188
2281
  return "failed";
2189
2282
  }
2190
- function applied(current, admissions, input, historyMutation) {
2283
+ function applied(current, input, effects) {
2191
2284
  return {
2192
2285
  outcome: "applied",
2193
2286
  snapshot: AgentSnapshotSchema.parse({
@@ -2196,27 +2289,14 @@ function applied(current, admissions, input, historyMutation) {
2196
2289
  runs: input.runs ?? current.runs,
2197
2290
  messages: input.messages ?? current.messages
2198
2291
  }),
2199
- admissions,
2200
- ...historyMutation && { historyMutation }
2292
+ ...effects?.runRecord && { runRecord: effects.runRecord },
2293
+ ...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
2294
+ ...effects?.historyMutation && { historyMutation: effects.historyMutation }
2201
2295
  };
2202
2296
  }
2203
- function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2297
+ function reduceStore(current, operation) {
2204
2298
  if (operation.type === "accept") {
2205
2299
  const input = operation.input;
2206
- const duplicate = currentAdmissions.find((candidate) => candidate.idempotencyKey === input.idempotencyKey);
2207
- if (duplicate) {
2208
- if (!duplicateInput) {
2209
- throw new Error("Duplicate admission input is unavailable from canonical history");
2210
- }
2211
- return {
2212
- outcome: "duplicate",
2213
- input: duplicateInput,
2214
- inputMessageId: duplicate.inputMessageId,
2215
- runId: duplicate.runId,
2216
- assistantMessageId: duplicate.assistantMessageId,
2217
- snapshot: current
2218
- };
2219
- }
2220
2300
  if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
2221
2301
  return conflict(current.version);
2222
2302
  }
@@ -2233,16 +2313,25 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2233
2313
  revision: coalescedRun.revision + 1,
2234
2314
  updatedAt: new Date().toISOString()
2235
2315
  }) : input.run;
2236
- const admission = AgentAdmissionIdentitySchema.parse({
2316
+ const admissionReceipt = AgentAdmissionReceiptSchema.parse({
2317
+ schemaVersion: 1,
2318
+ conversationId: input.input.conversationId,
2237
2319
  idempotencyKey: input.idempotencyKey,
2238
- inputMessageId: input.input.id,
2320
+ input: input.input,
2239
2321
  runId: assignedRun.id,
2240
2322
  assistantMessageId: assignedRun.assistantMessageId
2241
2323
  });
2242
- return applied(current, [...currentAdmissions, admission], {
2324
+ return applied(current, {
2243
2325
  messages: [...current.messages, input.input],
2244
2326
  runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
2245
- }, { type: "admit", input: input.input });
2327
+ }, {
2328
+ runRecord: AgentStoredRunSchema.parse({
2329
+ schemaVersion: 1,
2330
+ run: assignedRun
2331
+ }),
2332
+ admissionReceipt,
2333
+ historyMutation: { type: "admit", input: input.input }
2334
+ });
2246
2335
  }
2247
2336
  const conversationId = operation.input.conversationId;
2248
2337
  const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
@@ -2262,8 +2351,8 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2262
2351
  revision: run.revision + 1,
2263
2352
  updatedAt: new Date().toISOString()
2264
2353
  });
2265
- return applied(current, currentAdmissions, {
2266
- runs: replaceRun(current.runs, next)
2354
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2355
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
2267
2356
  });
2268
2357
  }
2269
2358
  if (operation.type === "checkpoint" && run) {
@@ -2276,10 +2365,13 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2276
2365
  revision: run.revision + 1,
2277
2366
  updatedAt: new Date().toISOString()
2278
2367
  });
2279
- return applied(current, currentAdmissions, {
2368
+ return applied(current, {
2280
2369
  runs: replaceRun(current.runs, next),
2281
2370
  messages: replaceMessage(current.messages, input.assistant)
2282
- }, { type: "upsert-assistant", message: input.assistant });
2371
+ }, {
2372
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next }),
2373
+ historyMutation: { type: "upsert-assistant", message: input.assistant }
2374
+ });
2283
2375
  }
2284
2376
  if (operation.type === "interrupt" && run) {
2285
2377
  if (run.revision !== operation.input.expectedRevision || run.state !== "running") {
@@ -2291,8 +2383,8 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2291
2383
  revision: run.revision + 1,
2292
2384
  updatedAt: new Date().toISOString()
2293
2385
  });
2294
- return applied(current, currentAdmissions, {
2295
- runs: replaceRun(current.runs, next)
2386
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2387
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
2296
2388
  });
2297
2389
  }
2298
2390
  if (operation.type === "recover" && run) {
@@ -2330,18 +2422,25 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2330
2422
  status: "failed",
2331
2423
  updatedAt: new Date().toISOString()
2332
2424
  });
2333
- return applied(current, currentAdmissions, {
2425
+ return applied(current, {
2334
2426
  runs: replaceRun(current.runs, next),
2335
2427
  messages: replaceMessage(current.messages, assistant)
2336
- }, { type: "upsert-assistant", message: assistant });
2428
+ }, {
2429
+ runRecord: AgentStoredRunSchema.parse({
2430
+ schemaVersion: 1,
2431
+ run: next,
2432
+ terminalAssistant: assistant
2433
+ }),
2434
+ historyMutation: { type: "upsert-assistant", message: assistant }
2435
+ });
2337
2436
  }
2338
- return applied(current, currentAdmissions, {
2339
- runs: replaceRun(current.runs, next)
2437
+ return applied(current, { runs: replaceRun(current.runs, next) }, {
2438
+ runRecord: AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })
2340
2439
  });
2341
2440
  }
2342
2441
  if (operation.type === "terminal" && run) {
2343
2442
  const input = operation.input;
2344
- 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)) {
2443
+ 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 !== terminalMessageStatus2(input.reason)) {
2345
2444
  return conflict(run.revision);
2346
2445
  }
2347
2446
  const next = AgentRunSchema.parse({
@@ -2352,10 +2451,17 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2352
2451
  revision: run.revision + 1,
2353
2452
  updatedAt: new Date().toISOString()
2354
2453
  });
2355
- return applied(current, currentAdmissions, {
2454
+ return applied(current, {
2356
2455
  runs: replaceRun(current.runs, next),
2357
2456
  messages: replaceMessage(current.messages, input.assistant)
2358
- }, { type: "upsert-assistant", message: input.assistant });
2457
+ }, {
2458
+ runRecord: AgentStoredRunSchema.parse({
2459
+ schemaVersion: 1,
2460
+ run: next,
2461
+ terminalAssistant: input.assistant
2462
+ }),
2463
+ historyMutation: { type: "upsert-assistant", message: input.assistant }
2464
+ });
2359
2465
  }
2360
2466
  if (operation.type === "compact") {
2361
2467
  const input = operation.input;
@@ -2377,10 +2483,12 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2377
2483
  input.summary,
2378
2484
  ...current.messages.slice(first + positions.length)
2379
2485
  ];
2380
- return applied(current, currentAdmissions, { messages }, {
2381
- type: "replace-compacted-range",
2382
- replacedMessageIds: input.replacedMessageIds,
2383
- summary: input.summary
2486
+ return applied(current, { messages }, {
2487
+ historyMutation: {
2488
+ type: "replace-compacted-range",
2489
+ replacedMessageIds: input.replacedMessageIds,
2490
+ summary: input.summary
2491
+ }
2384
2492
  });
2385
2493
  }
2386
2494
  return { outcome: "not_found" };
@@ -2388,48 +2496,130 @@ function reduceStore(current, currentAdmissions, operation, duplicateInput) {
2388
2496
  function operationConversationId(operation) {
2389
2497
  return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
2390
2498
  }
2499
+ function mergeRunRecords(...groups) {
2500
+ const records = new Map;
2501
+ for (const group of groups) {
2502
+ for (const rawRecord of group) {
2503
+ const record = AgentStoredRunSchema.parse(rawRecord);
2504
+ const previous = records.get(record.run.id);
2505
+ if (previous && previous.run.assistantMessageId !== record.run.assistantMessageId) {
2506
+ throw new TypeError("Stored agent run identity changed across normalized records");
2507
+ }
2508
+ records.set(record.run.id, record);
2509
+ }
2510
+ }
2511
+ return [...records.values()];
2512
+ }
2513
+ function referencedRunIds(messages) {
2514
+ return [...new Set(messages.flatMap((message) => message.runId ? [message.runId] : []))];
2515
+ }
2516
+ function validateAdmissionReceipt(receipt, record, conversationId) {
2517
+ const input = receipt.input;
2518
+ const run = record.run;
2519
+ 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)) {
2520
+ throw new TypeError("Admission receipt does not match its canonical run assignment");
2521
+ }
2522
+ }
2391
2523
  function createAgentRuntimeStore(driver) {
2392
2524
  const loadSnapshot = (conversationId) => driver.transaction(async (transaction) => {
2393
- const [stored, messages] = await Promise.all([
2394
- driver.state.load(transaction, conversationId),
2395
- driver.history.load(transaction, conversationId)
2525
+ const [stored, messages, activeRecords] = await Promise.all([
2526
+ driver.head.load(transaction, conversationId),
2527
+ driver.history.load(transaction, conversationId),
2528
+ driver.runs.listActive(transaction, conversationId)
2396
2529
  ]);
2397
- return snapshotOf(stored ?? emptyState(conversationId), messages);
2530
+ const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
2531
+ const referencedRecords = await driver.runs.loadMany(transaction, {
2532
+ conversationId,
2533
+ runIds: referencedRunIds(messages)
2534
+ });
2535
+ return snapshotOf(head, messages, mergeRunRecords(activeRecords, referencedRecords));
2398
2536
  });
2399
2537
  const mutate = (operation) => driver.transaction(async (transaction) => {
2400
2538
  const conversationId = operationConversationId(operation);
2401
- const [stored, messages] = await Promise.all([
2402
- driver.state.load(transaction, conversationId),
2403
- driver.history.load(transaction, conversationId)
2539
+ const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.input.runId;
2540
+ const [stored, messages, activeRecords, operationRecord, duplicateReceipt] = await Promise.all([
2541
+ driver.head.load(transaction, conversationId),
2542
+ driver.history.load(transaction, conversationId),
2543
+ driver.runs.listActive(transaction, conversationId),
2544
+ operationRunId ? driver.runs.load(transaction, { conversationId, runId: operationRunId }) : undefined,
2545
+ operation.type === "accept" ? driver.admissions.load(transaction, {
2546
+ conversationId,
2547
+ idempotencyKey: operation.input.idempotencyKey
2548
+ }) : undefined
2404
2549
  ]);
2405
- const state = AgentStoredStateSchema.parse(stored ?? emptyState(conversationId));
2406
- const current = snapshotOf(state, messages);
2407
- const duplicateIdentity = operation.type === "accept" ? state.admissions.find((candidate) => candidate.idempotencyKey === operation.input.idempotencyKey) : undefined;
2408
- const duplicateInput = duplicateIdentity ? await driver.history.loadById(transaction, {
2550
+ const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
2551
+ const referencedRecords = await driver.runs.loadMany(transaction, {
2409
2552
  conversationId,
2410
- messageId: duplicateIdentity.inputMessageId
2411
- }) : undefined;
2412
- if (duplicateIdentity && duplicateInput && (duplicateInput.id !== duplicateIdentity.inputMessageId || duplicateInput.conversationId !== conversationId || duplicateInput.role !== "user" || duplicateInput.status !== "committed" || duplicateInput.runId !== undefined)) {
2413
- throw new TypeError("Canonical duplicate input does not match its admission identity");
2553
+ runIds: referencedRunIds(messages)
2554
+ });
2555
+ const records = mergeRunRecords(activeRecords, referencedRecords, operationRecord ? [operationRecord] : []);
2556
+ const current = snapshotOf(head, messages, records);
2557
+ if (duplicateReceipt) {
2558
+ const duplicateRecord = await driver.runs.load(transaction, {
2559
+ conversationId,
2560
+ runId: duplicateReceipt.runId
2561
+ });
2562
+ if (!duplicateRecord) {
2563
+ throw new TypeError("Admission receipt points to a missing canonical run");
2564
+ }
2565
+ validateAdmissionReceipt(duplicateReceipt, duplicateRecord, conversationId);
2566
+ return {
2567
+ outcome: "duplicate",
2568
+ input: duplicateReceipt.input,
2569
+ inputMessageId: duplicateReceipt.input.id,
2570
+ runId: duplicateReceipt.runId,
2571
+ assistantMessageId: duplicateReceipt.assistantMessageId,
2572
+ run: duplicateRecord.run,
2573
+ ...duplicateRecord.terminalAssistant && {
2574
+ assistant: duplicateRecord.terminalAssistant
2575
+ },
2576
+ snapshot: snapshotOf(head, messages, mergeRunRecords(records, [duplicateRecord]))
2577
+ };
2414
2578
  }
2415
- const reduced = reduceStore(current, state.admissions, operation, duplicateInput);
2579
+ if (operation.type === "accept") {
2580
+ const inputCollision = await driver.admissions.loadByInputMessageId(transaction, {
2581
+ conversationId,
2582
+ inputMessageId: operation.input.input.id
2583
+ });
2584
+ if (inputCollision) {
2585
+ throw new TypeError("Input message identity is already assigned to an admission");
2586
+ }
2587
+ if (!operation.input.coalesceIntoRunId) {
2588
+ const [runCollision, assistantCollision] = await Promise.all([
2589
+ driver.runs.load(transaction, {
2590
+ conversationId,
2591
+ runId: operation.input.run.id
2592
+ }),
2593
+ driver.runs.loadByAssistantMessageId(transaction, {
2594
+ conversationId,
2595
+ assistantMessageId: operation.input.run.assistantMessageId
2596
+ })
2597
+ ]);
2598
+ if (runCollision || assistantCollision) {
2599
+ throw new TypeError("Queued run identities are already reserved");
2600
+ }
2601
+ }
2602
+ }
2603
+ const reduced = reduceStore(current, operation);
2416
2604
  if (reduced.outcome !== "applied")
2417
2605
  return reduced;
2418
- const nextState = AgentStoredStateSchema.parse({
2606
+ const nextHead = AgentRuntimeHeadSchema.parse({
2419
2607
  schemaVersion: 1,
2420
2608
  conversationId,
2421
- version: reduced.snapshot.version,
2422
- runs: reduced.snapshot.runs,
2423
- admissions: reduced.admissions
2609
+ version: reduced.snapshot.version
2424
2610
  });
2425
- const outcome = await driver.state.compareAndSwap(transaction, {
2611
+ const outcome = await driver.head.compareAndSwap(transaction, {
2426
2612
  conversationId,
2427
2613
  expectedVersion: current.version,
2428
- next: nextState,
2429
- recoverable: recoverableDescriptors(nextState)
2614
+ next: nextHead
2430
2615
  });
2431
2616
  if (outcome.outcome === "conflict")
2432
2617
  return conflict(outcome.actualVersion);
2618
+ if (reduced.runRecord)
2619
+ await driver.runs.save(transaction, reduced.runRecord);
2620
+ if (reduced.admissionReceipt) {
2621
+ await driver.admissions.create(transaction, reduced.admissionReceipt);
2622
+ }
2433
2623
  if (reduced.historyMutation) {
2434
2624
  await driver.history.apply(transaction, reduced.historyMutation);
2435
2625
  }
@@ -2481,10 +2671,16 @@ function createAgentRuntimeStore(driver) {
2481
2671
  }
2482
2672
  };
2483
2673
  }
2484
- function cloneStateMap(source) {
2674
+ function cloneHeadMap(source) {
2485
2675
  return new Map([...source].map(([key, value]) => [
2486
2676
  key,
2487
- AgentStoredStateSchema.parse(structuredClone(value))
2677
+ AgentRuntimeHeadSchema.parse(structuredClone(value))
2678
+ ]));
2679
+ }
2680
+ function cloneNestedMap(source, clone) {
2681
+ return new Map([...source].map(([outerKey, values]) => [
2682
+ outerKey,
2683
+ new Map([...values].map(([innerKey, value]) => [innerKey, clone(value)]))
2488
2684
  ]));
2489
2685
  }
2490
2686
  function cloneHistoryMap(source) {
@@ -2494,9 +2690,10 @@ function cloneHistoryMap(source) {
2494
2690
  ]));
2495
2691
  }
2496
2692
  function createMemoryAgentRuntimeStore() {
2497
- let states = new Map;
2693
+ let heads = new Map;
2694
+ let runs = new Map;
2695
+ let admissions = new Map;
2498
2696
  let histories = new Map;
2499
- let archivedMessages = new Map;
2500
2697
  let transactionTail = Promise.resolve();
2501
2698
  const driver = {
2502
2699
  async transaction(work) {
@@ -2509,50 +2706,91 @@ function createMemoryAgentRuntimeStore() {
2509
2706
  return;
2510
2707
  });
2511
2708
  const transaction = {
2512
- states: cloneStateMap(states),
2513
- histories: cloneHistoryMap(histories),
2514
- archivedMessages: new Map([...archivedMessages].map(([conversationId, messages]) => [
2515
- conversationId,
2516
- new Map([...messages].map(([messageId, message]) => [
2517
- messageId,
2518
- AgentMessageSchema.parse(structuredClone(message))
2519
- ]))
2520
- ]))
2709
+ heads: cloneHeadMap(heads),
2710
+ runs: cloneNestedMap(runs, (record) => AgentStoredRunSchema.parse(structuredClone(record))),
2711
+ admissions: cloneNestedMap(admissions, (receipt) => AgentAdmissionReceiptSchema.parse(structuredClone(receipt))),
2712
+ histories: cloneHistoryMap(histories)
2521
2713
  };
2522
2714
  try {
2523
2715
  const result = await work(transaction);
2524
- states = transaction.states;
2716
+ heads = transaction.heads;
2717
+ runs = transaction.runs;
2718
+ admissions = transaction.admissions;
2525
2719
  histories = transaction.histories;
2526
- archivedMessages = transaction.archivedMessages;
2527
2720
  return result;
2528
2721
  } finally {
2529
2722
  release.resolve();
2530
2723
  }
2531
2724
  },
2532
- state: {
2725
+ head: {
2533
2726
  async load(transaction, conversationId) {
2534
- const state = transaction.states.get(conversationId);
2535
- return state ? AgentStoredStateSchema.parse(structuredClone(state)) : undefined;
2727
+ const head = transaction.heads.get(conversationId);
2728
+ return head ? AgentRuntimeHeadSchema.parse(structuredClone(head)) : undefined;
2536
2729
  },
2537
2730
  async compareAndSwap(transaction, input) {
2538
- const current = transaction.states.get(input.conversationId);
2731
+ const current = transaction.heads.get(input.conversationId);
2539
2732
  const actualVersion = current?.version ?? 0;
2540
2733
  if (actualVersion !== input.expectedVersion) {
2541
2734
  return { outcome: "conflict", actualVersion };
2542
2735
  }
2543
- transaction.states.set(input.conversationId, AgentStoredStateSchema.parse(structuredClone(input.next)));
2736
+ transaction.heads.set(input.conversationId, AgentRuntimeHeadSchema.parse(structuredClone(input.next)));
2544
2737
  return { outcome: "applied" };
2545
2738
  }
2546
2739
  },
2740
+ runs: {
2741
+ async load(transaction, input) {
2742
+ const record = transaction.runs.get(input.conversationId)?.get(input.runId);
2743
+ return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
2744
+ },
2745
+ async loadByAssistantMessageId(transaction, input) {
2746
+ const record = [...transaction.runs.get(input.conversationId)?.values() ?? []].find((candidate) => candidate.run.assistantMessageId === input.assistantMessageId);
2747
+ return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
2748
+ },
2749
+ async loadMany(transaction, input) {
2750
+ const records = transaction.runs.get(input.conversationId);
2751
+ return input.runIds.flatMap((runId) => {
2752
+ const record = records?.get(runId);
2753
+ return record ? [AgentStoredRunSchema.parse(structuredClone(record))] : [];
2754
+ });
2755
+ },
2756
+ async listActive(transaction, conversationId) {
2757
+ return [...transaction.runs.get(conversationId)?.values() ?? []].filter((record) => ["queued", "running", "interrupt_requested"].includes(record.run.state)).map((record) => AgentStoredRunSchema.parse(structuredClone(record)));
2758
+ },
2759
+ async save(transaction, rawRecord) {
2760
+ const record = AgentStoredRunSchema.parse(structuredClone(rawRecord));
2761
+ const conversationRuns = transaction.runs.get(record.run.conversationId) ?? new Map;
2762
+ const collision = [...conversationRuns.values()].find((candidate) => candidate.run.id !== record.run.id && candidate.run.assistantMessageId === record.run.assistantMessageId);
2763
+ if (collision)
2764
+ throw new TypeError("Assistant message identity is already reserved");
2765
+ conversationRuns.set(record.run.id, record);
2766
+ transaction.runs.set(record.run.conversationId, conversationRuns);
2767
+ }
2768
+ },
2769
+ admissions: {
2770
+ async load(transaction, input) {
2771
+ const receipt = transaction.admissions.get(input.conversationId)?.get(input.idempotencyKey);
2772
+ return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
2773
+ },
2774
+ async loadByInputMessageId(transaction, input) {
2775
+ const receipt = [
2776
+ ...transaction.admissions.get(input.conversationId)?.values() ?? []
2777
+ ].find((candidate) => candidate.input.id === input.inputMessageId);
2778
+ return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
2779
+ },
2780
+ async create(transaction, rawReceipt) {
2781
+ const receipt = AgentAdmissionReceiptSchema.parse(structuredClone(rawReceipt));
2782
+ const conversationAdmissions = transaction.admissions.get(receipt.conversationId) ?? new Map;
2783
+ if (conversationAdmissions.has(receipt.idempotencyKey) || [...conversationAdmissions.values()].some((candidate) => candidate.input.id === receipt.input.id)) {
2784
+ throw new TypeError("Admission identity is already reserved");
2785
+ }
2786
+ conversationAdmissions.set(receipt.idempotencyKey, receipt);
2787
+ transaction.admissions.set(receipt.conversationId, conversationAdmissions);
2788
+ }
2789
+ },
2547
2790
  history: {
2548
2791
  async load(transaction, conversationId) {
2549
2792
  return (transaction.histories.get(conversationId) ?? []).map((message) => AgentMessageSchema.parse(structuredClone(message)));
2550
2793
  },
2551
- async loadById(transaction, input) {
2552
- const active = (transaction.histories.get(input.conversationId) ?? []).find((message2) => message2.id === input.messageId);
2553
- const message = active ?? transaction.archivedMessages.get(input.conversationId)?.get(input.messageId);
2554
- return message ? AgentMessageSchema.parse(structuredClone(message)) : undefined;
2555
- },
2556
2794
  async apply(transaction, rawMutation) {
2557
2795
  const mutation = AgentHistoryMutationSchema.parse(rawMutation);
2558
2796
  const conversationId = mutation.type === "admit" ? mutation.input.conversationId : mutation.type === "upsert-assistant" ? mutation.message.conversationId : mutation.summary.conversationId;
@@ -2570,11 +2808,6 @@ function createMemoryAgentRuntimeStore() {
2570
2808
  const first = positions[0];
2571
2809
  if (first === undefined)
2572
2810
  throw new Error("Compaction history range disappeared");
2573
- const archive = transaction.archivedMessages.get(conversationId) ?? new Map;
2574
- for (const message of current.filter((candidate) => replaced.has(candidate.id))) {
2575
- archive.set(message.id, AgentMessageSchema.parse(structuredClone(message)));
2576
- }
2577
- transaction.archivedMessages.set(conversationId, archive);
2578
2811
  transaction.histories.set(conversationId, [
2579
2812
  ...current.slice(0, first),
2580
2813
  mutation.summary,
@@ -2583,7 +2816,7 @@ function createMemoryAgentRuntimeStore() {
2583
2816
  }
2584
2817
  },
2585
2818
  async scanRecoverable(input) {
2586
- 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));
2819
+ 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));
2587
2820
  const cursorTuple = input.cursor ? parseRecoverableCursor(input.cursor) : undefined;
2588
2821
  const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId === cursorTuple[0] && item.run.id === cursorTuple[1]) + 1 : 0;
2589
2822
  const items = descriptors.slice(start, start + input.limit);
@@ -2601,7 +2834,7 @@ export {
2601
2834
  AcceptInputAndAssignRunSchema,
2602
2835
  AcquireAgentRunSchema,
2603
2836
  AgentAdmissionEventSchema,
2604
- AgentAdmissionIdentitySchema,
2837
+ AgentAdmissionReceiptSchema,
2605
2838
  AgentAssistantPlaceholderSchema,
2606
2839
  AgentCheckpointEventSchema,
2607
2840
  AgentControlPartSchema,
@@ -2633,6 +2866,7 @@ export {
2633
2866
  AgentRunStateSchema,
2634
2867
  AgentRuntimeEventCursorSchema,
2635
2868
  AgentRuntimeEventSchema,
2869
+ AgentRuntimeHeadSchema,
2636
2870
  AgentSnapshotSchema,
2637
2871
  AgentSourcePartSchema,
2638
2872
  AgentStoreAppliedSchema,
@@ -2640,7 +2874,7 @@ export {
2640
2874
  AgentStoreDuplicateSchema,
2641
2875
  AgentStoreMutationResultSchema,
2642
2876
  AgentStoreNotFoundSchema,
2643
- AgentStoredStateSchema,
2877
+ AgentStoredRunSchema,
2644
2878
  AgentTerminalEventSchema,
2645
2879
  AgentTerminalReasonSchema,
2646
2880
  AgentTextPartSchema,