stitchkit 0.85.0 → 0.85.2

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.
package/CHANGELOG.md CHANGED
@@ -15,6 +15,39 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.85.2] — 2026-09-08
19
+
20
+ ### Fixed
21
+
22
+ - **An asynchronous store no longer fails a run before the provider is
23
+ called.** The assistant checkpoint and the model-request admission are two
24
+ owned mutations of one run, written by two independent schedules — the stream
25
+ consumer and the SDK's model middleware — and each named the revision it read
26
+ before its own `await`. Where the store answered on a later tick (a worker, a
27
+ socket, a real database) the second write named a spent revision, the losing
28
+ compare-and-set threw out of `wrapStream`, and the run ended
29
+ `provider_failure` with zero provider calls; frequent checkpoints
30
+ (`checkpointEveryEvents: 1`) made it reliable. Owned mutations of a run now
31
+ take their turn in one per-run queue and read the revision inside that turn.
32
+ Independent runs are unaffected — the queue is per run — and fencing,
33
+ cancellation and checkpoint durability are unchanged. Proven on the packed
34
+ tarball by a consumer-lane fixture, not only in-repo. → ADR 0174 (amended).
35
+
36
+ ## [0.85.1] — 2026-09-07
37
+
38
+ ### Fixed
39
+
40
+ - **A later model step cannot outrun its durable lifecycle or the previous
41
+ structural checkpoint.** AI SDK lifecycle callbacks deliberately suppress
42
+ callback failures, so the 0.85.0 request-start write could lose a revision
43
+ race with stream persistence while the provider request still executed. The
44
+ runtime now uses the callback only for concrete-model call identity; awaited
45
+ step preparation and model middleware record request admission before `doStream`, and every step
46
+ after the first waits for the prior step's structural checkpoint. A rejected
47
+ lifecycle write therefore prevents the provider call instead of producing an
48
+ unrecorded inference. Deferred-store and failed-admission tests cover both
49
+ sides of the guarantee. → ADR 0174 (amended).
50
+
18
51
  ## [0.85.0] — 2026-09-07
19
52
 
20
53
  ### ⚠️ Breaking changes
