stitchkit 0.63.0 → 0.65.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.
- package/dist/agent-runtime/coordinator.d.ts +6 -7
- package/dist/agent-runtime/coordinator.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +12 -18
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/history.d.ts +13 -0
- package/dist/agent-runtime/history.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +327 -14
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/prompt.d.ts +3 -1
- package/dist/agent-runtime/prompt.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts +0 -4
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +11 -1
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts +24 -10
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver.d.ts +15 -10
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store.d.ts +8 -43
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-commit.d.ts +2 -3
- package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
- package/dist/agent-runtime.d.ts +2 -2
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +109 -181
- package/dist/{index-b1k33127.js → index-sbkyvacf.js} +54 -6
- package/dist/testing/agent-store-conformance.d.ts.map +1 -1
- package/dist/testing.js +64 -3
- package/llms-full.txt +207 -47
- package/package.json +1 -1
|
@@ -99,7 +99,6 @@ var AgentRunStateSchema = z.enum([
|
|
|
99
99
|
"completed",
|
|
100
100
|
"interrupted",
|
|
101
101
|
"superseded",
|
|
102
|
-
"absorbed",
|
|
103
102
|
"failed",
|
|
104
103
|
"cancelled",
|
|
105
104
|
"abandoned"
|
|
@@ -114,9 +113,24 @@ var AgentTerminalReasonSchema = z.enum([
|
|
|
114
113
|
"timeout",
|
|
115
114
|
"shutdown",
|
|
116
115
|
"provider_failure",
|
|
117
|
-
"
|
|
116
|
+
"context_overflow",
|
|
118
117
|
"abandoned"
|
|
119
118
|
]);
|
|
119
|
+
function runStateForTerminalReason(reason) {
|
|
120
|
+
if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
|
|
121
|
+
return "completed";
|
|
122
|
+
}
|
|
123
|
+
if (reason === "interrupted")
|
|
124
|
+
return "interrupted";
|
|
125
|
+
if (reason === "superseded")
|
|
126
|
+
return "superseded";
|
|
127
|
+
if (reason === "cancelled" || reason === "shutdown" || reason === "timeout") {
|
|
128
|
+
return "cancelled";
|
|
129
|
+
}
|
|
130
|
+
if (reason === "abandoned")
|
|
131
|
+
return "abandoned";
|
|
132
|
+
return "failed";
|
|
133
|
+
}
|
|
120
134
|
var AgentUsageValueSchema = z.object({
|
|
121
135
|
value: z.number().nonnegative().optional(),
|
|
122
136
|
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
@@ -134,7 +148,7 @@ var AgentUsageSchema = z.object({
|
|
|
134
148
|
cacheWriteTokens: AgentUsageValueSchema.optional(),
|
|
135
149
|
cost: AgentCostValueSchema.optional()
|
|
136
150
|
});
|
|
137
|
-
var
|
|
151
|
+
var AgentRunFieldsSchema = z.object({
|
|
138
152
|
schemaVersion: z.literal(1),
|
|
139
153
|
id: AgentRecordIdSchema,
|
|
140
154
|
conversationId: AgentRecordIdSchema,
|
|
@@ -146,11 +160,45 @@ var AgentRunSchema = z.object({
|
|
|
146
160
|
fencingToken: AgentRecordVersionSchema.optional(),
|
|
147
161
|
terminalReason: AgentTerminalReasonSchema.optional(),
|
|
148
162
|
terminalPolicyName: z.string().min(1).optional(),
|
|
149
|
-
absorbedIntoRunId: AgentRecordIdSchema.optional(),
|
|
150
163
|
usage: AgentUsageSchema.optional(),
|
|
151
164
|
createdAt: AgentTimestampSchema,
|
|
152
165
|
updatedAt: AgentTimestampSchema
|
|
153
166
|
});
|
|
167
|
+
var AgentRunSchema = AgentRunFieldsSchema.superRefine((run, ctx) => {
|
|
168
|
+
if (run.terminalReason === undefined) {
|
|
169
|
+
if (TERMINAL_RUN_STATES.has(run.state)) {
|
|
170
|
+
ctx.addIssue({
|
|
171
|
+
code: "custom",
|
|
172
|
+
path: ["terminalReason"],
|
|
173
|
+
message: `A run in terminal state "${run.state}" must say why it ended`
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const expected = runStateForTerminalReason(run.terminalReason);
|
|
179
|
+
if (run.state !== expected) {
|
|
180
|
+
ctx.addIssue({
|
|
181
|
+
code: "custom",
|
|
182
|
+
path: ["state"],
|
|
183
|
+
message: `Terminal reason "${run.terminalReason}" ends a run in state "${expected}", not "${run.state}"`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (run.terminalReason === "policy_stop" && run.terminalPolicyName === undefined) {
|
|
187
|
+
ctx.addIssue({
|
|
188
|
+
code: "custom",
|
|
189
|
+
path: ["terminalPolicyName"],
|
|
190
|
+
message: "A policy stop names the policy that stopped the run"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
var TERMINAL_RUN_STATES = new Set([
|
|
195
|
+
"completed",
|
|
196
|
+
"interrupted",
|
|
197
|
+
"superseded",
|
|
198
|
+
"cancelled",
|
|
199
|
+
"abandoned",
|
|
200
|
+
"failed"
|
|
201
|
+
]);
|
|
154
202
|
var AgentSnapshotSchema = z.object({
|
|
155
203
|
schemaVersion: z.literal(1),
|
|
156
204
|
conversationId: AgentRecordIdSchema,
|
|
@@ -160,9 +208,9 @@ var AgentSnapshotSchema = z.object({
|
|
|
160
208
|
});
|
|
161
209
|
var AgentRunMetricsSchema = z.object({
|
|
162
210
|
partial: z.boolean(),
|
|
163
|
-
usage: AgentUsageSchema
|
|
211
|
+
usage: AgentUsageSchema,
|
|
164
212
|
durationMs: z.number().nonnegative().optional(),
|
|
165
213
|
ttftMs: z.number().nonnegative().optional()
|
|
166
214
|
});
|
|
167
215
|
|
|
168
|
-
export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentTerminalReasonSchema, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
|
|
216
|
+
export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentTerminalReasonSchema, runStateForTerminalReason, AgentUsageValueSchema, AgentCostValueSchema, AgentUsageSchema, AgentRunSchema, AgentSnapshotSchema, AgentRunMetricsSchema };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-store-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/agent-store-conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"agent-store-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/agent-store-conformance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,IAAI,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAwCD,gFAAgF;AAChF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,IAAI,CAAC,CAiXf"}
|
package/dist/testing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentMessageSchema,
|
|
3
3
|
AgentRunSchema
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-sbkyvacf.js";
|
|
5
5
|
import {
|
|
6
6
|
createApplication
|
|
7
7
|
} from "./index-eabpd4tb.js";
|
|
@@ -308,11 +308,72 @@ async function runAgentStoreConformance(config) {
|
|
|
308
308
|
createdAt: "2026-08-22T00:00:00.000Z",
|
|
309
309
|
updatedAt: "2026-08-22T00:00:02.000Z"
|
|
310
310
|
});
|
|
311
|
+
if (checkpointedRun.fencingToken === undefined) {
|
|
312
|
+
throw new Error("Acquisition must persist a fencing token the store can read back");
|
|
313
|
+
}
|
|
314
|
+
const staleFence = await store.checkpointRunAssistant({
|
|
315
|
+
conversationId,
|
|
316
|
+
runId: running.id,
|
|
317
|
+
expectedRevision: checkpointedRun.revision,
|
|
318
|
+
ownerId: "conformance-owner",
|
|
319
|
+
fencingToken: checkpointedRun.fencingToken + 1,
|
|
320
|
+
assistant: AgentMessageSchema.parse({
|
|
321
|
+
schemaVersion: 1,
|
|
322
|
+
id: running.assistantMessageId,
|
|
323
|
+
conversationId,
|
|
324
|
+
runId: running.id,
|
|
325
|
+
role: "assistant",
|
|
326
|
+
status: "streaming",
|
|
327
|
+
parts: [{ type: "text", text: "stale fence" }],
|
|
328
|
+
createdAt: "2026-08-22T00:00:00.000Z",
|
|
329
|
+
updatedAt: "2026-08-22T00:00:01.500Z"
|
|
330
|
+
})
|
|
331
|
+
});
|
|
332
|
+
if (staleFence.outcome !== "conflict") {
|
|
333
|
+
throw new Error("A checkpoint with a stale fencing token must conflict");
|
|
334
|
+
}
|
|
335
|
+
const foreignOwner = await store.checkpointRunAssistant({
|
|
336
|
+
conversationId,
|
|
337
|
+
runId: running.id,
|
|
338
|
+
expectedRevision: checkpointedRun.revision,
|
|
339
|
+
ownerId: "a-different-runtime",
|
|
340
|
+
assistant: AgentMessageSchema.parse({
|
|
341
|
+
schemaVersion: 1,
|
|
342
|
+
id: running.assistantMessageId,
|
|
343
|
+
conversationId,
|
|
344
|
+
runId: running.id,
|
|
345
|
+
role: "assistant",
|
|
346
|
+
status: "streaming",
|
|
347
|
+
parts: [{ type: "text", text: "foreign owner" }],
|
|
348
|
+
createdAt: "2026-08-22T00:00:00.000Z",
|
|
349
|
+
updatedAt: "2026-08-22T00:00:01.700Z"
|
|
350
|
+
})
|
|
351
|
+
});
|
|
352
|
+
if (foreignOwner.outcome !== "conflict") {
|
|
353
|
+
throw new Error("A checkpoint from another owner must conflict");
|
|
354
|
+
}
|
|
355
|
+
const recoverable = await store.scanRecoverable({ limit: 10 });
|
|
356
|
+
if (!recoverable.items.some((item) => item.run.id === running.id)) {
|
|
357
|
+
throw new Error("A running run must appear in a recoverable scan");
|
|
358
|
+
}
|
|
359
|
+
const interrupted = await store.requestRunInterrupt({
|
|
360
|
+
conversationId,
|
|
361
|
+
runId: running.id,
|
|
362
|
+
expectedRevision: checkpointedRun.revision
|
|
363
|
+
});
|
|
364
|
+
requireOutcome(interrupted, "applied");
|
|
365
|
+
const interruptedRun = interrupted.snapshot.runs.find((run) => run.id === running.id);
|
|
366
|
+
if (interruptedRun?.state !== "interrupt_requested") {
|
|
367
|
+
throw new Error("A durable interrupt must move the run to interrupt_requested");
|
|
368
|
+
}
|
|
369
|
+
if (interruptedRun.usage?.cost?.value !== 0.25) {
|
|
370
|
+
throw new Error("An interrupt must not discard the figure the run had already spent");
|
|
371
|
+
}
|
|
311
372
|
const terminalResults = await Promise.all([
|
|
312
373
|
store.commitRunTerminal({
|
|
313
374
|
conversationId,
|
|
314
375
|
runId: running.id,
|
|
315
|
-
expectedRevision:
|
|
376
|
+
expectedRevision: interruptedRun.revision,
|
|
316
377
|
ownerId: "conformance-owner",
|
|
317
378
|
assistant: terminalAssistant,
|
|
318
379
|
reason: "success",
|
|
@@ -325,7 +386,7 @@ async function runAgentStoreConformance(config) {
|
|
|
325
386
|
store.commitRunTerminal({
|
|
326
387
|
conversationId,
|
|
327
388
|
runId: running.id,
|
|
328
|
-
expectedRevision:
|
|
389
|
+
expectedRevision: interruptedRun.revision,
|
|
329
390
|
ownerId: "conformance-owner",
|
|
330
391
|
assistant: terminalAssistant,
|
|
331
392
|
reason: "success",
|
package/llms-full.txt
CHANGED
|
@@ -58,7 +58,7 @@ own, recorded as an ADR.
|
|
|
58
58
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
59
59
|
| `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
60
60
|
| `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
|
|
61
|
-
| `stitchkit/agent-runtime` | server | evolving | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
61
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 8 of the 10 minors since 0.56.2, most recently 0.65.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
62
62
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
63
63
|
| `stitchkit/application` | server | evolving | managed resource graph, readiness, admission, schedules and bounded shutdown |
|
|
64
64
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
@@ -3496,7 +3496,9 @@ mechanics: durable acceptance, history projection, the AI SDK stream loop,
|
|
|
3496
3496
|
checkpoints, keyed interruption, terminal commit and stable application events.
|
|
3497
3497
|
|
|
3498
3498
|
If the application already owns that loop, continue importing `mountAgent` from
|
|
3499
|
-
`stitchkit/tools`.
|
|
3499
|
+
`stitchkit/tools`. `stitchkit/tools` does not depend on this entrypoint at all;
|
|
3500
|
+
this entrypoint uses the tool executor from it, but pulls no MCP peer, so
|
|
3501
|
+
choosing `mountAgent` alone costs you nothing from here.
|
|
3500
3502
|
|
|
3501
3503
|
## Install
|
|
3502
3504
|
|
|
@@ -3504,6 +3506,13 @@ If the application already owns that loop, continue importing `mountAgent` from
|
|
|
3504
3506
|
bun add stitchkit ai zod
|
|
3505
3507
|
```
|
|
3506
3508
|
|
|
3509
|
+
`mountAgent` from `stitchkit/tools` — used in the composition below — additionally
|
|
3510
|
+
needs the MCP peer, which that entrypoint imports statically:
|
|
3511
|
+
|
|
3512
|
+
```sh
|
|
3513
|
+
bun add @modelcontextprotocol/server
|
|
3514
|
+
```
|
|
3515
|
+
|
|
3507
3516
|
OpenRouter is isolated so other runtime users do not resolve its package:
|
|
3508
3517
|
|
|
3509
3518
|
```sh
|
|
@@ -3543,7 +3552,7 @@ const models = defineModelRegistry({
|
|
|
3543
3552
|
},
|
|
3544
3553
|
})
|
|
3545
3554
|
|
|
3546
|
-
const prompt = composeAgentPrompt([
|
|
3555
|
+
const prompt = composeAgentPrompt<{ userId: string }>([
|
|
3547
3556
|
{
|
|
3548
3557
|
name: 'product',
|
|
3549
3558
|
stability: 'stable',
|
|
@@ -3674,15 +3683,15 @@ input + queued run → running → execution settled → terminal CAS → succes
|
|
|
3674
3683
|
|
|
3675
3684
|
## What happens to a run when new input arrives
|
|
3676
3685
|
|
|
3677
|
-
`runs.inputPolicy` decides. It takes three values
|
|
3678
|
-
the fourth row
|
|
3686
|
+
`runs.inputPolicy` decides. It takes three values, or a function returning one;
|
|
3687
|
+
the fourth row is a behaviour that shipped and was withdrawn:
|
|
3679
3688
|
|
|
3680
3689
|
| policy | the run in flight | what it already produced |
|
|
3681
3690
|
|--------|-------------------|--------------------------|
|
|
3682
3691
|
| `queue` (default) | finishes first | kept |
|
|
3683
3692
|
| `interrupt` | ends | kept, and marked as cut off |
|
|
3684
3693
|
| `supersede` | ends | discarded from the prompt, kept in the record |
|
|
3685
|
-
|
|
|
3694
|
+
| _inject_ | continues | withdrawn in 0.65.0 — see below |
|
|
3686
3695
|
|
|
3687
3696
|
`interrupt` and `supersede` differ in exactly one thing, and the question that
|
|
3688
3697
|
picks between them is **not** "was the run interrupted" but **"did anyone see
|
|
@@ -3766,37 +3775,19 @@ const { decisions } = await projectAgentHistoryDetailed(snapshot.messages)
|
|
|
3766
3775
|
process-local escape hatch chooses the reason, so a caller that knows the answer
|
|
3767
3776
|
was never delivered can discard it without a newer input arriving.
|
|
3768
3777
|
|
|
3769
|
-
### The one that
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
behaviour every other policy already has.
|
|
3783
|
-
|
|
3784
|
-
When a run does absorb one:
|
|
3785
|
-
|
|
3786
|
-
- both inputs land in the answering run's `inputMessageIds`, so the durable
|
|
3787
|
-
record matches what the model was actually asked;
|
|
3788
|
-
- both submissions' tickets resolve to the same terminal result;
|
|
3789
|
-
- the absorbed run is marked `absorbed` with `absorbedIntoRunId` pointing at the
|
|
3790
|
-
run that answered. It is kept, not deleted — its admission receipt still points
|
|
3791
|
-
at it, so a duplicate submission has to resolve to something. It leaves the
|
|
3792
|
-
conversation snapshot, because a snapshot carries active runs plus those a
|
|
3793
|
-
message references and an absorbed run never wrote an assistant message;
|
|
3794
|
-
- the next provider call carries the re-projected history. An application's own
|
|
3795
|
-
`prepareStep` wins if it sets `messages` itself — it is the one that knows why.
|
|
3796
|
-
|
|
3797
|
-
A step boundary is the only place this can happen: the provider is between calls,
|
|
3798
|
-
so the next request can carry the new message. A single-step run never reaches
|
|
3799
|
-
one, and its successor simply runs next.
|
|
3778
|
+
### The one that is not offered
|
|
3779
|
+
|
|
3780
|
+
**inject** — hand the input to the loop between tool calls and let the run
|
|
3781
|
+
continue — shipped in 0.63.0 and was **withdrawn in 0.65.0**. It committed the
|
|
3782
|
+
absorption durably at a step boundary, before the answer existed, and everything
|
|
3783
|
+
downstream of that ordering was wrong: an accepted input could end up in a state
|
|
3784
|
+
that was neither active, recoverable nor terminal, so `close()` could report
|
|
3785
|
+
`settled: true` while leaving it permanently unanswerable, and a duplicate
|
|
3786
|
+
submission of the same idempotency key was refused forever.
|
|
3787
|
+
|
|
3788
|
+
The redesign is tracked in the backlog and is not a patch to what shipped: the
|
|
3789
|
+
absorption has to commit atomically **with** the terminal record, so that a run
|
|
3790
|
+
which ends first simply leaves an ordinary queued successor behind.
|
|
3800
3791
|
|
|
3801
3792
|
With `runs.coalescePending: true`, an active lane has at most one queued
|
|
3802
3793
|
successor. Every later accepted input is atomically appended to that successor;
|
|
@@ -3918,8 +3909,16 @@ crash between the database commit and `publish`. Reconnect should load canonical
|
|
|
3918
3909
|
state. Exactly-once external delivery remains an application-owned outbox.
|
|
3919
3910
|
|
|
3920
3911
|
Durable event IDs are derived from run, event type and snapshot version. Use
|
|
3921
|
-
`advanceAgentRuntimeEventCursor` to classify
|
|
3922
|
-
|
|
3912
|
+
`advanceAgentRuntimeEventCursor` to classify delivery.
|
|
3913
|
+
|
|
3914
|
+
**`gap` is reported for transient events only**, where `sequence` is a per-run
|
|
3915
|
+
counter the runtime really does increment once per event. Durable events carry
|
|
3916
|
+
the *conversation's* version, which advances on every mutation including the many
|
|
3917
|
+
that publish nothing — checkpoints, compaction, an acceptance that has not
|
|
3918
|
+
started — so two consecutive durable events are routinely several versions apart
|
|
3919
|
+
and adjacency says nothing. A durable loss is not detectable from the cursor;
|
|
3920
|
+
reload on reconnect, which is what a bounded fire-and-forget sink asks of you
|
|
3921
|
+
anyway. `createAgentRuntimeEventSink` adds a bounded failure-isolated lifecycle and typed
|
|
3923
3922
|
projection/redaction hook. `onPublishError` records direct publisher failures without changing the
|
|
3924
3923
|
already committed run.
|
|
3925
3924
|
|
|
@@ -4046,9 +4045,33 @@ spent nothing, and an omitted object could not tell you which one you had.
|
|
|
4046
4045
|
Two costs in different currencies do not add: the sum reports `unavailable`
|
|
4047
4046
|
rather than picking a label. The core records a currency and never converts one.
|
|
4048
4047
|
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4048
|
+
**A run's figure is durable, and that is where to read it when a channel loses
|
|
4049
|
+
it.** `AgentRun.usage` is written at every checkpoint and again with the terminal
|
|
4050
|
+
record, so a crashed process leaves behind what it had already spent and a
|
|
4051
|
+
dropped event is not a lost number:
|
|
4052
|
+
|
|
4053
|
+
```ts
|
|
4054
|
+
const snapshot = await store.loadSnapshot(conversationId)
|
|
4055
|
+
const spent = snapshot.runs.find((run) => run.id === runId)?.usage
|
|
4056
|
+
```
|
|
4057
|
+
|
|
4058
|
+
Two gaps are open and are stated rather than left to be discovered:
|
|
4059
|
+
|
|
4060
|
+
- **Both event sinks are bounded and drop under load**, by arrival order — the
|
|
4061
|
+
event carrying the money is exactly as droppable as the one carrying nothing.
|
|
4062
|
+
The run record is the recovery, and this paragraph is the only place that says
|
|
4063
|
+
so.
|
|
4064
|
+
- **`AgentRuntimeResult.metrics` is `undefined` when this executor did not win
|
|
4065
|
+
the terminal race.** Read `result.run.usage` — the durable figure — rather than
|
|
4066
|
+
`result.metrics`, which is the channel that can go missing.
|
|
4067
|
+
- **Compaction spend is invisible unless you report it.** `config.history.compact`
|
|
4068
|
+
calls a model inside the turn and produces no step and no event of its own.
|
|
4069
|
+
Return `usage` from `AgentCompactionResult` and it joins the run's figure;
|
|
4070
|
+
omit it and the run under-reports by whatever summarising cost.
|
|
4071
|
+
|
|
4072
|
+
What stitchkit does **not** keep is a ledger: one figure, on the run that
|
|
4073
|
+
produced it, never aggregated and never reconciled against a provider invoice
|
|
4074
|
+
(→ ADR 0110).
|
|
4052
4075
|
|
|
4053
4076
|
### Reconciling with the provider's own accounting
|
|
4054
4077
|
|
|
@@ -4066,8 +4089,10 @@ The join is yours, and `runId` is the key:
|
|
|
4066
4089
|
await ledger.record({
|
|
4067
4090
|
runId: terminal.run.id,
|
|
4068
4091
|
conversationId: terminal.run.conversationId,
|
|
4069
|
-
|
|
4070
|
-
|
|
4092
|
+
// The durable figure, not `terminal.metrics` — that one is absent whenever
|
|
4093
|
+
// this executor did not win the terminal race.
|
|
4094
|
+
costUsd: terminal.run.usage?.cost?.value ?? null, // null when `unavailable`
|
|
4095
|
+
provenance: terminal.run.usage?.cost?.provenance ?? 'unavailable',
|
|
4071
4096
|
})
|
|
4072
4097
|
|
|
4073
4098
|
// later — the provider's accounting names the same generation
|
|
@@ -7763,6 +7788,24 @@ additive** — adopting it changes nothing in your code. (See
|
|
|
7763
7788
|
So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
|
|
7764
7789
|
current one *up to* your target, and apply each snippet.
|
|
7765
7790
|
|
|
7791
|
+
## Before you bump, if you implement an agent store
|
|
7792
|
+
|
|
7793
|
+
One step, and it is mechanical. If your project has an `AgentRuntimeStore` — a
|
|
7794
|
+
Prisma adapter, an in-memory one, anything — run the conformance kit against it
|
|
7795
|
+
**on the version you are leaving**, then again after the bump:
|
|
7796
|
+
|
|
7797
|
+
```ts
|
|
7798
|
+
import { runAgentStoreConformance } from 'stitchkit/testing'
|
|
7799
|
+
|
|
7800
|
+
await runAgentStoreConformance({ store: yourStore, conversationId: 'conformance' })
|
|
7801
|
+
```
|
|
7802
|
+
|
|
7803
|
+
Green before and red after tells you the contract grew and where, in one run,
|
|
7804
|
+
instead of one failure at a time in production. Green both times means the
|
|
7805
|
+
upgrade owes you nothing on that surface — which is the usual answer if you
|
|
7806
|
+
implement `AgentRuntimeStoreDriver` and compose the aggregate with
|
|
7807
|
+
`createAgentRuntimeStore(driver)`, the supported shape (→ ADR 0111).
|
|
7808
|
+
|
|
7766
7809
|
## Flow (agent or human)
|
|
7767
7810
|
|
|
7768
7811
|
1. **Find the current version.** In the consumer: the resolved `stitchkit` in
|
|
@@ -7801,6 +7844,122 @@ current one *up to* your target, and apply each snippet.
|
|
|
7801
7844
|
runtime): bootstrap the server, one HTTP request, and any feature you rely on
|
|
7802
7845
|
(Socket.IO connect, an MCP tool call, a multipart upload, …).
|
|
7803
7846
|
|
|
7847
|
+
## Released migration: 0.65.0
|
|
7848
|
+
|
|
7849
|
+
The largest migration of the pre-1.0 line, and most of it is the compiler
|
|
7850
|
+
pointing at things. One item is a feature withdrawal and two change behaviour
|
|
7851
|
+
with no compile error at all.
|
|
7852
|
+
|
|
7853
|
+
### A projection a provider accepts, and a policy withdrawn
|
|
7854
|
+
|
|
7855
|
+
### If you compact, or use `system-note`, you were broken and now are not
|
|
7856
|
+
|
|
7857
|
+
No code change to adopt the fix — but **check your own code if you call
|
|
7858
|
+
`projectAgentHistory` yourself.** System and summary records no longer appear in
|
|
7859
|
+
`messages`; they come back in `system` and belong in the provider's instructions
|
|
7860
|
+
channel, which is the only place `ai` accepts them:
|
|
7861
|
+
|
|
7862
|
+
```ts
|
|
7863
|
+
// before — the provider refuses this outright
|
|
7864
|
+
const messages = await projectAgentHistory(snapshot.messages)
|
|
7865
|
+
streamText({ model, messages })
|
|
7866
|
+
|
|
7867
|
+
// after
|
|
7868
|
+
const { messages, system } = await projectAgentHistoryDetailed(snapshot.messages)
|
|
7869
|
+
streamText({
|
|
7870
|
+
model,
|
|
7871
|
+
instructions: system.map((content) => ({ role: 'system', content })),
|
|
7872
|
+
messages,
|
|
7873
|
+
})
|
|
7874
|
+
```
|
|
7875
|
+
|
|
7876
|
+
If you use `createAgentRuntime`, this is handled for you.
|
|
7877
|
+
|
|
7878
|
+
### `inputPolicy: 'inject'` is withdrawn
|
|
7879
|
+
|
|
7880
|
+
```ts
|
|
7881
|
+
// before
|
|
7882
|
+
runs: { inputPolicy: 'inject' }
|
|
7883
|
+
// after
|
|
7884
|
+
runs: { inputPolicy: 'queue' }
|
|
7885
|
+
```
|
|
7886
|
+
|
|
7887
|
+
`'absorbed'`, `AgentRun.absorbedIntoRunId` and `AgentRuntimeStore.absorbQueuedRun`
|
|
7888
|
+
go with it. A driver built on `AgentRuntimeStoreDriver` needs no change.
|
|
7889
|
+
|
|
7890
|
+
### Records must agree with themselves
|
|
7891
|
+
|
|
7892
|
+
If you construct `AgentRun` values — a store double, a fixture, a migration —
|
|
7893
|
+
derive the state instead of setting it:
|
|
7894
|
+
|
|
7895
|
+
```ts
|
|
7896
|
+
// after
|
|
7897
|
+
import { runStateForTerminalReason } from 'stitchkit/agent-runtime'
|
|
7898
|
+
AgentRunSchema.parse({ …run, terminalReason: reason, state: runStateForTerminalReason(reason) })
|
|
7899
|
+
```
|
|
7900
|
+
|
|
7901
|
+
A terminal state with no reason, a queued run carrying one, and `policy_stop`
|
|
7902
|
+
without a `terminalPolicyName` are now all refused at parse time.
|
|
7903
|
+
|
|
7904
|
+
### Enum and field changes the compiler will point at
|
|
7905
|
+
|
|
7906
|
+
- `AgentTerminalReason`: `'tool_failure'` removed (never produced),
|
|
7907
|
+
`'context_overflow'` added — this runtime's own refusal when the prompt does
|
|
7908
|
+
not fit, which used to report `provider_failure`.
|
|
7909
|
+
- `AgentRunState`: `'absorbed'` removed.
|
|
7910
|
+
- `AgentRunMetrics.usage` is required.
|
|
7911
|
+
- `AgentHistoryBudgetDecision['reason']`: `'superseded'` → `'unspeakable'`.
|
|
7912
|
+
|
|
7913
|
+
### Two behaviour changes with no compile error
|
|
7914
|
+
|
|
7915
|
+
- **`loop.idleTimeoutMs` now defaults to 60 000.** A run whose provider stream
|
|
7916
|
+
goes quiet for a minute ends as `timeout` instead of holding the lane forever.
|
|
7917
|
+
Pass `null` for the old behaviour, and think about why you want it.
|
|
7918
|
+
- **`advanceAgentRuntimeEventCursor` stops returning `gap` for durable events.**
|
|
7919
|
+
If you reload a conversation on `gap`, you were reloading after essentially
|
|
7920
|
+
every run. Transient events still report it, and there it is real.
|
|
7921
|
+
|
|
7922
|
+
## Released migration: 0.64.0
|
|
7923
|
+
|
|
7924
|
+
Two changes, and only one of them can break a build. Nothing was removed.
|
|
7925
|
+
|
|
7926
|
+
### An event that says which kind it is
|
|
7927
|
+
|
|
7928
|
+
### Narrow `AgentRunEvent` on `type`
|
|
7929
|
+
|
|
7930
|
+
```ts
|
|
7931
|
+
// before
|
|
7932
|
+
for (const event of events) record(event.usage?.cost?.value ?? 0)
|
|
7933
|
+
|
|
7934
|
+
// after — and the compiler will point at every site
|
|
7935
|
+
for (const event of events) {
|
|
7936
|
+
if (event.type !== 'run-terminal') continue
|
|
7937
|
+
record(event.usage.cost.value ?? null) // `usage` is present; unknown says so
|
|
7938
|
+
}
|
|
7939
|
+
```
|
|
7940
|
+
|
|
7941
|
+
`step` is now required on `step-finished`, `terminalReason` on `run-terminal`,
|
|
7942
|
+
and `usage` on both. `queueWaitMs` exists only on `run-started`. Nothing was
|
|
7943
|
+
removed — the fields that were optional because a *different* kind of event
|
|
7944
|
+
lacked them are now simply on the kinds that have them.
|
|
7945
|
+
|
|
7946
|
+
Import `AgentRunTerminalEventSchema` (or the sibling schemas) if you construct
|
|
7947
|
+
events in tests.
|
|
7948
|
+
|
|
7949
|
+
### If you implement `AgentRuntimeStore` directly, move to the driver
|
|
7950
|
+
|
|
7951
|
+
Not urgent and nothing breaks today — but the aggregate is no longer the
|
|
7952
|
+
supported target, so its future growth will not be announced as breaking
|
|
7953
|
+
(→ ADR 0111). The supported shape is one line:
|
|
7954
|
+
|
|
7955
|
+
```ts
|
|
7956
|
+
const driver: AgentRuntimeStoreDriver<TransactionClient> = { /* six primitives */ }
|
|
7957
|
+
export const store = createAgentRuntimeStore(driver)
|
|
7958
|
+
```
|
|
7959
|
+
|
|
7960
|
+
Run `runAgentStoreConformance` against your adapter before and after any bump —
|
|
7961
|
+
see *Before you bump, if you implement an agent store* above.
|
|
7962
|
+
|
|
7804
7963
|
## Released migration: 0.63.0
|
|
7805
7964
|
|
|
7806
7965
|
Four changes to what a running system reports and how an input reaches a run
|
|
@@ -10320,7 +10479,7 @@ Server-only optional application runtime. See the
|
|
|
10320
10479
|
| `AgentRuntimeRecordIds` | _type_ | optional caller-provided input, run and assistant IDs for stable application records |
|
|
10321
10480
|
| `AgentRuntimeAdmission` | _type_ | canonical committed input, assigned run, pending assistant projection, compatibility IDs and snapshot version |
|
|
10322
10481
|
| `AgentAdmissionEventSchema` | schema | post-commit admission projection; removes store rereads but does not imply exactly-once delivery |
|
|
10323
|
-
| `AgentRunMetricsSchema` | schema | optional provenance-aware usage and timings; `partial`
|
|
10482
|
+
| `AgentRunMetricsSchema` | schema | optional provenance-aware usage and timings; `partial` says the provider never reported the run finished, so the figure beside it is not a confirmed total |
|
|
10324
10483
|
| `AgentRuntimeRecoverOptions` | _type_ | bounded paged startup recovery with context resolver and explicit evidence policy |
|
|
10325
10484
|
| `AgentRuntimeConflictError` | class | thrown when a store mutation loses to a concurrent writer — catchable by type from `stitchkit/agent-runtime` |
|
|
10326
10485
|
| `AgentSessionCloseOptions` | _type_ | `gracePeriodMs` for natural settlement, then abort, then `forceTimeoutMs` for bounded settlement after it |
|
|
@@ -10355,8 +10514,7 @@ Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentRec
|
|
|
10355
10514
|
Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
|
|
10356
10515
|
`AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
|
|
10357
10516
|
`CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
|
|
10358
|
-
`RequestRunInterrupt`, `RequestRunInterruptSchema`, `
|
|
10359
|
-
`RecoverAgentRun`, `ReplaceCompactedRange`,
|
|
10517
|
+
`RequestRunInterrupt`, `RequestRunInterruptSchema`, `runStateForTerminalReason`, `RecoverAgentRun`, `ReplaceCompactedRange`,
|
|
10360
10518
|
`ReplaceCompactedRangeSchema`, `AgentStoreMutationResult`, `AgentStoreMutationResultSchema`,
|
|
10361
10519
|
`AgentStoreAppliedSchema`, `AgentStoreConflictSchema`, `AgentStoreDuplicateSchema`,
|
|
10362
10520
|
`AgentStoreNotFoundSchema`, `AgentAdmissionReceipt`, `AgentAdmissionReceiptSchema`,
|
|
@@ -10391,6 +10549,8 @@ snapshot reload; the bounded sink isolates transport failure and supports a type
|
|
|
10391
10549
|
|
|
10392
10550
|
Managed effects and operator telemetry additionally export `AgentToolFenceConfig`,
|
|
10393
10551
|
`AgentToolFenceContext`, `AgentObservability`, `AgentRunEvent`, `AgentRunEventSchema`,
|
|
10552
|
+
`AgentRunStartedEvent`, `AgentRunStartedEventSchema`, `AgentStepFinishedEvent`,
|
|
10553
|
+
`AgentStepFinishedEventSchema`, `AgentRunTerminalEvent`, `AgentRunTerminalEventSchema`,
|
|
10394
10554
|
`AgentRunSinkConfig`, `AgentRunSinkDrop` and `AgentRunSinkError`. A monotonic run `fencingToken`
|
|
10395
10555
|
may accompany checkpoint/terminal writes and tool context; internal causes are redacted unless an
|
|
10396
10556
|
operator-only observability sink explicitly opts in.
|
package/package.json
CHANGED