stitchkit 0.61.0 → 0.63.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/compaction.d.ts +14 -1
- package/dist/agent-runtime/compaction.d.ts.map +1 -1
- package/dist/agent-runtime/coordinator.d.ts +23 -2
- package/dist/agent-runtime/coordinator.d.ts.map +1 -1
- package/dist/agent-runtime/events.d.ts +140 -0
- package/dist/agent-runtime/events.d.ts.map +1 -1
- package/dist/agent-runtime/history.d.ts +39 -1
- package/dist/agent-runtime/history.d.ts.map +1 -1
- package/dist/agent-runtime/observability.d.ts +4 -0
- package/dist/agent-runtime/observability.d.ts.map +1 -1
- package/dist/agent-runtime/prompt.d.ts +1 -1
- package/dist/agent-runtime/prompt.d.ts.map +1 -1
- package/dist/agent-runtime/run-execution.d.ts +4 -0
- package/dist/agent-runtime/run-execution.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-internals.d.ts +47 -0
- package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
- package/dist/agent-runtime/runtime.d.ts +1 -0
- package/dist/agent-runtime/runtime.d.ts.map +1 -1
- package/dist/agent-runtime/schemas.d.ts +208 -77
- package/dist/agent-runtime/schemas.d.ts.map +1 -1
- package/dist/agent-runtime/store-driver.d.ts +191 -0
- package/dist/agent-runtime/store-driver.d.ts.map +1 -1
- package/dist/agent-runtime/store.d.ts +583 -0
- package/dist/agent-runtime/store.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-commit.d.ts +34 -4
- package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
- package/dist/agent-runtime/terminal-status.d.ts +13 -0
- package/dist/agent-runtime/terminal-status.d.ts.map +1 -1
- package/dist/agent-runtime.d.ts +1 -1
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +399 -99
- package/dist/{index-vtjgx3vv.js → index-b1k33127.js} +25 -18
- package/dist/testing/agent-store-conformance.d.ts.map +1 -1
- package/dist/testing.js +26 -4
- package/llms-full.txt +386 -9
- package/package.json +1 -1
|
@@ -68,6 +68,7 @@ var AgentMessageStatusSchema = z.enum([
|
|
|
68
68
|
"streaming",
|
|
69
69
|
"completed",
|
|
70
70
|
"interrupted",
|
|
71
|
+
"superseded",
|
|
71
72
|
"failed"
|
|
72
73
|
]);
|
|
73
74
|
var AgentMessageSchema = z.object({
|
|
@@ -97,6 +98,8 @@ var AgentRunStateSchema = z.enum([
|
|
|
97
98
|
"interrupt_requested",
|
|
98
99
|
"completed",
|
|
99
100
|
"interrupted",
|
|
101
|
+
"superseded",
|
|
102
|
+
"absorbed",
|
|
100
103
|
"failed",
|
|
101
104
|
"cancelled",
|
|
102
105
|
"abandoned"
|
|
@@ -104,7 +107,9 @@ var AgentRunStateSchema = z.enum([
|
|
|
104
107
|
var AgentTerminalReasonSchema = z.enum([
|
|
105
108
|
"success",
|
|
106
109
|
"policy_stop",
|
|
110
|
+
"provider_stop",
|
|
107
111
|
"interrupted",
|
|
112
|
+
"superseded",
|
|
108
113
|
"cancelled",
|
|
109
114
|
"timeout",
|
|
110
115
|
"shutdown",
|
|
@@ -112,6 +117,23 @@ var AgentTerminalReasonSchema = z.enum([
|
|
|
112
117
|
"tool_failure",
|
|
113
118
|
"abandoned"
|
|
114
119
|
]);
|
|
120
|
+
var AgentUsageValueSchema = z.object({
|
|
121
|
+
value: z.number().nonnegative().optional(),
|
|
122
|
+
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
123
|
+
});
|
|
124
|
+
var AgentCostValueSchema = z.object({
|
|
125
|
+
value: z.number().nonnegative().optional(),
|
|
126
|
+
currency: z.string().length(3).optional(),
|
|
127
|
+
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
128
|
+
});
|
|
129
|
+
var AgentUsageSchema = z.object({
|
|
130
|
+
inputTokens: AgentUsageValueSchema,
|
|
131
|
+
outputTokens: AgentUsageValueSchema,
|
|
132
|
+
reasoningTokens: AgentUsageValueSchema.optional(),
|
|
133
|
+
cacheReadTokens: AgentUsageValueSchema.optional(),
|
|
134
|
+
cacheWriteTokens: AgentUsageValueSchema.optional(),
|
|
135
|
+
cost: AgentCostValueSchema.optional()
|
|
136
|
+
});
|
|
115
137
|
var AgentRunSchema = z.object({
|
|
116
138
|
schemaVersion: z.literal(1),
|
|
117
139
|
id: AgentRecordIdSchema,
|
|
@@ -124,6 +146,8 @@ var AgentRunSchema = z.object({
|
|
|
124
146
|
fencingToken: AgentRecordVersionSchema.optional(),
|
|
125
147
|
terminalReason: AgentTerminalReasonSchema.optional(),
|
|
126
148
|
terminalPolicyName: z.string().min(1).optional(),
|
|
149
|
+
absorbedIntoRunId: AgentRecordIdSchema.optional(),
|
|
150
|
+
usage: AgentUsageSchema.optional(),
|
|
127
151
|
createdAt: AgentTimestampSchema,
|
|
128
152
|
updatedAt: AgentTimestampSchema
|
|
129
153
|
});
|
|
@@ -134,23 +158,6 @@ var AgentSnapshotSchema = z.object({
|
|
|
134
158
|
messages: z.array(AgentMessageSchema),
|
|
135
159
|
runs: z.array(AgentRunSchema)
|
|
136
160
|
});
|
|
137
|
-
var AgentUsageValueSchema = z.object({
|
|
138
|
-
value: z.number().nonnegative().optional(),
|
|
139
|
-
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
140
|
-
});
|
|
141
|
-
var AgentCostValueSchema = z.object({
|
|
142
|
-
value: z.number().nonnegative().optional(),
|
|
143
|
-
currency: z.string().length(3).optional(),
|
|
144
|
-
provenance: z.enum(["provider-reported", "computed", "estimated", "unavailable"])
|
|
145
|
-
});
|
|
146
|
-
var AgentUsageSchema = z.object({
|
|
147
|
-
inputTokens: AgentUsageValueSchema,
|
|
148
|
-
outputTokens: AgentUsageValueSchema,
|
|
149
|
-
reasoningTokens: AgentUsageValueSchema.optional(),
|
|
150
|
-
cacheReadTokens: AgentUsageValueSchema.optional(),
|
|
151
|
-
cacheWriteTokens: AgentUsageValueSchema.optional(),
|
|
152
|
-
cost: AgentCostValueSchema.optional()
|
|
153
|
-
});
|
|
154
161
|
var AgentRunMetricsSchema = z.object({
|
|
155
162
|
partial: z.boolean(),
|
|
156
163
|
usage: AgentUsageSchema.optional(),
|
|
@@ -158,4 +165,4 @@ var AgentRunMetricsSchema = z.object({
|
|
|
158
165
|
ttftMs: z.number().nonnegative().optional()
|
|
159
166
|
});
|
|
160
167
|
|
|
161
|
-
export { AgentRecordIdSchema, AgentRecordVersionSchema, AgentTimestampSchema, AgentJsonObjectSchema, AgentProviderEnvelopeSchema, AgentTextPartSchema, AgentReasoningPartSchema, AgentFilePartSchema, AgentSourcePartSchema, AgentToolCallPartSchema, AgentToolResultPartSchema, AgentOpaquePartSchema, AgentControlPartSchema, AgentMessagePartSchema, AgentMessageRoleSchema, AgentMessageStatusSchema, AgentMessageSchema, AgentAssistantPlaceholderSchema, AgentRunStateSchema, AgentTerminalReasonSchema,
|
|
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 };
|
|
@@ -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,CA4Sf"}
|
package/dist/testing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentMessageSchema,
|
|
3
3
|
AgentRunSchema
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-b1k33127.js";
|
|
5
5
|
import {
|
|
6
6
|
createApplication
|
|
7
7
|
} from "./index-eabpd4tb.js";
|
|
@@ -283,12 +283,20 @@ async function runAgentStoreConformance(config) {
|
|
|
283
283
|
parts: [{ type: "text", text: "checkpoint" }],
|
|
284
284
|
createdAt: "2026-08-22T00:00:00.000Z",
|
|
285
285
|
updatedAt: "2026-08-22T00:00:01.000Z"
|
|
286
|
-
})
|
|
286
|
+
}),
|
|
287
|
+
usage: {
|
|
288
|
+
inputTokens: { value: 1000, provenance: "provider-reported" },
|
|
289
|
+
outputTokens: { value: 100, provenance: "provider-reported" },
|
|
290
|
+
cost: { value: 0.25, currency: "USD", provenance: "provider-reported" }
|
|
291
|
+
}
|
|
287
292
|
});
|
|
288
293
|
requireOutcome(checkpoint, "applied");
|
|
289
294
|
const checkpointedRun = checkpoint.snapshot.runs.find((run) => run.id === running.id);
|
|
290
295
|
if (!checkpointedRun)
|
|
291
296
|
throw new Error("Checkpointed run disappeared");
|
|
297
|
+
if (checkpointedRun.usage?.cost?.value !== 0.25) {
|
|
298
|
+
throw new Error("Checkpoint did not persist the run usage it was given");
|
|
299
|
+
}
|
|
292
300
|
const terminalAssistant = AgentMessageSchema.parse({
|
|
293
301
|
schemaVersion: 1,
|
|
294
302
|
id: running.assistantMessageId,
|
|
@@ -307,7 +315,12 @@ async function runAgentStoreConformance(config) {
|
|
|
307
315
|
expectedRevision: checkpointedRun.revision,
|
|
308
316
|
ownerId: "conformance-owner",
|
|
309
317
|
assistant: terminalAssistant,
|
|
310
|
-
reason: "success"
|
|
318
|
+
reason: "success",
|
|
319
|
+
usage: {
|
|
320
|
+
inputTokens: { value: 3000, provenance: "computed" },
|
|
321
|
+
outputTokens: { value: 300, provenance: "computed" },
|
|
322
|
+
cost: { value: 1.5, currency: "USD", provenance: "computed" }
|
|
323
|
+
}
|
|
311
324
|
}),
|
|
312
325
|
store.commitRunTerminal({
|
|
313
326
|
conversationId,
|
|
@@ -315,7 +328,12 @@ async function runAgentStoreConformance(config) {
|
|
|
315
328
|
expectedRevision: checkpointedRun.revision,
|
|
316
329
|
ownerId: "conformance-owner",
|
|
317
330
|
assistant: terminalAssistant,
|
|
318
|
-
reason: "success"
|
|
331
|
+
reason: "success",
|
|
332
|
+
usage: {
|
|
333
|
+
inputTokens: { value: 3000, provenance: "computed" },
|
|
334
|
+
outputTokens: { value: 300, provenance: "computed" },
|
|
335
|
+
cost: { value: 1.5, currency: "USD", provenance: "computed" }
|
|
336
|
+
}
|
|
319
337
|
})
|
|
320
338
|
]);
|
|
321
339
|
const terminalOutcomes = terminalResults.map((result) => result.outcome).sort();
|
|
@@ -326,6 +344,10 @@ async function runAgentStoreConformance(config) {
|
|
|
326
344
|
if (terminalApplied?.outcome !== "applied") {
|
|
327
345
|
throw new Error("Terminal race produced no applied result");
|
|
328
346
|
}
|
|
347
|
+
const settledRun = terminalApplied.snapshot.runs.find((run) => run.id === running.id);
|
|
348
|
+
if (settledRun?.usage?.cost?.value !== 1.5) {
|
|
349
|
+
throw new Error("Terminal commit did not persist the run usage it was given");
|
|
350
|
+
}
|
|
329
351
|
const compactedTerminal = await store.replaceCompactedRange({
|
|
330
352
|
conversationId,
|
|
331
353
|
expectedVersion: terminalApplied.snapshot.version,
|
package/llms-full.txt
CHANGED
|
@@ -3672,6 +3672,132 @@ process-local coordinator releases its lane only after terminal commit.
|
|
|
3672
3672
|
input + queued run → running → execution settled → terminal CAS → successor
|
|
3673
3673
|
```
|
|
3674
3674
|
|
|
3675
|
+
## What happens to a run when new input arrives
|
|
3676
|
+
|
|
3677
|
+
`runs.inputPolicy` decides. It takes three values (or a function returning one);
|
|
3678
|
+
the fourth row below is a behaviour it deliberately does not offer yet:
|
|
3679
|
+
|
|
3680
|
+
| policy | the run in flight | what it already produced |
|
|
3681
|
+
|--------|-------------------|--------------------------|
|
|
3682
|
+
| `queue` (default) | finishes first | kept |
|
|
3683
|
+
| `interrupt` | ends | kept, and marked as cut off |
|
|
3684
|
+
| `supersede` | ends | discarded from the prompt, kept in the record |
|
|
3685
|
+
| `inject` | continues | kept — it takes the new input at a step boundary |
|
|
3686
|
+
|
|
3687
|
+
`interrupt` and `supersede` differ in exactly one thing, and the question that
|
|
3688
|
+
picks between them is **not** "was the run interrupted" but **"did anyone see
|
|
3689
|
+
what it produced"**:
|
|
3690
|
+
|
|
3691
|
+
- The user pressed **stop**. The partial answer was streamed to their screen and
|
|
3692
|
+
they read it. It belongs in the conversation — dropping it makes the history
|
|
3693
|
+
lie to the model about what the human has seen. That is `interrupt`.
|
|
3694
|
+
- A newer message **superseded** the run. Whether the partial reached anyone
|
|
3695
|
+
depends on the delivery surface: a token stream shows it as it is produced, a
|
|
3696
|
+
surface that sends nothing until the run is done never showed it at all. When
|
|
3697
|
+
it reached nobody, it is not part of the conversation. That is `supersede`.
|
|
3698
|
+
|
|
3699
|
+
**Stitchkit cannot answer that question for you** — delivery belongs to the
|
|
3700
|
+
transport, and the runtime sees an abort, not a screen. Hence a declared policy
|
|
3701
|
+
(→ ADR 0108), and hence `inputPolicy` accepting a function, so one application
|
|
3702
|
+
can hold two surfaces with different rules without the core learning which is
|
|
3703
|
+
which:
|
|
3704
|
+
|
|
3705
|
+
```ts
|
|
3706
|
+
runs: {
|
|
3707
|
+
inputPolicy: (input) =>
|
|
3708
|
+
protocol.parseContext(input.context).surface === 'operator' ? 'queue' : 'supersede',
|
|
3709
|
+
}
|
|
3710
|
+
```
|
|
3711
|
+
|
|
3712
|
+
`input.context` is the **raw** context here — admission runs before the runtime
|
|
3713
|
+
parses it, so the callback narrows it itself with the protocol it already has.
|
|
3714
|
+
|
|
3715
|
+
A superseded run ends with `terminalReason: 'superseded'`, run state
|
|
3716
|
+
`'superseded'` and an assistant message of status `'superseded'`. **The record
|
|
3717
|
+
is kept** — excluded from the projection, not deleted — so an operator can see
|
|
3718
|
+
what was thrown away, and run identity, admission receipts and the terminal CAS
|
|
3719
|
+
keep the row they depend on. Compaction leaves it alone for the same reason: a
|
|
3720
|
+
turn whose answer is never spoken is not a turn that may be summarised into one,
|
|
3721
|
+
because that would both feed the discarded text to the summariser and drop the
|
|
3722
|
+
record in `replacedMessageIds`.
|
|
3723
|
+
|
|
3724
|
+
It is also outside the token budget. `selectAgentHistory` removes it with reason
|
|
3725
|
+
`'superseded'` and does not count it, so an abandoned fragment cannot push a
|
|
3726
|
+
real turn out of a context it never occupies.
|
|
3727
|
+
|
|
3728
|
+
### How an interrupted turn reaches the model
|
|
3729
|
+
|
|
3730
|
+
An interrupted turn is projected, and says so:
|
|
3731
|
+
|
|
3732
|
+
```text
|
|
3733
|
+
{ role: 'assistant', content: [
|
|
3734
|
+
{ type: 'text', text: 'We are the team, where would you like' },
|
|
3735
|
+
{ type: 'text', text: '[interrupted: this turn was cut off before it finished]' },
|
|
3736
|
+
]}
|
|
3737
|
+
```
|
|
3738
|
+
|
|
3739
|
+
`history.interruptedAssistant` chooses the form, and the difference between the
|
|
3740
|
+
first two is structural rather than cosmetic. **An assistant turn in provider
|
|
3741
|
+
history is a commitment**: the model reads its own previous turn as something it
|
|
3742
|
+
said and stays consistent with it. A system line is context.
|
|
3743
|
+
|
|
3744
|
+
| value | form | right when |
|
|
3745
|
+
|-------|------|-----------|
|
|
3746
|
+
| `assistant-marked` (default) | assistant turn plus a marker | the human read the text |
|
|
3747
|
+
| `system-note` | `[interrupted] partial response: …` as a system line | the fragment reached nobody |
|
|
3748
|
+
| `omit` | not projected at all | you want it gone from the request |
|
|
3749
|
+
|
|
3750
|
+
There is deliberately no value that reproduces what the projection used to do,
|
|
3751
|
+
which was to send the partial as an ordinary assistant turn and drop its
|
|
3752
|
+
`control` marker on the way. That was the defect, not a behaviour to stay
|
|
3753
|
+
compatible with.
|
|
3754
|
+
|
|
3755
|
+
`projectAgentHistoryDetailed` reports what reached the provider, including part
|
|
3756
|
+
types that no projected content stands for:
|
|
3757
|
+
|
|
3758
|
+
```ts
|
|
3759
|
+
const { decisions } = await projectAgentHistoryDetailed(snapshot.messages)
|
|
3760
|
+
// → { messageId: 'assistant-2', action: 'projected', reason: 'projected',
|
|
3761
|
+
// omittedParts: ['source', 'provider'] }
|
|
3762
|
+
// → { messageId: 'assistant-1', action: 'omitted', reason: 'superseded' }
|
|
3763
|
+
```
|
|
3764
|
+
|
|
3765
|
+
`runtime.stop(key, 'supersede')` is the same decision taken by hand: the
|
|
3766
|
+
process-local escape hatch chooses the reason, so a caller that knows the answer
|
|
3767
|
+
was never delivered can discard it without a newer input arriving.
|
|
3768
|
+
|
|
3769
|
+
### The one that ends nothing
|
|
3770
|
+
|
|
3771
|
+
**`inject`** hands the input to the loop between tool calls and lets the run keep
|
|
3772
|
+
going. It is right when the input *refines* rather than redirects — a correction
|
|
3773
|
+
arriving while a multi-step task is halfway through, where discarding the
|
|
3774
|
+
finished steps would be pure loss.
|
|
3775
|
+
|
|
3776
|
+
It queues like `queue`, and a run already in flight takes it on at its next step
|
|
3777
|
+
boundary. That ordering is the whole design: the tempting shape, attaching a new
|
|
3778
|
+
input straight to a running run, has a loss case with no honest answer, because
|
|
3779
|
+
the run may terminate before the loop reaches a boundary and the input would then
|
|
3780
|
+
be recorded as answered by a turn that never saw it. Queue first and absorb
|
|
3781
|
+
opportunistically, and the fallback is simply that the successor runs — the
|
|
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.
|
|
3800
|
+
|
|
3675
3801
|
With `runs.coalescePending: true`, an active lane has at most one queued
|
|
3676
3802
|
successor. Every later accepted input is atomically appended to that successor;
|
|
3677
3803
|
its `AgentRun.inputMessageIds` records the whole batch and every input ticket
|
|
@@ -3722,8 +3848,12 @@ coordinator signal. If provider completion races that revision change, the termi
|
|
|
3722
3848
|
the canonical snapshot. An already-terminal winner settles the ticket directly; a still-owned
|
|
3723
3849
|
`interrupt_requested` run is committed as `interrupted`, and unrelated aggregate-head conflicts
|
|
3724
3850
|
remain retriable while the run is active with the same owner and fencing token. A stale owner or
|
|
3725
|
-
fencing token remains a conflict. Only the execution that applies the terminal mutation
|
|
3726
|
-
terminal event
|
|
3851
|
+
fencing token remains a conflict. Only the execution that applies the terminal mutation publishes the
|
|
3852
|
+
**delivery** `terminal` event — a loser settles from canonical state without republishing the turn,
|
|
3853
|
+
and its `AgentRuntimeResult.metrics` is `undefined`. The **operator** `run-terminal` event is not
|
|
3854
|
+
gated that way: a losing execution still ran, and still spent whatever it spent, so it reports its
|
|
3855
|
+
own usage. The two channels answer to different readers — delivering a turn twice is a user's
|
|
3856
|
+
problem, and omitting a run's cost is an operator's.
|
|
3727
3857
|
`runtime.stop(key)` is the process-local signal-only escape hatch.
|
|
3728
3858
|
|
|
3729
3859
|
## Store operations
|
|
@@ -3882,10 +4012,72 @@ sending new event kinds to existing request sinks. Product events omit provider
|
|
|
3882
4012
|
causes. Operator `internalCause` is also redacted by default; an operator-only sink must explicitly
|
|
3883
4013
|
set `includeInternalCause` and own its retention policy.
|
|
3884
4014
|
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
4015
|
+
### What a run says it spent
|
|
4016
|
+
|
|
4017
|
+
Usage values carry `provider-reported`, `computed`, `estimated` or `unavailable`
|
|
4018
|
+
provenance, **per field**. Cost additionally carries an ISO currency code;
|
|
4019
|
+
OpenRouter-reported cost is normalized as USD.
|
|
4020
|
+
|
|
4021
|
+
Read the provenance before the number (→ ADR 0109):
|
|
4022
|
+
|
|
4023
|
+
- **`provider-reported`** — the provider handed us exactly this. On
|
|
4024
|
+
`step-finished`, that is what a step's figures are.
|
|
4025
|
+
- **`computed`** — a total, added up over steps. **Every figure on a terminal
|
|
4026
|
+
event is this**, tokens included: the AI SDK's `totalUsage` is a sum it
|
|
4027
|
+
performed, not a number a provider reported for the run. It is not a figure to
|
|
4028
|
+
bill against unchanged.
|
|
4029
|
+
- **`unavailable`** — nobody reported it. Not zero.
|
|
4030
|
+
|
|
4031
|
+
Two rules follow from that last one, and they differ by field on purpose:
|
|
4032
|
+
|
|
4033
|
+
- **A token total with an unreported step is a floor**, labelled `computed`. A
|
|
4034
|
+
token count is a diagnostic, and a floor is a useful one.
|
|
4035
|
+
- **A cost with an unreported step is `unavailable`, not a floor.** Money is what
|
|
4036
|
+
people bill against, and "at least $1.00" reported as `$1.00` is the same class
|
|
4037
|
+
of lie this whole section exists to remove. One step that did not report its
|
|
4038
|
+
cost makes the run's cost unknown — not smaller. It also stays unknown: later
|
|
4039
|
+
steps reporting normally cannot revive it.
|
|
4040
|
+
|
|
4041
|
+
**A terminal event always carries `usage`.** A run that ended before the provider
|
|
4042
|
+
reported anything — superseded, interrupted, timed out, shut down, failed —
|
|
4043
|
+
carries every field `unavailable`. That is deliberately different from a run that
|
|
4044
|
+
spent nothing, and an omitted object could not tell you which one you had.
|
|
4045
|
+
|
|
4046
|
+
Two costs in different currencies do not add: the sum reports `unavailable`
|
|
4047
|
+
rather than picking a label. The core records a currency and never converts one.
|
|
4048
|
+
|
|
4049
|
+
Usage is **not durable**. It reaches you on the operator and delivery event
|
|
4050
|
+
streams and in `AgentRuntimeResult.metrics`, and stitchkit writes no spend to the
|
|
4051
|
+
store — where a figure lives afterwards is the application's (→ ADR 0002).
|
|
4052
|
+
|
|
4053
|
+
### Reconciling with the provider's own accounting
|
|
4054
|
+
|
|
4055
|
+
Whether a provider bills for a call that was aborted mid-flight cannot be known
|
|
4056
|
+
inside the process: the authoritative number arrives later, from the provider's
|
|
4057
|
+
accounting. **stitchkit does not accept it back** (→ ADR 0110) — a terminal run
|
|
4058
|
+
is an absorbing state, and a write that reached it through the conversation
|
|
4059
|
+
aggregate could conflict a concurrent compaction into discarding a summary it had
|
|
4060
|
+
just paid a model to produce.
|
|
4061
|
+
|
|
4062
|
+
The join is yours, and `runId` is the key:
|
|
4063
|
+
|
|
4064
|
+
```ts
|
|
4065
|
+
// when the run terminates — write what the runtime observed
|
|
4066
|
+
await ledger.record({
|
|
4067
|
+
runId: terminal.run.id,
|
|
4068
|
+
conversationId: terminal.run.conversationId,
|
|
4069
|
+
costUsd: terminal.metrics?.usage?.cost?.value ?? null, // null when `unavailable`
|
|
4070
|
+
provenance: terminal.metrics?.usage?.cost?.provenance ?? 'unavailable',
|
|
4071
|
+
})
|
|
4072
|
+
|
|
4073
|
+
// later — the provider's accounting names the same generation
|
|
4074
|
+
await ledger.reconcile({ runId, costUsd: billed, provenance: 'provider-reported' })
|
|
4075
|
+
```
|
|
4076
|
+
|
|
4077
|
+
Record the row even when the figure is `unavailable`: that row is the evidence
|
|
4078
|
+
that a run happened and cost something nobody has counted yet, and it is what the
|
|
4079
|
+
provider's later figure attaches to. A run with no row is a run you cannot
|
|
4080
|
+
reconcile.
|
|
3889
4081
|
|
|
3890
4082
|
The sink deduplicates stable event IDs by default. Cross-crash exactly-once still requires a durable
|
|
3891
4083
|
outbox.
|
|
@@ -7609,6 +7801,190 @@ current one *up to* your target, and apply each snippet.
|
|
|
7609
7801
|
runtime): bootstrap the server, one HTTP request, and any feature you rely on
|
|
7610
7802
|
(Socket.IO connect, an MCP tool call, a multipart upload, …).
|
|
7611
7803
|
|
|
7804
|
+
## Released migration: 0.63.0
|
|
7805
|
+
|
|
7806
|
+
Four changes to what a running system reports and how an input reaches a run
|
|
7807
|
+
in flight. Nothing moves an export except one added store member, and only an
|
|
7808
|
+
application implementing `AgentRuntimeStore` directly has to touch code.
|
|
7809
|
+
|
|
7810
|
+
### A spend figure that survives, and an input that joins
|
|
7811
|
+
|
|
7812
|
+
### `AgentRuntimeStore` has a ninth member
|
|
7813
|
+
|
|
7814
|
+
Only if you implement the aggregate interface directly — an adapter built on
|
|
7815
|
+
`AgentRuntimeStoreDriver` needs no change, and the public conformance kit covers
|
|
7816
|
+
the new operation.
|
|
7817
|
+
|
|
7818
|
+
```ts
|
|
7819
|
+
// after
|
|
7820
|
+
absorbQueuedRun(input: AbsorbQueuedRun): Promise<AgentStoreMutationResult>
|
|
7821
|
+
```
|
|
7822
|
+
|
|
7823
|
+
It moves a queued successor's inputs into the run already answering, in one
|
|
7824
|
+
mutation, and marks the successor `absorbed`. Two run records change in one
|
|
7825
|
+
transaction — if your driver persists them one at a time, persist both.
|
|
7826
|
+
|
|
7827
|
+
### A provider failure says so
|
|
7828
|
+
|
|
7829
|
+
```ts
|
|
7830
|
+
// before — an upstream error arrived as a policy stop with no policy
|
|
7831
|
+
if (terminal.reason === 'policy_stop') retryLater()
|
|
7832
|
+
|
|
7833
|
+
// after
|
|
7834
|
+
if (terminal.reason === 'provider_failure') retryLater()
|
|
7835
|
+
if (terminal.reason === 'provider_stop') { /* a length cap or content filter */ }
|
|
7836
|
+
```
|
|
7837
|
+
|
|
7838
|
+
`policy_stop` now always carries the `policyName` that caused it. If you switch
|
|
7839
|
+
exhaustively on `AgentTerminalReason` or `AgentRunState`, add `'provider_stop'`
|
|
7840
|
+
and `'absorbed'`.
|
|
7841
|
+
|
|
7842
|
+
### `partial` changed meaning
|
|
7843
|
+
|
|
7844
|
+
It used to tell you which event kind you were holding. It now tells you whether
|
|
7845
|
+
the figure beside it is a confirmed total:
|
|
7846
|
+
|
|
7847
|
+
```ts
|
|
7848
|
+
// after — true when the provider never reported the run finished
|
|
7849
|
+
if (metrics.partial) treatAsFloor(metrics.usage)
|
|
7850
|
+
```
|
|
7851
|
+
|
|
7852
|
+
Terminal events for runs that were superseded, interrupted, timed out or failed
|
|
7853
|
+
before the provider finished now report `partial: true` where they reported
|
|
7854
|
+
`false`.
|
|
7855
|
+
|
|
7856
|
+
### Checkpoint metrics are a running total
|
|
7857
|
+
|
|
7858
|
+
```ts
|
|
7859
|
+
// wrong, and now wrong by more than it used to be
|
|
7860
|
+
const spent = checkpoints.reduce((total, c) => total + (c.metrics.usage?.cost?.value ?? 0), 0)
|
|
7861
|
+
// right
|
|
7862
|
+
const spent = checkpoints.at(-1)?.metrics.usage?.cost?.value
|
|
7863
|
+
```
|
|
7864
|
+
|
|
7865
|
+
### You can read a run's spend back from the store
|
|
7866
|
+
|
|
7867
|
+
`AgentRun.usage` is written at every checkpoint and with the terminal record, so
|
|
7868
|
+
a dropped observability event no longer loses the number, and a process that dies
|
|
7869
|
+
mid-stream leaves behind what it had already spent.
|
|
7870
|
+
|
|
7871
|
+
## Released migration: 0.62.0
|
|
7872
|
+
|
|
7873
|
+
Two groups of behaviour changes. Nothing moves an export — the surface is
|
|
7874
|
+
strictly additive — and every item changes what a running system reports or
|
|
7875
|
+
sends, which is what this heading is for. One of them changes a number you may
|
|
7876
|
+
already be billing against.
|
|
7877
|
+
|
|
7878
|
+
### A run reports what it spent
|
|
7879
|
+
|
|
7880
|
+
Three changes to what the runtime says about cost and tokens. Nothing moves an
|
|
7881
|
+
export; all three change numbers a running system reports, and one of them
|
|
7882
|
+
changes a number you may already be billing against.
|
|
7883
|
+
|
|
7884
|
+
#### Multi-step cost was under-reported and is now summed
|
|
7885
|
+
|
|
7886
|
+
No code change is needed to get the fix — but check any predicate that reads
|
|
7887
|
+
`provenance`:
|
|
7888
|
+
|
|
7889
|
+
```ts
|
|
7890
|
+
// before — accepted a number that was one step's cost, not the run's
|
|
7891
|
+
if (usage.cost?.provenance === 'provider-reported') bill(usage.cost.value)
|
|
7892
|
+
|
|
7893
|
+
// after — a sum stitchkit performed says so
|
|
7894
|
+
if (usage.cost && usage.cost.provenance !== 'unavailable') bill(usage.cost.value)
|
|
7895
|
+
```
|
|
7896
|
+
|
|
7897
|
+
`'computed'` means stitchkit added up provider-reported parts. It is not a guess
|
|
7898
|
+
— `'estimated'` is the word for that — but it is deliberately not
|
|
7899
|
+
`'provider-reported'`, because that label is what a caller filters on when it
|
|
7900
|
+
wants a figure it can bill against unchanged, and a sum is not one.
|
|
7901
|
+
|
|
7902
|
+
Token totals moved with it. The AI SDK's `totalUsage` is a sum *it* performed
|
|
7903
|
+
over per-step provider figures — not a run total any provider handed over — so
|
|
7904
|
+
labelling it `provider-reported` was the same overstatement. **A run total on a
|
|
7905
|
+
terminal event is always `computed`.** If you want a figure with the provider's
|
|
7906
|
+
own word on it, read `step-finished`: each step carries what that call reported.
|
|
7907
|
+
|
|
7908
|
+
#### `usage` is always present on a terminal event
|
|
7909
|
+
|
|
7910
|
+
```ts
|
|
7911
|
+
// before — absent when the run ended before the provider's `finish`
|
|
7912
|
+
const spent = event.usage?.cost?.value ?? 0 // silently 0 for a real spend
|
|
7913
|
+
|
|
7914
|
+
// after — present, and it says what it does not know
|
|
7915
|
+
event.usage?.cost?.provenance === 'unavailable' // we spent, and cannot say how much
|
|
7916
|
+
```
|
|
7917
|
+
|
|
7918
|
+
Keep the optional chaining: one `AgentRunEvent` shape covers `run-started`,
|
|
7919
|
+
`step-finished` and `run-terminal`, so `usage` stays optional on the type. The
|
|
7920
|
+
guarantee is about terminal events, and a schema shared with `run-started`
|
|
7921
|
+
cannot express it.
|
|
7922
|
+
|
|
7923
|
+
**Do not read `unavailable` as zero.** A run aborted mid-stream has spent real
|
|
7924
|
+
money that nobody has counted; a run that never reached the provider has not.
|
|
7925
|
+
Both used to look the same and now do not.
|
|
7926
|
+
|
|
7927
|
+
#### A losing executor reports its own spend
|
|
7928
|
+
|
|
7929
|
+
An execution that loses the terminal compare-and-swap now emits an operator
|
|
7930
|
+
`run-terminal` event, because it ran and it spent. If a sink treated those events
|
|
7931
|
+
as "runs this process committed", that is no longer true — `AgentRuntimeResult.metrics`
|
|
7932
|
+
is still `undefined` for a losing executor and remains the way to tell.
|
|
7933
|
+
|
|
7934
|
+
The *delivery* `terminal` event is unchanged and still fires only for the winner,
|
|
7935
|
+
so nothing delivers a turn twice.
|
|
7936
|
+
|
|
7937
|
+
### An interrupted answer stops passing as a finished one
|
|
7938
|
+
|
|
7939
|
+
Two behaviour changes, one shared cause: a run ended by a newer input used to
|
|
7940
|
+
leave its half-written answer in the conversation with no sign that it was cut
|
|
7941
|
+
off, and the next request to the provider carried it as an ordinary assistant
|
|
7942
|
+
turn.
|
|
7943
|
+
|
|
7944
|
+
#### The projection marks an interrupted turn
|
|
7945
|
+
|
|
7946
|
+
Nothing to change to adopt the fix — the default is the fixed behaviour. What to
|
|
7947
|
+
check is whether the marker is the *right* form for your surface, and the
|
|
7948
|
+
question that decides it is not "was the run interrupted" but **"did anyone see
|
|
7949
|
+
what it produced"**.
|
|
7950
|
+
|
|
7951
|
+
```ts
|
|
7952
|
+
// after — pick the form; the default is 'assistant-marked'
|
|
7953
|
+
createAgentRuntime({ history: { interruptedAssistant: 'system-note' } })
|
|
7954
|
+
```
|
|
7955
|
+
|
|
7956
|
+
- **The user pressed stop and the text was on their screen** — keep
|
|
7957
|
+
`'assistant-marked'`. The assistant turn is the truthful record of what the
|
|
7958
|
+
human read, and the model should stay consistent with it.
|
|
7959
|
+
- **The partial never reached anyone** — a surface that sends nothing until the
|
|
7960
|
+
run is done — prefer `'system-note'`. An assistant turn in provider history is
|
|
7961
|
+
a commitment the model stays consistent with; a system line is context.
|
|
7962
|
+
|
|
7963
|
+
If you pass `history.project` you own the projection outright and none of this
|
|
7964
|
+
applies — but the same question does.
|
|
7965
|
+
|
|
7966
|
+
#### A run ended by a newer input can now say so
|
|
7967
|
+
|
|
7968
|
+
```ts
|
|
7969
|
+
// before
|
|
7970
|
+
runs: { inputPolicy: 'interrupt' } // ends the run, keeps its partial answer
|
|
7971
|
+
|
|
7972
|
+
// after — for a surface where a follow-up message invalidates the answer
|
|
7973
|
+
// in flight rather than merely stopping it
|
|
7974
|
+
runs: { inputPolicy: 'supersede' } // ends the run, discards its partial answer
|
|
7975
|
+
```
|
|
7976
|
+
|
|
7977
|
+
`inputPolicy` also accepts `(input) => policy`, which is how one application
|
|
7978
|
+
gives two conversation surfaces different rules without the runtime learning
|
|
7979
|
+
which is which.
|
|
7980
|
+
|
|
7981
|
+
A superseded run terminates with `terminalReason: 'superseded'`, state
|
|
7982
|
+
`'superseded'` and an assistant message of status `'superseded'`. **If you switch
|
|
7983
|
+
exhaustively on any of those enums, add the arm** — that is the part of this
|
|
7984
|
+
release that can break a build rather than a behaviour. The record itself is
|
|
7985
|
+
kept: it is excluded from the projection, not deleted, so an operator can still
|
|
7986
|
+
see what was thrown away.
|
|
7987
|
+
|
|
7612
7988
|
## Released migration: 0.61.0
|
|
7613
7989
|
|
|
7614
7990
|
Three behaviour changes between versions. None moves an export — the surface is
|
|
@@ -9938,7 +10314,7 @@ Server-only optional application runtime. See the
|
|
|
9938
10314
|
| `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
|
|
9939
10315
|
| `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware context budget |
|
|
9940
10316
|
| `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
|
|
9941
|
-
| `createAgentSessionCoordinator` | function | strict process-local queue/interrupt lifecycle |
|
|
10317
|
+
| `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
|
|
9942
10318
|
| `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
|
|
9943
10319
|
| `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
|
|
9944
10320
|
| `AgentRuntimeRecordIds` | _type_ | optional caller-provided input, run and assistant IDs for stable application records |
|
|
@@ -9949,7 +10325,7 @@ Server-only optional application runtime. See the
|
|
|
9949
10325
|
| `AgentRuntimeConflictError` | class | thrown when a store mutation loses to a concurrent writer — catchable by type from `stitchkit/agent-runtime` |
|
|
9950
10326
|
| `AgentSessionCloseOptions` | _type_ | `gracePeriodMs` for natural settlement, then abort, then `forceTimeoutMs` for bounded settlement after it |
|
|
9951
10327
|
| `AgentSessionCloseResult` | _type_ | what `close()` achieved: `settled`, or `timedOut` with `remaining` runs still in flight. Only omitting `forceTimeoutMs` guarantees nothing is in flight on return |
|
|
9952
|
-
| `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver
|
|
10328
|
+
| `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver, explicit unresolved-file behavior, and how an interrupted turn reaches the model (`interruptedAssistant`) |
|
|
9953
10329
|
| `createAgentToolFenceLifecycle` | function | pre-effect and post-effect run ownership fence for `mountAgent` |
|
|
9954
10330
|
| `AgentRuntimeEventSchema` | schema | transient stream lifecycle plus post-commit admission/checkpoint/run-state/terminal projections |
|
|
9955
10331
|
| `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
|
|
@@ -9979,7 +10355,8 @@ Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentRec
|
|
|
9979
10355
|
Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
|
|
9980
10356
|
`AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
|
|
9981
10357
|
`CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
|
|
9982
|
-
`RequestRunInterrupt`, `RequestRunInterruptSchema`, `
|
|
10358
|
+
`RequestRunInterrupt`, `RequestRunInterruptSchema`, `AbsorbQueuedRun`, `AbsorbQueuedRunSchema`,
|
|
10359
|
+
`RecoverAgentRun`, `ReplaceCompactedRange`,
|
|
9983
10360
|
`ReplaceCompactedRangeSchema`, `AgentStoreMutationResult`, `AgentStoreMutationResultSchema`,
|
|
9984
10361
|
`AgentStoreAppliedSchema`, `AgentStoreConflictSchema`, `AgentStoreDuplicateSchema`,
|
|
9985
10362
|
`AgentStoreNotFoundSchema`, `AgentAdmissionReceipt`, `AgentAdmissionReceiptSchema`,
|
package/package.json
CHANGED