@@ -1 +1 @@
1
- {"version":3,"file":"run-execution.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/run-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,OAAO,EACb,MAAM,IAAI,CAAC;AAMZ,OAAO,EAAE,KAAK,iBAAiB,EAAuB,MAAM,UAAU,CAAC;AAEvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAI1D,OAAO,KAAK,EAAqB,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAavE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAKL,KAAK,QAAQ,EAOd,MAAM,WAAW,CAAC;AAuCnB,8DAA8D;AAC9D,MAAM,WAAW,uBAAuB,CAAC,OAAO,EAAE,KAAK,SAAS,OAAO;IACrE,MAAM,EAAE,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,IAAI,MAAM,CAAC;IACrB,GAAG,IAAI,IAAI,CAAC;IACZ,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,KAAK,SAAS,OAAO,EAC9D,YAAY,EAAE,uBAAuB,CAAC,OAAO,EAAE,KAAK,CAAC,WAcb;IACtC,WAAW,EAAE,QAAQ,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB,KAAG,OAAO,CAAC,kBAAkB,CAAC,CAuhChC"}
1
+ {"version":3,"file":"run-execution.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/run-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,OAAO,EACb,MAAM,IAAI,CAAC;AAMZ,OAAO,EAAE,KAAK,iBAAiB,EAAuB,MAAM,UAAU,CAAC;AAEvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAK1D,OAAO,KAAK,EAAqB,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAavE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAKL,KAAK,QAAQ,EAOd,MAAM,WAAW,CAAC;AAuCnB,8DAA8D;AAC9D,MAAM,WAAW,uBAAuB,CAAC,OAAO,EAAE,KAAK,SAAS,OAAO;IACrE,MAAM,EAAE,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,IAAI,MAAM,CAAC;IACrB,GAAG,IAAI,IAAI,CAAC;IACZ,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,KAAK,SAAS,OAAO,EAC9D,YAAY,EAAE,uBAAuB,CAAC,OAAO,EAAE,KAAK,CAAC,WAcb;IACtC,WAAW,EAAE,QAAQ,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB,KAAG,OAAO,CAAC,kBAAkB,CAAC,CA8iChC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Owned mutations of one run, applied one at a time.
3
+ *
4
+ * Every owned mutation is a compare-and-set against the run's current revision,
5
+ * and the revision it must name is the one the previous mutation produced.
6
+ * Reading that revision and writing it back are therefore safe apart only while
7
+ * nothing else can write in between — which stops being true the moment a store
8
+ * returns a promise that yields. With an asynchronous store the assistant
9
+ * checkpoint and the model-request admission both name the revision they read
10
+ * before their own `await`, the later write is rejected as a conflict, and the
11
+ * run fails without the provider ever having been called.
12
+ *
13
+ * The terminal commit answers the same hazard with a bounded retry, which suits
14
+ * it: committing a terminal twice is a duplicate the store recognises. It does
15
+ * not suit these two — a retried operation write would publish a second
16
+ * lifecycle event for one provider call — so they take their turn instead.
17
+ */
18
+ export type RunMutationQueue = <T>(mutation: () => Promise<T>) => Promise<T>;
19
+ /** A queue of owned mutations for one run. */
20
+ export declare function createRunMutationQueue(): RunMutationQueue;
21
+ //# sourceMappingURL=run-mutation-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-mutation-queue.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/run-mutation-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7E,8CAA8C;AAC9C,wBAAgB,sBAAsB,IAAI,gBAAgB,CAgBzD"}
@@ -1,4 +1,6 @@
1
+ import { type LanguageModel } from 'ai';
1
2
  import type { AgentRuntimeEvent } from './event-schema.js';
3
+ import type { RunMutationQueue } from './run-mutation-queue.js';
2
4
  import { type AgentRun, type AgentSnapshot } from './schemas.js';
3
5
  import type { AgentRuntimeStore } from './store.js';
4
6
  type TerminalOperationPhase = 'completed' | 'failed' | 'cancelled';
@@ -9,11 +11,16 @@ export interface AgentRunOperationLifecycleConfig {
9
11
  acceptSnapshot(snapshot: AgentSnapshot): void;
10
12
  publish(event: AgentRuntimeEvent): Promise<void>;
11
13
  now(): Date;
14
+ /** The order every owned mutation of this run takes its turn in. */
15
+ serialize: RunMutationQueue;
12
16
  }
13
17
  /** One durable latest-operation state machine for an executing run. */
14
18
  export declare function createAgentRunOperationLifecycle(config: AgentRunOperationLifecycleConfig): {
19
+ noteProviderCall(callId: string): void;
20
+ prepareModel(model: LanguageModel, step: number, fallbackCallId: string): Promise<LanguageModel>;
21
+ checkpointStep(step: number): void;
22
+ failStepCheckpoints(error: unknown): void;
15
23
  startCompaction(operationId: string): Promise<void>;
16
- startModelRequest(providerCallId: string, step: number): Promise<void>;
17
24
  firstOutput(): Promise<void>;
18
25
  finish(phase: TerminalOperationPhase): Promise<void>;
19
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"run-operation-lifecycle.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/run-operation-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGxD,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGjD,KAAK,sBAAsB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEnE,MAAM,WAAW,gCAAgC;IAC/C,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,IAAI,QAAQ,CAAC;IACvB,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,GAAG,IAAI,IAAI,CAAC;CACb;AAED,uEAAuE;AACvE,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,gCAAgC;IA+BrF,eAAe,cAAc,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD,iBAAiB,iBAAiB,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtE,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,MAAM,QAAQ,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;EAavD"}
1
+ {"version":3,"file":"run-operation-lifecycle.d.ts","sourceRoot":"","sources":["../../src/agent-runtime/run-operation-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,IAAI,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAGjD,KAAK,sBAAsB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEnE,MAAM,WAAW,gCAAgC;IAC/C,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,IAAI,QAAQ,CAAC;IACvB,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,GAAG,IAAI,IAAI,CAAC;IACZ,oEAAoE;IACpE,SAAS,EAAE,gBAAgB,CAAC;CAC7B;AAED,uEAAuE;AACvE,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,gCAAgC;IA+CrF,gBAAgB,SAAS,MAAM,GAAG,IAAI;IAGhC,YAAY,QACT,aAAa,QACd,MAAM,kBACI,MAAM,GACrB,OAAO,CAAC,aAAa,CAAC;IAgCzB,cAAc,OAAO,MAAM,GAAG,IAAI;IAQlC,mBAAmB,QAAQ,OAAO,GAAG,IAAI;IAIzC,eAAe,cAAc,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,MAAM,QAAQ,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;EAavD"}
@@ -2,7 +2,7 @@ import {
2
2
  composeAgentPrompt,
3
3
  createAgentRuntime,
4
4
  createAgentRuntimeEventSink
5
- } from "./index-9merfjxf.js";
5
+ } from "./index-2jg7b11j.js";
6
6
  import {
7
7
  AgentModelDescriptorSchema
8
8
  } from "./index-tg3m2ec5.js";
@@ -49,7 +49,7 @@ import {
49
49
  registerDeferredToolRepair,
50
50
  repairedSearchCall,
51
51
  selectAgentHistory
52
- } from "./index-9merfjxf.js";
52
+ } from "./index-2jg7b11j.js";
53
53
  import {
54
54
  AgentModelCapabilitySchema,
55
55
  AgentModelCatalogEntrySchema,
@@ -934,6 +934,23 @@ function repairedSearchCall(toolCall, searchName) {
934
934
  };
935
935
  }
