stitchkit 0.62.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/llms-full.txt CHANGED
@@ -3682,7 +3682,7 @@ the fourth row below is a behaviour it deliberately does not offer yet:
3682
3682
  | `queue` (default) | finishes first | kept |
3683
3683
  | `interrupt` | ends | kept, and marked as cut off |
3684
3684
  | `supersede` | ends | discarded from the prompt, kept in the record |
3685
- | _inject_ | continues | — not supported; see below |
3685
+ | `inject` | continues | kept it takes the new input at a step boundary |
3686
3686
 
3687
3687
  `interrupt` and `supersede` differ in exactly one thing, and the question that
3688
3688
  picks between them is **not** "was the run interrupted" but **"did anyone see
@@ -3766,15 +3766,37 @@ const { decisions } = await projectAgentHistoryDetailed(snapshot.messages)
3766
3766
  process-local escape hatch chooses the reason, so a caller that knows the answer
3767
3767
  was never delivered can discard it without a newer input arriving.
3768
3768
 
3769
- ### The one that is not supported
3770
-
3771
- **inject** hand the input to the loop between tool calls and let the run
3772
- continue has no primitive. `loop.prepareStep` passes the AI SDK return type
3773
- through, so an application *can* append messages between steps, but nothing
3774
- hands `prepareStep` the pending inputs and nothing attaches an absorbed input to
3775
- the running run's `inputMessageIds`. A run that answered two messages would
3776
- carry a durable record claiming it answered one. It is reachable by hand, not
3777
- supported.
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.
3778
3800
 
3779
3801
  With `runs.coalescePending: true`, an active lane has at most one queued
3780
3802
  successor. Every later accepted input is atomically appended to that successor;
@@ -4028,6 +4050,35 @@ Usage is **not durable**. It reaches you on the operator and delivery event
4028
4050
  streams and in `AgentRuntimeResult.metrics`, and stitchkit writes no spend to the
4029
4051
  store — where a figure lives afterwards is the application's (→ ADR 0002).
4030
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.
4081
+
4031
4082
  The sink deduplicates stable event IDs by default. Cross-crash exactly-once still requires a durable
4032
4083
  outbox.
4033
4084
 
@@ -7750,6 +7801,73 @@ current one *up to* your target, and apply each snippet.
7750
7801
  runtime): bootstrap the server, one HTTP request, and any feature you rely on
7751
7802
  (Socket.IO connect, an MCP tool call, a multipart upload, …).
7752
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
+
7753
7871
  ## Released migration: 0.62.0
7754
7872
 
7755
7873
  Two groups of behaviour changes. Nothing moves an export — the surface is
@@ -10237,7 +10355,8 @@ Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentRec
10237
10355
  Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
10238
10356
  `AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
10239
10357
  `CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
10240
- `RequestRunInterrupt`, `RequestRunInterruptSchema`, `RecoverAgentRun`, `ReplaceCompactedRange`,
10358
+ `RequestRunInterrupt`, `RequestRunInterruptSchema`, `AbsorbQueuedRun`, `AbsorbQueuedRunSchema`,
10359
+ `RecoverAgentRun`, `ReplaceCompactedRange`,
10241
10360
  `ReplaceCompactedRangeSchema`, `AgentStoreMutationResult`, `AgentStoreMutationResultSchema`,
10242
10361
  `AgentStoreAppliedSchema`, `AgentStoreConflictSchema`, `AgentStoreDuplicateSchema`,
10243
10362
  `AgentStoreNotFoundSchema`, `AgentAdmissionReceipt`, `AgentAdmissionReceiptSchema`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
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",