936
936
 
937
+ // src/agent-runtime/run-mutation-queue.ts
938
+ function createRunMutationQueue() {
939
+ let tail = Promise.resolve();
940
+ return (mutation) => {
941
+ const result = tail.then(mutation, mutation);
942
+ tail = result.then(() => {
943
+ return;
944
+ }, () => {
945
+ return;
946
+ });
947
+ return result;
948
+ };
949
+ }
950
+
951
+ // src/agent-runtime/run-operation-lifecycle.ts
952
+ import { wrapLanguageModel } from "ai";
953
+
937
954
  // src/agent-runtime/runtime-internals.ts
938
955
  import { z as z2 } from "zod";
939
956
  function findRun(runs, runId) {
@@ -1078,7 +1095,10 @@ function createIdleDeadline(parent, timeoutMs) {
1078
1095
  // src/agent-runtime/run-operation-lifecycle.ts
1079
1096
  function createAgentRunOperationLifecycle(config) {
1080
1097
  let active;
1081
- const record = async (operation) => {
1098
+ let providerCallId;
1099
+ let checkpointedStep = -1;
1100
+ const checkpointWaiters = new Map;
1101
+ const record = (operation) => config.serialize(async () => {
1082
1102
  const current = config.currentRun();
1083
1103
  const snapshot = appliedSnapshot(await config.store.recordRunOperation({
1084
1104
  conversationId: current.conversationId,
@@ -1100,8 +1120,58 @@ function createAgentRunOperationLifecycle(config) {
1100
1120
  operation,
1101
1121
  emittedAt: config.now().toISOString()
1102
1122
  });
1103
- };
1123
+ });
1124
+ const startModelRequest = (callId, step) => record(AgentRunOperationSchema.parse({
1125
+ operationId: `${callId}:${step}`,
1126
+ kind: "model-request",
1127
+ phase: "started",
1128
+ step,
1129
+ startedAt: config.now().toISOString()
1130
+ }));
1104
1131
  return {
1132
+ noteProviderCall(callId) {
1133
+ providerCallId = callId;
1134
+ },
1135
+ async prepareModel(model, step, fallbackCallId) {
1136
+ if (step > checkpointedStep + 1) {
1137
+ const waiter = checkpointWaiters.get(step - 1) ?? Promise.withResolvers();
1138
+ checkpointWaiters.set(step - 1, waiter);
1139
+ await waiter.promise;
1140
+ }
1141
+ if (typeof model === "string") {
1142
+ await startModelRequest(fallbackCallId, step);
1143
+ return model;
1144
+ }
1145
+ return wrapLanguageModel({
1146
+ model,
1147
+ middleware: {
1148
+ specificationVersion: "v4",
1149
+ wrapStream: async ({ doStream }) => {
1150
+ const callId = providerCallId;
1151
+ providerCallId = undefined;
1152
+ if (!callId) {
1153
+ throw new Error("AgentRuntime model call reached the provider without lifecycle identity");
1154
+ }
1155
+ await startModelRequest(callId, step);
1156
+ return doStream();
1157
+ }
1158
+ }
1159
+ });
1160
+ },
1161
+ checkpointStep(step) {
1162
+ checkpointedStep = Math.max(checkpointedStep, step);
1163
+ for (const [waitingFor, waiter] of checkpointWaiters) {
1164
+ if (waitingFor > checkpointedStep)
1165
+ continue;
1166
+ checkpointWaiters.delete(waitingFor);
1167
+ waiter.resolve();
1168
+ }
1169
+ },
1170
+ failStepCheckpoints(error) {
1171
+ for (const waiter of checkpointWaiters.values())
1172
+ waiter.reject(error);
1173
+ checkpointWaiters.clear();
1174
+ },
1105
1175
  startCompaction(operationId) {
1106
1176
  return record(AgentRunOperationSchema.parse({
1107
1177
  operationId,
@@ -1110,15 +1180,6 @@ function createAgentRunOperationLifecycle(config) {
1110
1180
  startedAt: config.now().toISOString()
1111
1181
  }));
1112
1182
  },
1113
- startModelRequest(providerCallId, step) {
1114
- return record(AgentRunOperationSchema.parse({
1115
- operationId: `${providerCallId}:${step}`,
1116
- kind: "model-request",
1117
- phase: "started",
1118
- step,
1119
- startedAt: config.now().toISOString()
1120
- }));
1121
- },
1122
1183
  firstOutput() {
1123
1184
  if (active?.kind !== "model-request" || active.phase !== "started") {
1124
1185
  return Promise.resolve();
@@ -1267,9 +1328,11 @@ function createRunExecutor(dependencies) {
1267
1328
  let terminalPolicyName;
1268
1329
  const idleDeadline = createIdleDeadline(input.signal, idleTimeoutMs);
1269
1330
  const executionSignal = idleDeadline.signal;
1331
+ const serialize = createRunMutationQueue();
1270
1332
  const operationLifecycle = createAgentRunOperationLifecycle({
1271
1333
  store: config.store,
1272
1334
  runtimeEpoch,
1335
+ serialize,
1273
1336
  currentRun: () => run,
1274
1337
  acceptSnapshot: (next) => {
1275
1338
  snapshot = next;
@@ -1300,7 +1363,7 @@ function createRunExecutor(dependencies) {
1300
1363
  ...provider && { provider }
1301
1364
  }));
1302
1365
  };
1303
- const checkpoint = async () => {
1366
+ const checkpoint = () => serialize(async () => {
1304
1367
  assistant = AgentMessageSchema.parse({
1305
1368
  ...assistant,
1306
1369
  parts,
@@ -1333,7 +1396,7 @@ function createRunExecutor(dependencies) {
1333
1396
  metrics: checkpointMetrics,
1334
1397
  emittedAt: now().toISOString()
1335
1398
  });
1336
- };
1399
+ });
1337
1400
  try {
1338
1401
  if (config.history?.compact) {
1339
1402
  await operationLifecycle.startCompaction(generateId());
@@ -1506,16 +1569,17 @@ function createRunExecutor(dependencies) {
1506
1569
  }
1507
1570
  return messages;
1508
1571
  };
1572
+ const fallbackModel = selectedModel.model;
1509
1573
  const result = streamAgentTextBoundary({
1510
- model: selectedModel.model,
1574
+ model: fallbackModel,
1511
1575
  tools,
1512
1576
  instructions: withCarriedSystem(prompt.instructions),
1513
1577
  messages: history,
1514
1578
  abortSignal: executionSignal,
1515
1579
  maxRetries: 0,
1516
1580
  stopWhen: stopConditions,
1517
- onLanguageModelCallStart: async (event) => {
1518
- await operationLifecycle.startModelRequest(event.callId, step);
1581
+ onLanguageModelCallStart: (event) => {
1582
+ operationLifecycle.noteProviderCall(event.callId);
1519
1583
  },
1520
1584
  repairToolCall: deferredToolRepair(config.loop?.prepareStep),
1521
1585
  ...config.loop?.toolApproval && {
@@ -1525,25 +1589,27 @@ function createRunExecutor(dependencies) {
1525
1589
  ...config.loop?.toolApprovalSecret && {
1526
1590
  experimental_toolApprovalSecret: config.loop.toolApprovalSecret
1527
1591
  },
1528
- ...(config.loop?.prepareStep || injection) && {
1529
- prepareStep: async (options) => {
1530
- const previousStep = options.steps.at(-1);
1531
- if (previousStep) {
1532
- lastPromptTokens = selectedModel?.normalizeUsage?.({
1533
- usage: previousStep.usage,
1534
- providerMetadata: previousStep.providerMetadata
1535
- })?.inputTokens ?? normalizeSdkUsage(previousStep.usage).inputTokens;
1536
- }
1537
- const prepared = await config.loop?.prepareStep?.({
1538
- ...options,
1539
- ...runtimeContext
1540
- });
1541
- const injected = await takeInjectedMessages();
1542
- if (injected.length === 0)
1543
- return prepared;
1544
- const base = prepared?.messages ?? options.messages;
1545
- return { ...prepared, messages: [...base, ...injected] };
1592
+ prepareStep: async (options) => {
1593
+ const previousStep = options.steps.at(-1);
1594
+ if (previousStep) {
1595
+ lastPromptTokens = selectedModel?.normalizeUsage?.({
1596
+ usage: previousStep.usage,
1597
+ providerMetadata: previousStep.providerMetadata
1598
+ })?.inputTokens ?? normalizeSdkUsage(previousStep.usage).inputTokens;
1546
1599
  }
1600
+ const prepared = await config.loop?.prepareStep?.({
1601
+ ...options,
1602
+ ...runtimeContext
1603
+ });
1604
+ const injected = await takeInjectedMessages();
1605
+ const preparedStep = injected.length === 0 ? prepared : {
1606
+ ...prepared,
1607
+ messages: [...prepared?.messages ?? options.messages, ...injected]
1608
+ };
1609
+ return {
1610
+ ...preparedStep,
1611
+ model: await operationLifecycle.prepareModel(preparedStep?.model ?? fallbackModel, options.stepNumber, generateId())
1612
+ };
1547
1613
  }
1548
1614
  });
1549
1615
  for await (const part of result.stream) {
@@ -1819,6 +1885,8 @@ function createRunExecutor(dependencies) {
1819
1885
  if (structuralBoundary || eventsSinceCheckpoint >= checkpointEveryEvents) {
1820
1886
  await checkpoint();
1821
1887
  eventsSinceCheckpoint = 0;
1888
+ if (part.type === "finish-step")
1889
+ operationLifecycle.checkpointStep(step - 1);
1822
1890
  }
1823
1891
  }
1824
1892
  await operationLifecycle.finish(executionSignal.aborted ? "cancelled" : terminalReason === "provider_failure" ? "failed" : "completed");
@@ -1844,6 +1912,7 @@ function createRunExecutor(dependencies) {
1844
1912
  }
1845
1913
  }
1846
1914
  } catch (error) {
1915
+ operationLifecycle.failStepCheckpoints(error);
1847
1916
  internalCause = error;
1848
1917
  const latest = await config.store.loadRun({
1849
1918
  conversationId: run.conversationId,
package/llms-full.txt CHANGED
@@ -5276,8 +5276,11 @@ transaction. Drivers without this guarantee must leave the capability absent. Se
5276
5276
  - `run-operation` follows a successful operation CAS. Its `operation` is the
5277
5277
  durable `AgentRun.lastOperation`: `model-request` or `compaction`, with
5278
5278
  `started`, `first-output`, `completed`, `failed` or `cancelled` phase and the
5279
- original timestamps. Model request start means the provider call is about to
5280
- enter `doStream`, not that HTTP transmission is measured. First output is a
5279
+ original timestamps. Concrete models use SDK call identity in an awaited model
5280
+ middleware; global provider IDs use a runtime-generated identity during awaited
5281
+ step preparation. Both durably admit the request before entering `doStream`, so a failed
5282
+ write prevents the provider call. This does not claim HTTP transmission is
5283
+ measured. First output is a
5281
5284
  non-empty text/reasoning/tool-argument delta or complete tool call; metadata,
5282
5285
  usage, files and sources do not count;
5283
5286
  - `tool-status` is transient lifecycle presentation with JSON-safe input on
@@ -5292,7 +5295,9 @@ request/response and step-finish boundaries force an assistant checkpoint and
5292
5295
  reset that batch. The checkpoint is awaited after the managed loop observes the
5293
5296
  normalized boundary. It does **not** promise persistence before a tool side
5294
5297
  effect: tool fencing supplies the real ownership checks around execution, while
5295
- cross-crash effect idempotency stays application-owned. ADR 0174.
5298
+ cross-crash effect idempotency stays application-owned. The following model
5299
+ step waits for the previous step-finish checkpoint before its own durable
5300
+ request admission and provider invocation. → ADR 0174.
5296
5301
 
5297
5302
  `compaction` names invocation of the configured callback, including a
5298
5303
  `not_needed` context-budget check; it does not by itself say that history was
@@ -15187,7 +15192,7 @@ Server-only optional application runtime. See the
15187
15192
  | `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver, explicit unresolved-file behavior, and how an interrupted turn reaches the model (`interruptedAssistant`) |
15188
15193
  | `createAgentToolFenceLifecycle` | function | pre-effect and post-effect run ownership fence for `mountAgent`; compose beside application idempotency for [durable operations](../guide/mcp-and-agents.md#durable-application-owned-execution) |
15189
15194
  | `AgentRuntimeEventSchema` | schema | transient stream lifecycle plus post-commit admission/checkpoint/run-state/run-operation/terminal projections |
15190
- | `AgentRunOperationKindSchema` / `AgentRunOperationPhaseSchema` / `AgentRunOperationSchema` / `AgentRunOperation` | schemas / _type_ | latest durable model-request or compaction phase with operation/step identity and original timestamps |
15195
+ | `AgentRunOperationKindSchema` / `AgentRunOperationPhaseSchema` / `AgentRunOperationSchema` / `AgentRunOperation` | schemas / _type_ | latest durable model-request or compaction phase with operation/step identity and original timestamps; request admission is awaited before provider invocation |
15191
15196
  | `RecordRunOperationSchema` / `RecordRunOperation` | schema / _type_ | owner/fencing/revision-checked mutation of `AgentRun.lastOperation` |
15192
15197
  | `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
15193
15198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.85.0",
3
+ "version": "0.85.2",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",
@@ -285,7 +285,7 @@
285
285
  "@types/json-schema": "^7.0.15",
286
286
  "@types/react": "^19.2.18",
287
287
  "@typescript/typescript6": "^6.0.2",
288
- "ai": "^7.0.79",
288
+ "ai": "^7.0.93",
289
289
  "fake-indexeddb": "^6.2.5",
290
290
  "grammy": "^1.45.1",
291
291
  "react": "^19.2.8",