stitchkit 0.122.0 → 0.122.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 +34 -0
- package/dist/agent-runtime/deferred-tool-search.d.ts.map +1 -1
- package/dist/agent-runtime/deferred-tool-types.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +4 -3
- package/dist/entrypoints/cli.js +2 -1
- package/dist/entrypoints/tools/connections.js +7 -1
- package/dist/entrypoints/tools/mcp.js +2 -1
- package/dist/entrypoints/tools/toolkit.js +3 -2
- package/dist/index-332d0m9g.js +37 -0
- package/dist/{index-v8yqk4w7.js → index-aswegq1r.js} +3 -5
- package/dist/{index-knmnj11j.js → index-y6ye582c.js} +7 -5
- package/dist/tools/connections/mcp.d.ts.map +1 -1
- package/dist/tools/mcp/catalog.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +1 -2
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/read-operation.d.ts +21 -0
- package/dist/tools/read-operation.d.ts.map +1 -0
- package/llms/agent-runtime.2.txt +632 -745
- package/llms/agent-runtime.3.txt +733 -492
- package/llms/agent-runtime.4.txt +521 -88
- package/llms/agent-runtime.5.txt +114 -0
- package/llms/agent-runtime.txt +4 -655
- package/llms/cli.txt +12 -3
- package/llms/tools.2.txt +109 -0
- package/llms/tools.txt +30 -109
- package/llms-full.txt +47 -4
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/dist/tools/cli/repeatable-read.d.ts +0 -9
- package/dist/tools/cli/repeatable-read.d.ts.map +0 -1
package/llms/agent-runtime.3.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# stitchkit/agent-runtime — stitchkit docs slice, part 3 of
|
|
1
|
+
# stitchkit/agent-runtime — stitchkit docs slice, part 3 of 5
|
|
2
2
|
|
|
3
3
|
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
4
|
> `llms.txt` in this package.
|
|
@@ -7,539 +7,780 @@
|
|
|
7
7
|
Parts of this slice:
|
|
8
8
|
- `llms/agent-runtime.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
9
9
|
- `llms/agent-runtime.2.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
10
|
-
- `llms/agent-runtime.3.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
11
|
-
- `llms/agent-runtime.4.txt`: API reference: `stitchkit-
|
|
10
|
+
- `llms/agent-runtime.3.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
11
|
+
- `llms/agent-runtime.4.txt`: Guide: Agent application runtime (docs/guide/agent-runtime.md) · API reference: `stitchkit/agent-runtime` (docs/api/reference.md)
|
|
12
|
+
- `llms/agent-runtime.5.txt`: API reference: `stitchkit-tui` (docs/api/reference.md)
|
|
12
13
|
|
|
13
14
|
==============================================================================
|
|
14
15
|
# Guide: Agent application runtime (docs/guide/agent-runtime.md) (continued)
|
|
15
16
|
==============================================================================
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## What happens to a run when new input arrives
|
|
19
|
+
|
|
20
|
+
`runs.inputPolicy` decides. It takes five values, or a function returning one:
|
|
21
|
+
|
|
22
|
+
| policy | the run in flight | what it already produced |
|
|
23
|
+
|--------|-------------------|--------------------------|
|
|
24
|
+
| `queue` (default) | finishes first | kept |
|
|
25
|
+
| `inject` | continues, and answers the new input too | kept, and built on |
|
|
26
|
+
| `interrupt` | ends | kept, and marked as cut off |
|
|
27
|
+
| `interrupt-next` | ends; this input runs before ordinary queued work | kept, and marked as cut off |
|
|
28
|
+
| `supersede` | ends | discarded from the prompt, kept in the record |
|
|
29
|
+
|
|
30
|
+
`interrupt-next` is the explicit priority path for an urgent input. If A is
|
|
31
|
+
running, ordinary B is queued, and urgent C arrives, the coordinator aborts A,
|
|
32
|
+
waits for A to settle and then runs C before B. B keeps its durable identity and
|
|
33
|
+
eventually runs. Urgent inputs remain FIFO among themselves. They do not
|
|
34
|
+
coalesce into an ordinary pending run: that would erase the priority boundary.
|
|
35
|
+
|
|
36
|
+
`interrupt` and `supersede` differ in exactly one thing, and the question that
|
|
37
|
+
picks between them is **not** "was the run interrupted" but **"did anyone see
|
|
38
|
+
what it produced"**:
|
|
39
|
+
|
|
40
|
+
- The user pressed **stop**. The partial answer was streamed to their screen and
|
|
41
|
+
they read it. It belongs in the conversation — dropping it makes the history
|
|
42
|
+
lie to the model about what the human has seen. That is `interrupt`.
|
|
43
|
+
- A newer message **superseded** the run. Whether the partial reached anyone
|
|
44
|
+
depends on the delivery surface: a token stream shows it as it is produced, a
|
|
45
|
+
surface that sends nothing until the run is done never showed it at all. When
|
|
46
|
+
it reached nobody, it is not part of the conversation. That is `supersede`.
|
|
47
|
+
|
|
48
|
+
**Stitchkit cannot answer that question for you** — delivery belongs to the
|
|
49
|
+
transport, and the runtime sees an abort, not a screen. Hence a declared policy
|
|
50
|
+
(→ ADR 0108), and hence `inputPolicy` accepting a function, so one application
|
|
51
|
+
can hold two surfaces with different rules without the core learning which is
|
|
52
|
+
which:
|
|
18
53
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
```ts
|
|
55
|
+
runs: {
|
|
56
|
+
inputPolicy: (input) =>
|
|
57
|
+
protocol.parseContext(input.context).surface === 'operator' ? 'queue' : 'supersede',
|
|
58
|
+
}
|
|
59
|
+
```
|
|
24
60
|
|
|
25
|
-
|
|
61
|
+
`input.context` is the **raw** context here — admission runs before the runtime
|
|
62
|
+
parses it, so the callback narrows it itself with the protocol it already has.
|
|
26
63
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
A superseded run ends with `terminalReason: 'superseded'`, run state
|
|
65
|
+
`'superseded'` and an assistant message of status `'superseded'`. **The record
|
|
66
|
+
is kept** — excluded from the projection, not deleted — so an operator can see
|
|
67
|
+
what was thrown away, and run identity, admission receipts and the terminal CAS
|
|
68
|
+
keep the row they depend on. Compaction leaves it alone for the same reason: a
|
|
69
|
+
turn whose answer is never spoken is not a turn that may be summarised into one,
|
|
70
|
+
because that would both feed the discarded text to the summariser and drop the
|
|
71
|
+
record in `replacedMessageIds`.
|
|
30
72
|
|
|
31
|
-
|
|
73
|
+
It is also outside the token budget. `selectAgentHistory` removes it with reason
|
|
74
|
+
`'superseded'` and does not count it, so an abandoned fragment cannot push a
|
|
75
|
+
real turn out of a context it never occupies.
|
|
32
76
|
|
|
33
|
-
|
|
34
|
-
`step-finished`, that is what a step's figures are.
|
|
35
|
-
- **`computed`** — a total, added up over steps. **Every figure on a terminal
|
|
36
|
-
event is this**, tokens included: the AI SDK's `totalUsage` is a sum it
|
|
37
|
-
performed, not a number a provider reported for the run. It is not a figure to
|
|
38
|
-
bill against unchanged.
|
|
39
|
-
- **`unavailable`** — nobody reported it. Not zero.
|
|
77
|
+
### How an interrupted turn reaches the model
|
|
40
78
|
|
|
41
|
-
|
|
79
|
+
An interrupted turn is projected, and says so:
|
|
42
80
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
steps reporting normally cannot revive it.
|
|
81
|
+
```text
|
|
82
|
+
{ role: 'assistant', content: [
|
|
83
|
+
{ type: 'text', text: 'We are the team, where would you like' },
|
|
84
|
+
{ type: 'text', text: '[interrupted: this turn was cut off before it finished]' },
|
|
85
|
+
]}
|
|
86
|
+
```
|
|
50
87
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
`history.interruptedAssistant` chooses the form, and the difference between the
|
|
89
|
+
first two is structural rather than cosmetic. **An assistant turn in provider
|
|
90
|
+
history is a commitment**: the model reads its own previous turn as something it
|
|
91
|
+
said and stays consistent with it. A system line is context.
|
|
55
92
|
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
| value | form | right when |
|
|
94
|
+
|-------|------|-----------|
|
|
95
|
+
| `assistant-marked` (default) | assistant turn plus a marker | the human read the text |
|
|
96
|
+
| `system-note` | `[interrupted] partial response: …` as a system line | the fragment reached nobody |
|
|
97
|
+
| `omit` | not projected at all | you want it gone from the request |
|
|
58
98
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
There is deliberately no value that reproduces what the projection used to do,
|
|
100
|
+
which was to send the partial as an ordinary assistant turn and drop its
|
|
101
|
+
`control` marker on the way. That was the defect, not a behaviour to stay
|
|
102
|
+
compatible with.
|
|
62
103
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
record, so a crashed process leaves behind what it had already spent and a
|
|
66
|
-
dropped event is not a lost number:
|
|
104
|
+
`projectAgentHistoryDetailed` reports what reached the provider, including part
|
|
105
|
+
types that no projected content stands for:
|
|
67
106
|
|
|
68
107
|
```ts
|
|
69
|
-
const
|
|
70
|
-
|
|
108
|
+
const { decisions } = await projectAgentHistoryDetailed(snapshot.messages)
|
|
109
|
+
// → { messageId: 'assistant-2', action: 'projected', reason: 'projected',
|
|
110
|
+
// omittedParts: ['source', 'provider'] }
|
|
111
|
+
// → { messageId: 'assistant-1', action: 'omitted', reason: 'superseded' }
|
|
71
112
|
```
|
|
72
113
|
|
|
73
|
-
|
|
114
|
+
`runtime.stop(key, 'supersede')` is the same decision taken by hand: the
|
|
115
|
+
process-local escape hatch chooses the reason, so a caller that knows the answer
|
|
116
|
+
was never delivered can discard it without a newer input arriving.
|
|
117
|
+
|
|
118
|
+
### An input that joins a run in flight
|
|
119
|
+
|
|
120
|
+
`inject` is right when the new input **refines** rather than redirects, and the
|
|
121
|
+
steps already taken are still worth something: *"summarise this thread… actually,
|
|
122
|
+
in bullet points"* should not throw away the reading the first message paid for.
|
|
123
|
+
|
|
124
|
+
What happens:
|
|
125
|
+
|
|
126
|
+
1. The input is admitted exactly like any other — a committed user message and a
|
|
127
|
+
**queued run**, durable before anything else happens.
|
|
128
|
+
2. At the running loop's next step boundary, that input joins its prompt. Only
|
|
129
|
+
that input: an unrelated queued submission is never carried in.
|
|
130
|
+
3. When the run finishes, its terminal commit — **one transaction** — records
|
|
131
|
+
the input as one the run answered and settles the queued successor with
|
|
132
|
+
`terminalReason: 'absorbed'` and `absorbedIntoRunId` naming the run that
|
|
133
|
+
answered it. Every ticket for that successor resolves to the same answer.
|
|
134
|
+
|
|
135
|
+
Nothing durable happens between 1 and 3, and that is the design (→ ADR 0113).
|
|
136
|
+
A run that crashes, is closed, or is interrupted after taking an input on
|
|
137
|
+
commits no absorption at all, so what is left behind is an ordinary queued
|
|
138
|
+
successor — the state every other policy already produces and recovery already
|
|
139
|
+
handles. **There is no ordering in which an accepted input becomes
|
|
140
|
+
unanswerable.** Only a run that *completes* may absorb; an interrupted one took
|
|
141
|
+
the input into its prompt and then stopped, and does not get to say it answered
|
|
142
|
+
it.
|
|
143
|
+
|
|
144
|
+
An absorbed run has **no assistant message of its own** — it produced none, and
|
|
145
|
+
writing an empty one would be a record claiming otherwise. Its answer is
|
|
146
|
+
reachable through `absorbedIntoRunId`, and the store follows that pointer itself
|
|
147
|
+
when a submission arrives on the absorbed run's idempotency key, so a retry
|
|
148
|
+
after a restart returns the answer rather than an empty terminal record.
|
|
149
|
+
|
|
150
|
+
It does publish one more `run-state` event, carrying `'superseded'`. It never
|
|
151
|
+
enters the run executor, so that event is the only thing that tells a delivery
|
|
152
|
+
surface following its `runId` that it is no longer queued.
|
|
153
|
+
|
|
154
|
+
With `coalescePending`, one successor can carry several inputs, and an
|
|
155
|
+
absorption covers a successor **whole or not at all** — a partial one would
|
|
156
|
+
leave a terminal run with inputs nobody answered. Inputs that coalesce before
|
|
157
|
+
the absorbing run's last step boundary join the same absorption. One that
|
|
158
|
+
arrives after it cancels the absorption: the successor then answers all of its
|
|
159
|
+
inputs itself, and the absorbing run has answered one of them too. A duplicate
|
|
160
|
+
answer, never a missing one.
|
|
161
|
+
|
|
162
|
+
(0.63.0 shipped a version of this that committed the absorption at the step
|
|
163
|
+
boundary, before the answer existed, and it was withdrawn in 0.65.0. ADR 0113
|
|
164
|
+
records what that ordering broke.)
|
|
165
|
+
|
|
166
|
+
With `runs.coalescePending: true`, an active lane has at most one queued
|
|
167
|
+
successor. Every later accepted input is atomically appended to that successor;
|
|
168
|
+
its `AgentRun.inputMessageIds` records the whole batch and every input ticket
|
|
169
|
+
resolves to the same terminal run. Coalescing never mutates the active run.
|
|
170
|
+
|
|
171
|
+
`AbortSignal.aborted` is only a cooperation request. A successor does not begin
|
|
172
|
+
while the predecessor still owns managed callbacks. A hung predecessor blocks
|
|
173
|
+
the lane in the first version.
|
|
174
|
+
|
|
175
|
+
Shutdown is two-phase, and both budgets carry the names they carry everywhere
|
|
176
|
+
else in Stitchkit:
|
|
74
177
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`result.metrics`, which is the channel that can go missing.
|
|
82
|
-
- **Compaction spend is invisible unless you report it.** `config.history.compact`
|
|
83
|
-
calls a model inside the turn and produces no step and no event of its own.
|
|
84
|
-
Return `usage` from `AgentCompactionResult` and it joins the run's figure;
|
|
85
|
-
omit it and the run under-reports by whatever summarising cost.
|
|
86
|
-
|
|
87
|
-
What stitchkit does **not** keep is a ledger: one figure, on the run that
|
|
88
|
-
produced it, never aggregated and never reconciled against a provider invoice
|
|
89
|
-
(→ ADR 0110).
|
|
178
|
+
```ts
|
|
179
|
+
const closed = await runtime.close({ gracePeriodMs: 30_000, forceTimeoutMs: 5_000 })
|
|
180
|
+
if (!closed.settled) {
|
|
181
|
+
console.warn(`exiting with ${closed.remaining} run(s) still in flight`)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
90
184
|
|
|
91
|
-
|
|
185
|
+
`close` first rejects new process-local admissions and gives active runs
|
|
186
|
+
`gracePeriodMs` to finish on their own. Only after that budget expires does it
|
|
187
|
+
abort them with reason `shutdown`; `forceTimeoutMs` then bounds the settlement
|
|
188
|
+
wait for a non-cooperative model or tool, measured from the abort — the two
|
|
189
|
+
budgets add up rather than overlapping.
|
|
190
|
+
|
|
191
|
+
**`close()` reports what it achieved rather than promising an outcome it cannot
|
|
192
|
+
reach.** The result is `{ settled, timedOut, remaining }`: `settled` when every
|
|
193
|
+
in-flight run finished, `timedOut` with a `remaining` count when the force
|
|
194
|
+
budget expired first. There is no combination of budgets that is both bounded
|
|
195
|
+
and guaranteed to leave nothing in flight — that is the trade the budgets exist
|
|
196
|
+
to make, and the result is where you read which side of it you got:
|
|
197
|
+
|
|
198
|
+
| budgets | behaviour | can return with a run in flight |
|
|
199
|
+
|---------|-----------|---------------------------------|
|
|
200
|
+
| neither | aborts immediately, waits for settlement | no — unbounded wait |
|
|
201
|
+
| `gracePeriodMs` only | waits, aborts, then waits for settlement | no — unbounded wait |
|
|
202
|
+
| `forceTimeoutMs` only | aborts immediately, waits at most that long | **yes** — `timedOut` |
|
|
203
|
+
| both | waits, aborts, waits at most that long | **yes** — `timedOut` |
|
|
204
|
+
|
|
205
|
+
A caller that wants the old shape still writes `await runtime.close(…)` and
|
|
206
|
+
ignores the result. A caller deciding whether to exit the process reads it.
|
|
207
|
+
Durably queued records rejected from the local queue remain recoverable through
|
|
208
|
+
`scanRecoverable`; close never marks them terminal on its own.
|
|
209
|
+
|
|
210
|
+
Use `await runtime.interrupt({ conversationId, runId })` when the interruption
|
|
211
|
+
must be durable: it first commits `interrupt_requested`, then aborts the local
|
|
212
|
+
coordinator signal. If provider completion races that revision change, the terminal path reloads
|
|
213
|
+
the canonical snapshot. An already-terminal winner settles the ticket directly; a still-owned
|
|
214
|
+
`interrupt_requested` run is committed as `interrupted`, and unrelated aggregate-head conflicts
|
|
215
|
+
remain retriable while the run is active with the same owner and fencing token. A stale owner or
|
|
216
|
+
fencing token remains a conflict. Only the execution that applies the terminal mutation publishes the
|
|
217
|
+
**delivery** `terminal` event — a loser settles from canonical state without republishing the turn,
|
|
218
|
+
and its `AgentRuntimeResult.metrics` is `undefined`. The **operator** `run-terminal` event is not
|
|
219
|
+
gated that way: a losing execution still ran, and still spent whatever it spent, so it reports its
|
|
220
|
+
own usage. The two channels answer to different readers — delivering a turn twice is a user's
|
|
221
|
+
problem, and omitting a run's cost is an operator's.
|
|
222
|
+
`runtime.stop(key)` is the process-local signal-only escape hatch.
|
|
223
|
+
|
|
224
|
+
When an operator has positive evidence that another process owning one known
|
|
225
|
+
run is gone, close that run through the runtime boundary rather than updating
|
|
226
|
+
an adapter table or JSON payload directly:
|
|
92
227
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
228
|
+
```ts
|
|
229
|
+
await runtime.abandon({
|
|
230
|
+
conversationId,
|
|
231
|
+
runId,
|
|
232
|
+
expectedRevision: run.revision,
|
|
233
|
+
staleOwner: true,
|
|
234
|
+
})
|
|
235
|
+
```
|
|
99
236
|
|
|
100
|
-
The
|
|
237
|
+
The revision protects against stale operator evidence. The mutation updates the
|
|
238
|
+
canonical run and failed assistant atomically, removes the run from recovery
|
|
239
|
+
indexes, and publishes the resulting `run-state`. A run that moved since it was
|
|
240
|
+
read returns a conflict; `staleOwner: true` is mandatory evidence, not a default.
|
|
241
|
+
|
|
242
|
+
The same evidence holds inside one process when a store failure outlives the
|
|
243
|
+
run's own handling. A store call around execution that throws is retried for
|
|
244
|
+
about half a second, then decided by where the run stood. Retries bound a call
|
|
245
|
+
that fails, not one that hangs, so give the store its own timeouts:
|
|
246
|
+
|
|
247
|
+
| Failure | Outcome of `result` |
|
|
248
|
+
|---|---|
|
|
249
|
+
| reading or acquiring the run, retries spent, or a lost reply to the acquisition | resolves with reason `abandoned`, the cause on the operator's `run-terminal` |
|
|
250
|
+
| the first assistant draft | resolves with reason `runtime_failure`, like a checkpoint later in the turn |
|
|
251
|
+
| a conflict on a run another executor holds | rejects with the conflict; the record stays that executor's |
|
|
252
|
+
| a conflict on a run still queued, behind another run | rejects with `AgentRunUnsettledError`; `recover()` or `resume` picks it up |
|
|
253
|
+
| the terminal commit, or closing the run, retries spent | rejects with `AgentRunUnsettledError` |
|
|
254
|
+
|
|
255
|
+
`AgentRunUnsettledError.run` is the record as last read, and the store may be ahead
|
|
256
|
+
of it. Nothing in this process executes that run, and later turns of its
|
|
257
|
+
conversation wait behind it. `recover()` skips a `running` run with a live owner, so
|
|
258
|
+
a long-running process closes it itself once the store answers. Read the record
|
|
259
|
+
first: with no owner or with `error.ownerId` it is this runtime's, and any other
|
|
260
|
+
owner is executing it.
|
|
101
261
|
|
|
102
262
|
```ts
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
263
|
+
const settled = await ticket.result.catch(async (error) => {
|
|
264
|
+
if (!(error instanceof AgentRunUnsettledError)) throw error
|
|
265
|
+
const { conversationId, id: runId } = error.run
|
|
266
|
+
const current = await store.loadRun({ conversationId, runId })
|
|
267
|
+
if (current && [undefined, error.ownerId].includes(current.run.ownerId)) {
|
|
268
|
+
await runtime.abandon({
|
|
269
|
+
conversationId,
|
|
270
|
+
runId,
|
|
271
|
+
expectedRevision: current.run.revision,
|
|
272
|
+
staleOwner: true,
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
throw error
|
|
111
276
|
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
A `queued` run lost to a run ahead of it, and `runtime.resume()` can run it instead
|
|
280
|
+
once that one ends.
|
|
281
|
+
|
|
282
|
+
## Store operations
|
|
283
|
+
|
|
284
|
+
`AgentRuntimeStore` remains the runtime-facing aggregate. Application adapters
|
|
285
|
+
implement the smaller `AgentRuntimeStoreDriver` rather than these eleven members:
|
|
286
|
+
|
|
287
|
+
- `acceptInputAndAssignRun`
|
|
288
|
+
- `acquireRun`
|
|
289
|
+
- `checkpointRunAssistant`
|
|
290
|
+
- `requestRunInterrupt`
|
|
291
|
+
- `recoverRun`
|
|
292
|
+
- `commitRunTerminal`
|
|
293
|
+
- `replaceCompactedRange`
|
|
294
|
+
- `loadSnapshot` — the whole conversation; see **Reading a conversation** below
|
|
295
|
+
- `loadRun` — one run by id, with the answer it produced if it has ended
|
|
296
|
+
- `listActiveRuns` — the runs of one conversation that have not ended
|
|
297
|
+
- `scanRecoverable` — one **bounded page** of recoverable runs; `recover()`
|
|
298
|
+
calls this and nothing else, so an adapter that implements the interface has
|
|
299
|
+
everything recovery needs
|
|
300
|
+
|
|
301
|
+
### Writing history no run owns
|
|
302
|
+
|
|
303
|
+
`seedConversationInput` writes messages no run owns, exactly once per `seedKey`; the key is
|
|
304
|
+
recorded beside the history, so compaction or replacement of the messages never brings them
|
|
305
|
+
back. It places them by `placement`:
|
|
306
|
+
|
|
307
|
+
- absent or `'lead'` — committed user instructions ahead of the whole conversation;
|
|
308
|
+
- `'after-settled'` — an exchange that already happened without a model (a confirmed voice
|
|
309
|
+
command and its receipt): committed `user` and completed `assistant` messages. They land after
|
|
310
|
+
the settled history and ahead of the first message of any run still active, so an active run's
|
|
311
|
+
input and answer stay adjacent and the next run reads the exchange before the input queued
|
|
312
|
+
behind it. With no active run they are the tail.
|
|
112
313
|
|
|
113
|
-
|
|
114
|
-
await
|
|
314
|
+
```ts
|
|
315
|
+
await store.seedConversationInput({
|
|
316
|
+
conversationId,
|
|
317
|
+
seedKey: `voice:${commandId}`,
|
|
318
|
+
placement: 'after-settled',
|
|
319
|
+
inputs: [ownerMessage, receiptMessage],
|
|
320
|
+
})
|
|
115
321
|
```
|
|
116
322
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
323
|
+
The pair is ordinary history: it is in `loadSnapshot`, compaction may summarize it, and the next
|
|
324
|
+
provider request carries it. No model is called and no run is created.
|
|
325
|
+
|
|
326
|
+
### Reading a conversation
|
|
327
|
+
|
|
328
|
+
Two shapes of read, and the difference matters as a conversation grows.
|
|
329
|
+
|
|
330
|
+
**Bounded.** `loadRun` and `listActiveRuns` read run records and the
|
|
331
|
+
conversation head. Neither touches history, so neither grows with the length of
|
|
332
|
+
the conversation. `loadRun` is how you resolve the `runId` that
|
|
333
|
+
`submit().admission` hands back — it returns the run, the version it was read
|
|
334
|
+
at, and, once the run is terminal, the answer it produced.
|
|
335
|
+
|
|
336
|
+
**Unbounded.** `loadSnapshot` returns every message and every run, and so does
|
|
337
|
+
every mutation result: the store's reducer validates its invariants against the
|
|
338
|
+
whole conversation, and the runtime builds the next prompt from the snapshot the
|
|
339
|
+
mutation returns. Ask for it when you need the conversation — composing a
|
|
340
|
+
prompt, or compacting — and not to look one run up.
|
|
341
|
+
|
|
342
|
+
So the cost of a run scales with the length of its conversation, not with the
|
|
343
|
+
length of the turn. **Configure compaction** (see below) for anything
|
|
344
|
+
long-running: it is the only thing in the framework that makes a conversation
|
|
345
|
+
smaller, and without it a year-old assistant thread is read in full on every
|
|
346
|
+
turn. This is a known limit, held deliberately rather than by omission — paged
|
|
347
|
+
history would have to change what a snapshot *is*, and the store's invariants
|
|
348
|
+
with it, and that is a decision on its own (→ ADR 0112).
|
|
349
|
+
|
|
350
|
+
Every mutation carries an expected run revision or snapshot version. Input
|
|
351
|
+
assignment additionally carries an idempotency identity. A conflict is a
|
|
352
|
+
control outcome; stale data is never silently overwritten.
|
|
353
|
+
|
|
354
|
+
Acquisition increments an optional monotonic `fencingToken`. The managed runtime carries it through
|
|
355
|
+
checkpoint/terminal CAS and tool context, so a distributed adapter can reject an old owner even if
|
|
356
|
+
an owner label is reused. Lease expiry and renewal remain application-owned.
|
|
357
|
+
|
|
358
|
+
On startup, `runtime.recover({ resolveContext })` consumes bounded lightweight
|
|
359
|
+
pages, then restores each conversation's durable execution order before any run
|
|
360
|
+
acquires. Persisted `executionSequence` orders work that started; queued
|
|
361
|
+
`interrupt-next` work precedes ordinary queued work with FIFO preserved inside
|
|
362
|
+
each class. Scan identifiers, equal timestamps and page boundaries therefore
|
|
363
|
+
never become queue order. Its safe default resumes queued runs and reports acquired or
|
|
364
|
+
`interrupt_requested` runs as skipped. A policy may requeue acquired work only
|
|
365
|
+
with explicit replay-safe evidence, or abandon it only with stale-owner
|
|
366
|
+
evidence. Each attempted run returns its own outcome/error; a `resumed` or
|
|
367
|
+
`requeued` outcome also exposes the terminal `result` promise. The outcome is
|
|
368
|
+
reported only after durable acquisition, so a lost acquisition is `failed`
|
|
369
|
+
rather than a successful-looking handoff. `pageSize`, `maxRuns`, and `signal`
|
|
370
|
+
bound the pass. `runtime.resume(...)` remains available for one known queued
|
|
371
|
+
record; its `accepted` promise likewise means that the recovered run acquired
|
|
372
|
+
durable ownership, while `result` carries terminal completion.
|
|
373
|
+
|
|
374
|
+
Canonical records currently write `schemaVersion: 1`. A durable adapter owns
|
|
375
|
+
read-time migration of older rows: migrate to the current shape at its storage
|
|
376
|
+
boundary, validate with the exported schema, and write only the current
|
|
377
|
+
version. Core deliberately does not guess an application's database migration
|
|
378
|
+
or silently accept an unknown future version.
|
|
379
|
+
|
|
380
|
+
### Purging a conversation
|
|
121
381
|
|
|
122
|
-
|
|
123
|
-
|
|
382
|
+
```ts
|
|
383
|
+
import { purgeAgentConversation } from 'stitchkit/agent-runtime';
|
|
124
384
|
|
|
125
|
-
|
|
385
|
+
const result = await purgeAgentConversation(store, {
|
|
386
|
+
conversationId,
|
|
387
|
+
expectedVersion: snapshot.version, // optional stale-intent protection
|
|
388
|
+
});
|
|
389
|
+
```
|
|
126
390
|
|
|
127
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
391
|
+
The optional `AgentRuntimeStore.purgeConversation` capability is implemented by the memory and
|
|
392
|
+
initialized Bun/Node SQLite stores. The helper returns `unsupported` for stores without it; it
|
|
393
|
+
never pretends deletion succeeded. Other outcomes are `purged`, `already_purged`,
|
|
394
|
+
`active` (with `runIds`), or `conflict` (with `actualVersion`). Storage failures reject.
|
|
395
|
+
|
|
396
|
+
Purge refuses queued, running and interrupt-requested runs. It does not interrupt, force-abandon
|
|
397
|
+
or wait for a provider. Authorize the destructive request in the host, close its conversation
|
|
398
|
+
ingress/attachments, then settle runs through normal interruption or explicitly justified recovery.
|
|
399
|
+
Retry purge after they are terminal. An optional expected version must match the current snapshot;
|
|
400
|
+
a version conflict requires a fresh deletion decision, not blind retry with a new version.
|
|
401
|
+
|
|
402
|
+
Successful purge atomically removes messages (including compacted inactive rows), all runs and
|
|
403
|
+
retained terminal assistants, admissions/idempotency receipts, conversation heads and their owned
|
|
404
|
+
indexes. It retains only a permanent conversation-ID tombstone. Purging an unknown ID reserves it
|
|
405
|
+
as well, so a submit paused before admission cannot create it later. Repeating the call returns
|
|
406
|
+
`already_purged`, regardless of the original expected version. A lost response is safely retried
|
|
407
|
+
with the same ID; **new chats require a new ID**.
|
|
408
|
+
|
|
409
|
+
Every later runtime mutation rejects with `AgentConversationPurgedError`, including fresh and
|
|
410
|
+
duplicate admissions, checkpoints, recovery and compaction. Existing controller leases grant no
|
|
411
|
+
exception. Existing reads return empty snapshots, absent runs and empty message pages; purged
|
|
412
|
+
conversations disappear from the SQLite catalog and recovery scans. Snapshots retain their
|
|
413
|
+
existing empty `version: 0` shape; use the purge result, not an empty snapshot, as deletion evidence.
|
|
414
|
+
|
|
415
|
+
SQLite commits the tombstone and all deletions in one `BEGIN IMMEDIATE` transaction. Failure before
|
|
416
|
+
commit rolls everything back; reopen/recovery cannot restore committed deleted records. Same-thread
|
|
417
|
+
connection contention rejects promptly without effects; retry after the competing operation settles.
|
|
418
|
+
Initialization adds the tombstone table and write guards to schema v1 transactionally, without
|
|
419
|
+
changing application tables or `user_version`. Guards also fence older writers. `initialize: false`
|
|
420
|
+
on an original v1 database does not add purge capability; open an initialized writer first.
|
|
421
|
+
|
|
422
|
+
Consumer-owned model selections, projections, attachment files, delivery/outbox records and event
|
|
423
|
+
logs remain outside this boundary. Retain an authorized cleanup intent plus any needed opaque file
|
|
424
|
+
references in consumer storage, purge first, then retry idempotent consumer cleanup. Invalidate local
|
|
425
|
+
caches/subscriptions so an old delivered event cannot repopulate a UI. The library never traverses
|
|
426
|
+
paths, revokes remote artifacts or claims a cross-store transaction. Nor does logical purge securely
|
|
427
|
+
wipe database free pages, WAL or backups. Memory tombstones last only for that store instance.
|
|
428
|
+
|
|
429
|
+
Custom normalized drivers may opt in through `AgentConversationPurgeDriver` on `driver.conversations`.
|
|
430
|
+
Their transaction must serialize `isPurged`, head/active-run checks, `remove`, and **all** mutations
|
|
431
|
+
for the same ID, including absent-ID admission. An optimistic head CAS alone is insufficient. `remove`
|
|
432
|
+
must delete every owned payload/index and preserve the tombstone; errors must roll back the entire
|
|
433
|
+
transaction. Drivers without this guarantee must leave the capability absent. See ADR 0138.
|
|
434
|
+
|
|
435
|
+
## Events and reconnect
|
|
436
|
+
|
|
437
|
+
A subscriber that switches over `event.type` handles one transient event
|
|
438
|
+
beyond deltas and reasoning: `attempt-reset`, published when `loop.retry`
|
|
439
|
+
starts a new attempt, meaning everything this run streamed before it is
|
|
440
|
+
withdrawn. `reduceAgentControlEvent` does that; a hand-written reducer must,
|
|
441
|
+
or it keeps the failed attempt's partial text on screen.
|
|
442
|
+
|
|
443
|
+
`publish` receives event classes with different guarantees:
|
|
444
|
+
|
|
445
|
+
- `admission` follows a successful acceptance CAS and carries the same complete
|
|
446
|
+
projection as `ticket.admission`;
|
|
447
|
+
|
|
448
|
+
- `assistant-delta` is transient and ordered by
|
|
449
|
+
`(runId, runtimeEpoch, sequence)`;
|
|
450
|
+
- `reasoning-start`, `reasoning-delta` and `reasoning-end` are transient and
|
|
451
|
+
ordered by the same identity; delta carries only the current text fragment,
|
|
452
|
+
while provider metadata stays inside a validated canonical envelope;
|
|
453
|
+
- `assistant-checkpoint` follows a successful checkpoint CAS;
|
|
454
|
+
- `run-state` follows durable queue/acquire/interrupt transitions;
|
|
455
|
+
- `run-operation` follows a successful operation CAS. Its `operation` is the
|
|
456
|
+
durable `AgentRun.lastOperation`: `model-request` or `compaction`, with
|
|
457
|
+
`started`, `first-output`, `completed`, `failed` or `cancelled` phase and the
|
|
458
|
+
original timestamps. Concrete models use SDK call identity in an awaited model
|
|
459
|
+
middleware; global provider IDs use a runtime-generated identity during awaited
|
|
460
|
+
step preparation. Both durably admit the request before entering `doStream`, so a failed
|
|
461
|
+
write prevents the provider call. This does not claim HTTP transmission is
|
|
462
|
+
measured. First output is a
|
|
463
|
+
non-empty text/reasoning/tool-argument delta or complete tool call; metadata,
|
|
464
|
+
usage, files and sources do not count;
|
|
465
|
+
- `tool-status` is transient lifecycle presentation with JSON-safe input on
|
|
466
|
+
start and output on completion. A mounted typed failure carries the same safe
|
|
467
|
+
`{ error, retryable, details?, _hint? }` envelope as the durable result; an unknown
|
|
468
|
+
internal cause remains generic and stays in local observability only;
|
|
469
|
+
- `tool-progress` is transient: what a running tool handler reported through
|
|
470
|
+
`ctx.reportProgress` — `message`, `progress` and `total`, the fields an MCP host
|
|
471
|
+
receives — with the call's `callId` and `toolName`. It never enters the
|
|
472
|
+
model's input or the durable history. A message longer than
|
|
473
|
+
`AGENT_TOOL_PROGRESS_MESSAGE_MAX` (280) is cut, reports of one call closer
|
|
474
|
+
than 250 ms apart are dropped, and a report after the call's terminal
|
|
475
|
+
`tool-status` or after its attempt was abandoned is dropped; the terminal
|
|
476
|
+
status carries the final word. `reduceAgentControlEvent` keeps the last
|
|
477
|
+
report of each running call in `transientByRun[runId].tools[callId]` and
|
|
478
|
+
removes it on that call's `completed`, `failed` or `interrupted`. The runtime
|
|
479
|
+
hands its reporter to a call through the SDK tool context, beside its step
|
|
480
|
+
ledger; a tool that declares its own `contextSchema` keeps that contract and
|
|
481
|
+
reports nowhere;
|
|
482
|
+
- `terminal` follows the winning terminal CAS.
|
|
483
|
+
|
|
484
|
+
`loop.checkpointEveryEvents` batches ordinary stream parts (default `20`) and
|
|
485
|
+
does not batch live event delivery. Tool call/result/error/denial, approval
|
|
486
|
+
request/response and step-finish boundaries force an assistant checkpoint and
|
|
487
|
+
reset that batch. The checkpoint is awaited after the managed loop observes the
|
|
488
|
+
normalized boundary. It does **not** promise persistence before a tool side
|
|
489
|
+
effect: tool fencing supplies the real ownership checks around execution, while
|
|
490
|
+
cross-crash effect idempotency stays application-owned. The following model
|
|
491
|
+
step waits for the previous step-finish checkpoint before its own durable
|
|
492
|
+
request admission and provider invocation. → ADR 0174.
|
|
493
|
+
|
|
494
|
+
`compaction` names invocation of the configured callback, including a
|
|
495
|
+
`not_needed` context-budget check; it does not by itself say that history was
|
|
496
|
+
summarized. Operation timestamps are retained wall-clock observations and may
|
|
497
|
+
move backwards after a clock correction. Do not subtract them for duration;
|
|
498
|
+
phase order comes from durable snapshot versions/event order.
|
|
499
|
+
|
|
500
|
+
These are post-commit notifications, not a transactional outbox: a process can
|
|
501
|
+
crash between the database commit and `publish`. Reconnect should load canonical
|
|
502
|
+
state. Exactly-once external delivery remains an application-owned outbox.
|
|
503
|
+
|
|
504
|
+
### External channel ingress and delivery
|
|
505
|
+
|
|
506
|
+
The executable
|
|
507
|
+
[`external-channel-harness.ts`](../../packages/core/examples/agent-runtime/external-channel-harness.ts)
|
|
508
|
+
is the reference composition for webhooks, polling transports and local-device
|
|
509
|
+
adapters. Its generic boundary injects a runtime, durable store and delivery
|
|
510
|
+
adapter; the optional
|
|
511
|
+
[`external-channel-grammy.ts`](../../packages/core/examples/agent-runtime/external-channel-grammy.ts)
|
|
512
|
+
attaches the same ingress function to grammY polling or webhook lifecycle
|
|
513
|
+
without putting provider types in the agent runtime.
|
|
514
|
+
|
|
515
|
+
Inbound update identity, principal resolution, conversation mapping and reply
|
|
516
|
+
target are application policy. Persist and deduplicate the update before
|
|
517
|
+
`runtime.submit`, use the stored idempotency key on crash recovery, then persist
|
|
518
|
+
the admitted `runId`. A duplicate arriving after restart resolves to that same
|
|
519
|
+
mapping instead of creating another model turn.
|
|
520
|
+
|
|
521
|
+
For output, choose `terminal-only` or `streaming` explicitly. Stable durable
|
|
522
|
+
`eventId` values and transient `(runtimeEpoch, sequence)` identities become
|
|
523
|
+
outbox keys; the application store assigns causal ordinals. A bounded channel
|
|
524
|
+
is only a process-local wakeup and never the durable authority. Before sending,
|
|
525
|
+
persist one of these states:
|
|
526
|
+
|
|
527
|
+
| State | Recovery action |
|
|
528
|
+
| --- | --- |
|
|
529
|
+
| `not-dispatched` | may dispatch once |
|
|
530
|
+
| `possibly-dispatched` | reconcile with the adapter or remain unresolved |
|
|
531
|
+
| `acknowledged` | deduplicate; delivery is complete |
|
|
532
|
+
|
|
533
|
+
The agent `terminal` event and the adapter's delivery receipt are intentionally
|
|
534
|
+
different records. A successful run does not prove that a reply reached its
|
|
535
|
+
channel, and a delivery failure never rewrites the canonical agent result.
|
|
536
|
+
Shutdown stops ingress, drains accepted process-local work and leaves ambiguous
|
|
537
|
+
outbox records visible for a later reconciliation pass. Stitchkit does not own
|
|
538
|
+
a channel database, durable broker, provider payload schema or application
|
|
539
|
+
identity policy.
|
|
540
|
+
|
|
541
|
+
Durable event IDs are derived from run, event type and snapshot version. Use
|
|
542
|
+
`advanceAgentRuntimeEventCursor` to classify delivery.
|
|
543
|
+
|
|
544
|
+
**`gap` is reported for transient events only**, where `sequence` is a per-run
|
|
545
|
+
counter the runtime really does increment once per event. Durable events carry
|
|
546
|
+
the *conversation's* version, which advances on every mutation including the many
|
|
547
|
+
that publish nothing — checkpoints, compaction, an acceptance that has not
|
|
548
|
+
started — so two consecutive durable events are routinely several versions apart
|
|
549
|
+
and adjacency says nothing. A durable loss is not detectable from the cursor;
|
|
550
|
+
reload on reconnect, which is what a bounded fire-and-forget sink asks of you
|
|
551
|
+
anyway. `createAgentRuntimeEventSink` adds a bounded failure-isolated lifecycle and typed
|
|
552
|
+
projection/redaction hook. `onPublishError` records direct publisher failures without changing the
|
|
553
|
+
already committed run.
|
|
554
|
+
|
|
555
|
+
A named custom stop condition terminalizes with `policy_stop`; its `policyName`
|
|
556
|
+
is persisted on the run and included in the terminal event/result. `max-steps`
|
|
557
|
+
is the reserved built-in policy name. `loop.prepareStep` is the controlled AI
|
|
558
|
+
SDK step boundary for changing active tools, model, instructions or messages.
|
|
559
|
+
It cannot replace the managed tool set or bypass its lifecycle fence.
|
|
560
|
+
|
|
561
|
+
Context can grow between steps as tool results and deferred schemas enter the
|
|
562
|
+
provider prompt. When application budgeting can prove that the next assembled
|
|
563
|
+
step exceeds the selected model window, refuse it by type before that provider
|
|
564
|
+
call:
|
|
134
565
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
566
|
+
```ts
|
|
567
|
+
prepareStep: (step) => {
|
|
568
|
+
if (wouldExceedSelectedModelWindow(step)) {
|
|
569
|
+
throw new AgentContextOverflowError('Prepared step exceeds the selected model window')
|
|
570
|
+
}
|
|
571
|
+
return chooseProductStepOptions(step)
|
|
572
|
+
}
|
|
573
|
+
```
|
|
140
574
|
|
|
141
|
-
|
|
575
|
+
That deliberate refusal ends the run as `context_overflow` on the durable
|
|
576
|
+
record, delivery terminal and operator event. Stitchkit does not inspect error
|
|
577
|
+
messages — it recognises failures by identity, not by text. A store refusing an
|
|
578
|
+
owned mutation ends the run `storage_conflict`; `protocol.acceptTerminal`
|
|
579
|
+
refusing a finished message ends it `output_rejected`; a check the SDK runs on
|
|
580
|
+
what this runtime handed it ends it `runtime_failure`. Every error the runtime
|
|
581
|
+
cannot identify as its own remains `provider_failure`, and operator-only
|
|
582
|
+
observability retains its original cause in all of them.
|
|
142
583
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
runtime store, not a second database. The completion path sends exactly one user
|
|
146
|
-
prompt, no framework instructions and no tools. It never starts an agent loop.
|
|
584
|
+
Completion validity belongs to the protocol and is checked before the terminal
|
|
585
|
+
CAS. Protocols that require a visible answer opt in explicitly:
|
|
147
586
|
|
|
148
587
|
```ts
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
authorize: async ({ context, conversationId, action, trace }) => {
|
|
156
|
-
const session = await authenticate(context);
|
|
157
|
-
await authorizeConversation(session, conversationId, action, trace);
|
|
158
|
-
return { subject: session.subject }; // verified identity, never a body field
|
|
159
|
-
},
|
|
160
|
-
});
|
|
588
|
+
const protocol = defineAgentProtocol({
|
|
589
|
+
context: ContextSchema,
|
|
590
|
+
inputMetadata: InputMetadataSchema,
|
|
591
|
+
terminalAcceptance: 'require-output',
|
|
592
|
+
})
|
|
593
|
+
```
|
|
161
594
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
595
|
+
The built-in rule accepts non-blank text, generated files, structured provider
|
|
596
|
+
parts, and a named tool-only `policy_stop`. The default is `allow-empty` for
|
|
597
|
+
protocols where an empty acknowledgement is meaningful. Pass a callback when
|
|
598
|
+
the product has a narrower definition; it receives the candidate message,
|
|
599
|
+
terminal reason and optional policy name, and runs before persistence. A false
|
|
600
|
+
result fails the candidate instead of rewriting an already committed success.
|
|
601
|
+
|
|
602
|
+
`loop.idleTimeoutMs` measures provider silence. It starts immediately before the
|
|
603
|
+
provider call, resets on stream activity, and pauses while local tools execute.
|
|
604
|
+
Resource preparation, child waits and approval waiting do not spend this budget.
|
|
605
|
+
The host gives tools their own deadlines. A stalled provider still aborts with
|
|
606
|
+
durable reason `timeout`; caller interruption and shutdown reach tools directly.
|
|
607
|
+
|
|
608
|
+
Reconnect loads the durable snapshot. Missing transient deltas do not mean the
|
|
609
|
+
canonical result was lost. Exactly-once external delivery requires the
|
|
610
|
+
application's transactional outbox or stable-ID deduplication.
|
|
611
|
+
|
|
612
|
+
## Managed tools
|
|
613
|
+
|
|
614
|
+
Durable `step` bodies return lossless JSON: finite numbers (excluding negative zero),
|
|
615
|
+
strings, booleans, null, dense arrays and plain data objects. Return `null` explicitly
|
|
616
|
+
for effect-only bodies and convert Date values to strings. Unsupported values are
|
|
617
|
+
refused after the body, so external effects still require idempotency. First results
|
|
618
|
+
and replay are detached snapshots; mutating a returned object cannot rewrite a record.
|
|
619
|
+
The optional [sandbox coding profile](sandbox.md#coding-tool-integration) reuses the
|
|
620
|
+
same tool mounting, authorization and output handling for namespace-backed commands.
|
|
621
|
+
|
|
622
|
+
Always compose `toolFenceLifecycle` into `mountAgent`. It checks ownership
|
|
623
|
+
before a managed side effect and again before accepting its result. Fence loss
|
|
624
|
+
uses an internal control signal: it stops the old loop and is not sent to the
|
|
625
|
+
model as a tool error.
|
|
626
|
+
|
|
627
|
+
`mountAgent` sends failed runner outcomes through the AI SDK's tool-error
|
|
628
|
+
channel. AgentRuntime persists them with `outcome: 'error'`, publishes the same
|
|
629
|
+
safe envelope, and replays them as an error result on later turns. Classification
|
|
630
|
+
comes from the execution channel, never from inspecting output fields: a valid
|
|
631
|
+
successful result such as `{ error: 'domain value' }` remains successful. Typed
|
|
632
|
+
`AppError` codes and safe details may reach the model; arbitrary thrown causes
|
|
633
|
+
are retained for local hooks and reduced to `INTERNAL_SERVER_ERROR` outside.
|
|
634
|
+
|
|
635
|
+
The framework cannot undo an already-started non-cooperative external effect.
|
|
636
|
+
Pass the stable call/run idempotency identity into business mutations when the
|
|
637
|
+
effect must be replay-safe.
|
|
638
|
+
|
|
639
|
+
## History, provider metadata and files
|
|
640
|
+
|
|
641
|
+
`projectAgentHistory` converts canonical engine records into provider-valid AI
|
|
642
|
+
SDK messages and pairs tool calls/results in persisted causal order. Adjacent
|
|
643
|
+
parallel calls stay in one assistant round and their adjacent results stay in
|
|
644
|
+
one tool round; a dependent call after those results starts a new assistant
|
|
645
|
+
round, and trailing final text remains after the last tool result. Provider-required metadata is kept
|
|
646
|
+
in a versioned opaque envelope and omitted from product delivery by default.
|
|
647
|
+
`projectAgentHistoryDetailed` additionally returns one inspectable decision per canonical record;
|
|
648
|
+
leading assistant records, crash drafts and unmatched tool chronology are never silently passed to
|
|
649
|
+
the provider.
|
|
650
|
+
|
|
651
|
+
`selectAgentHistory` is the non-destructive context-window selector. It removes only whole oldest
|
|
652
|
+
complete turns, protects system/summary, incomplete and configured recent turns, and reports every
|
|
653
|
+
keep/remove reason with measured/estimated/unavailable token provenance. An oversized protected turn
|
|
654
|
+
returns `oversized`; unavailable accounting returns `unavailable` without invented arithmetic.
|
|
655
|
+
|
|
656
|
+
`ComposedAgentPrompt.instructions` accepts the AI SDK `Instructions` contract.
|
|
657
|
+
Use `adaptInstructions` when a provider needs metadata on the system message:
|
|
658
|
+
|
|
659
|
+
```ts
|
|
660
|
+
prompt({
|
|
661
|
+
context,
|
|
662
|
+
signal,
|
|
663
|
+
adaptInstructions: (content) => ({
|
|
664
|
+
role: 'system',
|
|
665
|
+
content,
|
|
666
|
+
providerOptions: { openrouter: { cacheControl: { type: 'ephemeral' } } },
|
|
180
667
|
}),
|
|
181
|
-
}
|
|
182
|
-
|
|
668
|
+
})
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Attachments and generated files remain application-owned. The runtime only
|
|
672
|
+
stores a reference returned by `persistGeneratedFile`; it never silently puts
|
|
673
|
+
base64 blobs into the neutral history store.
|
|
674
|
+
|
|
675
|
+
To send a stored attachment back to a multimodal model, configure
|
|
676
|
+
`history.resolveFile`. It maps the neutral file reference to AI SDK file data
|
|
677
|
+
(URL, bytes, provider reference or text). Without a resolver the
|
|
678
|
+
`unresolvedFile` policy applies, and it is **`omit`** by default: the file part
|
|
679
|
+
simply does not reach the provider. Choose `text` for a describing placeholder —
|
|
680
|
+
the filename or media type, never the storage reference, because that string is
|
|
681
|
+
an address inside your infrastructure and this content travels upstream — or
|
|
682
|
+
`error` to fail loudly. The `error` message does name the reference: it is
|
|
683
|
+
thrown into your process, where you are owed the whole story.
|
|
684
|
+
|
|
685
|
+
## Compaction
|
|
686
|
+
|
|
687
|
+
`structuredCompaction` summarizes complete provider-valid turn groups outside
|
|
688
|
+
the store lock, then calls `replaceCompactedRange` against the exact snapshot
|
|
689
|
+
version. A concurrent input produces `conflict`; the stale summary is not
|
|
690
|
+
applied. Summary records use the dedicated `summary` role and project to a
|
|
691
|
+
provider system message. The consumer supplies the structured summary schema
|
|
692
|
+
and prompt. Pass `previousSummary` for a direct call, or configure
|
|
693
|
+
`readPreviousSummary` for runtime-managed compaction, to merge and atomically
|
|
694
|
+
replace a leading summary on the next compaction.
|
|
695
|
+
Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
|
|
696
|
+
eligible range and recomputes the summary; the stale summary is never retried.
|
|
697
|
+
|
|
698
|
+
### Recovering a prefix after a failed turn
|
|
699
|
+
|
|
700
|
+
The default evidence policy omits failed assistants. A failed turn therefore stops the
|
|
701
|
+
compactable prefix, including completed turns after it. A terminal failure is not an
|
|
702
|
+
unanswered tool call, but opting its partial evidence into a summary is an explicit host
|
|
703
|
+
decision. Use the existing `evidencePolicy: { failedAssistant: 'assistant-marked' }` on
|
|
704
|
+
`structuredCompaction` (or `selectCompactableHistory`) when that evidence is wanted.
|
|
705
|
+
The same policy belongs on the summarizer's history projection and on any corresponding
|
|
706
|
+
token-budget selection. Stored statuses remain unchanged.
|
|
707
|
+
|
|
708
|
+
The summarizer receives **raw** `eligibleMessages`, not provider messages and not an
|
|
709
|
+
automatic failure marker. Project them with the same policy before sending them to a
|
|
710
|
+
model, and preserve failed outcomes in the application's structured summary and prompt:
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
const evidencePolicy: AgentHistoryEvidencePolicy = {
|
|
714
|
+
failedAssistant: 'assistant-marked',
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Inside structuredCompaction({ evidencePolicy, ... }).summarize:
|
|
718
|
+
const projected = await projectAgentHistoryDetailed(eligibleMessages, {
|
|
719
|
+
evidencePolicy,
|
|
720
|
+
incompleteToolTurn: 'error',
|
|
721
|
+
})
|
|
722
|
+
const failedMessageIds = eligibleMessages
|
|
723
|
+
.filter(message => message.role === 'assistant' && message.status === 'failed')
|
|
724
|
+
.map(message => message.id)
|
|
725
|
+
// Pass projected.messages, projected.system and failedMessageIds to the host's
|
|
726
|
+
// summarizer. Its schema/prompt must retain failures and uncertain effects as
|
|
727
|
+
// such, merge previousSummary, and never describe partial output as success.
|
|
728
|
+
```
|
|
183
729
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
730
|
+
An opted-in failed assistant projects an explicit non-success marker even when it has
|
|
731
|
+
no text or only control parts. A dangling call, unresolved approval, invalid result
|
|
732
|
+
chronology or a user input with no assistant remains a barrier under either policy.
|
|
733
|
+
Raw whole-turn completeness does not guarantee that every record can be projected:
|
|
734
|
+
for example, a call and its result split across records may be rejected by the projector.
|
|
735
|
+
`incompleteToolTurn: 'error'` makes that rejection stop summarization; the original history
|
|
736
|
+
stays canonical. A host with such records must use a status-aware summarizer that explicitly
|
|
737
|
+
represents every relevant record, or retain the range. Do not silently summarize an omitted
|
|
738
|
+
exchange, filter the failed record out of the replacement range, rewrite its status or skip
|
|
739
|
+
over an incomplete turn. The replacement remains one contiguous prefix under snapshot CAS.
|
|
740
|
+
|
|
741
|
+
Provider chronology and external effect authority answer different questions (ADR 0269).
|
|
742
|
+
A matching `tool-result` with `outcome: 'error'` and an application code such as `UNKNOWN`
|
|
743
|
+
closes the provider exchange; it does **not** prove the external effect failed or succeeded.
|
|
744
|
+
Compaction changes the model-history projection, not the canonical event journal or the
|
|
745
|
+
retained terminal admission receipt. The `durability/effect` intent, accepted proof and
|
|
746
|
+
uncertain outcome remain available after compaction and archive restore. A reconstructed
|
|
747
|
+
`createLocalStepDurability` using the original `(conversationId, runId, effectName)` returns
|
|
748
|
+
the accepted proof, reconciles a bare intent, or returns `uncertain` without calling `run`
|
|
749
|
+
again. It does not deduplicate a new run or a newly named effect. Standalone effect callers
|
|
750
|
+
still own the exclusive execution lease; sharing a ledger is not an atomic claim.
|
|
751
|
+
An application-owned receipt outside that journal retains its own recovery rules and must
|
|
752
|
+
not be discarded because a history turn became compactable.
|
|
753
|
+
|
|
754
|
+
### Compacting without the store
|
|
755
|
+
|
|
756
|
+
The *selection* inside compaction needs no store, no version and no runtime, so
|
|
757
|
+
it is published on its own as `selectCompactableHistory` — for an application
|
|
758
|
+
that drives the model itself and keeps its own history (→ ADR 0142):
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
import { selectCompactableHistory } from 'stitchkit/agent-runtime'
|
|
762
|
+
|
|
763
|
+
const { leadingSummary, compactable, retained } = selectCompactableHistory({
|
|
764
|
+
messages,
|
|
765
|
+
keepRecentTurns: 3,
|
|
766
|
+
})
|
|
767
|
+
if (compactable.length > 0) {
|
|
768
|
+
const summary = await summarize(compactable, leadingSummary)
|
|
769
|
+
messages = [summary, ...retained] // replace the old summary, never stack one
|
|
770
|
+
}
|
|
188
771
|
```
|
|
189
772
|
|
|
190
|
-
`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
whose process died stays claimed: inspect the receipt before choosing a new key.
|
|
196
|
-
|
|
197
|
-
The agent retains its existing prompt, tools and loop. Both modes produce
|
|
198
|
-
`invocation/started`, `provider/request`, `provider/response` and
|
|
199
|
-
`invocation/finished` records through `read`. Each attempt has its own ID; response
|
|
200
|
-
usage is counted once and keeps `unavailable` distinct from measured zero. Requested
|
|
201
|
-
registry selection, sent adapter identity and effective provider identity are
|
|
202
|
-
separate. Effective identity is null when not reported. A gateway's hidden retries
|
|
203
|
-
are not visible as additional attempts.
|
|
204
|
-
|
|
205
|
-
`currentModelInvocationAttempt()` returns the causal context inside a provider's
|
|
206
|
-
`fetch` callback or streamed read. Use it to join a host transport audit to
|
|
207
|
-
`operationId`, `invocationId`, `attemptId`, optional `runId` and `step`.
|
|
208
|
-
`onAttempt` is an awaited admission hook; a rejected hook prevents the provider call.
|
|
209
|
-
SDK payload hashes are not represented as hashes of HTTP wire bytes.
|
|
210
|
-
|
|
211
|
-
Prompts and output are AES-256-GCM payload artifacts. `read` returns references and
|
|
212
|
-
hashes; `readPayload({ conversationId, artifactId }, context)` separately authorizes
|
|
213
|
-
`read-payload` and checks authentication tags and hashes. `payloadKey` must be 32
|
|
214
|
-
bytes and is copied at construction. Keep it outside the event store/backups; retain
|
|
215
|
-
the appropriate key when restoring an archive. `maxPayloadBytes` defaults to 4 MiB
|
|
216
|
-
(range 1 KiB–64 MiB). Streaming output evidence keeps parts up to half of it; past
|
|
217
|
-
that the evidence carries `truncated: { parts, sha256 }` over the whole stream, and
|
|
218
|
-
the answer itself is never cut. A receipt that cannot be written never changes an
|
|
219
|
-
outcome: `complete()` returns it with `receiptError` beside it, and a succeeded
|
|
220
|
-
answer keeps its `text`. Request headers
|
|
221
|
-
are excluded. Protect existing agent history independently: enabling receipts does
|
|
222
|
-
not encrypt its ordinary conversation records.
|
|
223
|
-
|
|
224
|
-
The built-in memory and SQLite stores implement `appendEventOnce`. Custom stores
|
|
225
|
-
must implement its once-only check and append atomically, returning the original
|
|
226
|
-
event on a duplicate key scoped by conversation and kind. A factory without this
|
|
227
|
-
capability refuses receipt setup. The driver-backed implementation finds the keyed
|
|
228
|
-
event by its identity through the driver's optional `events.find` — one index probe
|
|
229
|
-
in SQLite — so admission costs the same on a long conversation as on a new one; a
|
|
230
|
-
custom driver without `find` falls back to paging the log in one transaction.
|
|
231
|
-
`store.findEventOnce({ conversationId, kind, key })` reads the same event back.
|
|
232
|
-
Invocation payload artifacts are keyed by their artifact id the same way.
|
|
233
|
-
|
|
234
|
-
### Migration from direct SDK completions
|
|
235
|
-
|
|
236
|
-
**Who must act:** applications opting into shared completion/agent receipts.
|
|
237
|
-
Replace the direct `generateText` call with `invocations.complete`, configure host
|
|
238
|
-
authorization and a retained payload key, and pass the same ledger in the agent's
|
|
239
|
-
`invocations` option. Use the returned invocation ID and the paginated `read` method
|
|
240
|
-
for receipt queries; do not import runtime internals. Available since 0.100.0;
|
|
241
|
-
existing runtime configurations need no migration.
|
|
773
|
+
`compactable` is the oldest **whole complete** turns and `retained` is
|
|
774
|
+
everything the model must still hear; together they are the input, in order. A
|
|
775
|
+
turn holding a tool call whose result never arrived is never eligible, and
|
|
776
|
+
neither is anything after it: half a turn hands the provider a call with no
|
|
777
|
+
result, which most of them refuse outright.
|
|
242
778
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
779
|
+
`structuredCompaction` calls this same function, so the published selection and
|
|
780
|
+
the runtime's cannot drift into two behaviours. What it adds is the part that
|
|
781
|
+
does need the store — writing the result back under a version check.
|
|
246
782
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
| Export | Kind | Summary |
|
|
253
|
-
|--------|------|---------|
|
|
254
|
-
| `createAgentRuntime` | function | compose durable acceptance, stream loop, checkpoints, coordination, managed tools and winner-only terminal publication; reconciles same-owner terminal/interrupt/head CAS races before releasing the lane |
|
|
255
|
-
| `createDeferredAgentToolSurface` | function | bounded canonical catalog search plus durable same-run activation of direct `mountAgent` tools; supports one immutable surface or a finite identity-specific registry |
|
|
256
|
-
| `DeferredAgentToolSearchInputSchema` / `DeferredAgentToolMatchSchema` / `DeferredAgentToolReceiptSchema` | schema | bounded search input, public match and versioned durable selection receipt |
|
|
257
|
-
| `DeferredAgentToolSurfaceConfig` / `DeferredAgentToolCommonConfig` / `DeferredAgentToolSurfaceDefinition` / `DeferredAgentToolController` | _type_ | finite catalogs, selector, pin, budget and `mount`/`prepareStep` composition contracts |
|
|
258
|
-
| `DeferredAgentRuntimeToolDefinition` / `DeferredAgentToolMountConfig` | _type_ | peer-free Agent-only runtime definition and canonical mount configuration accepted by the controller |
|
|
259
|
-
| `DeferredAgentToolManifestEntry` / `DeferredAgentToolSearchContext` / `DeferredAgentToolReceipt` | _type_ | canonical selector input and inferred durable receipt records |
|
|
260
|
-
| `DeferredAgentToolEvent` | _type_ | PII-free search/step evidence with surface, provenance, counts, canonical schema bytes and ceilings |
|
|
261
|
-
| `defineAgentProtocol` | function | declare context, input metadata, canonical parts and optional pre-CAS terminal acceptance (`allow-empty`, `require-output` or callback) |
|
|
262
|
-
| `hasAgentTerminalOutput` | function | generic `require-output` predicate for non-blank text, generated files, structured provider parts and explicit tool-only policy stops |
|
|
263
|
-
| `AgentMessageSchema` / `AgentRunSchema` / `AgentSnapshotSchema` | schema | versioned canonical engine records |
|
|
264
|
-
| `AgentRunQueuePrioritySchema` | schema | durable opt-in priority for queued `interrupt-next` runs |
|
|
265
|
-
| `AgentRuntimeStore` | _type_ | aggregate CAS transaction boundary for message, run and compaction mutations |
|
|
266
|
-
| `BranchAgentConversationInputSchema` / `BranchAgentConversationInput` | schema / _type_ | strict branch request: stable request key, empty destination, settled conversation boundary or archive bytes, fresh artifact authorization |
|
|
267
|
-
| `AgentConversationBranchReceiptSchema` / `AgentConversationBranchReceipt` | schema / _type_ | durable lineage: request fingerprint, source/destination identities, source sequence/version/digest and creation time |
|
|
268
|
-
| `AgentConversationBranchResultSchema` / `AgentConversationBranchResult` | schema / _type_ | `created` or `duplicate` with the same durable receipt; `AgentRuntimeStore.branchConversation` is optional for hand-written stores |
|
|
269
|
-
| `createAgentRuntimeStore` | function | build the aggregate store from one coherent transaction driver; framework owns every state transition |
|
|
270
|
-
| `AgentRuntimeStoreDriver` | _type_ | ORM-neutral transaction over a bounded head, normalized runs/admissions, product history, a **required** `events: { append, list }` ledger table, an optional `archive: { export, import }` for driver-owned durable payloads, and indexed run recovery; optional `{ access: 'read' }` selects a coherent read transaction while absent options remain write-safe |
|
|
271
|
-
| `recordAgentRetryDecision` / `AgentRetryPolicy` | function / _type_ | `loop.retry` policy and the ledger record of a retry decision: explicit retryability and backoff at a durable provider-step boundary |
|
|
272
|
-
| `AgentRuntimeHeadSchema` | schema | constant-size conversation identity plus monotonic runtime version |
|
|
273
|
-
| `AgentStoredRunSchema` | schema | canonical normalized run with an optional retained terminal assistant |
|
|
274
|
-
| `AgentAdmissionReceiptSchema` | schema | durable idempotency receipt with canonical input and assigned run/assistant identities |
|
|
275
|
-
| `AgentHistoryMutationSchema` | schema | typed canonical message mutation applied inside the winning state transaction |
|
|
276
|
-
| `RecoverAgentRunSchema` | schema | explicit abandon/requeue recovery decision; acquired runs require replay-safe evidence |
|
|
277
|
-
| `createMemoryAgentRuntimeStore` | function | process-local reference adapter, not production durability |
|
|
278
|
-
| `purgeAgentConversation` | function | dispatch optional atomic deletion; returns `unsupported`, `active`, `conflict`, `purged` or `already_purged` |
|
|
279
|
-
| `AgentConversationPurgeInputSchema` / `AgentConversationPurgeInput` | schema / _type_ | conversation ID and optional expected snapshot version |
|
|
280
|
-
| `AgentConversationPurgeResultSchema` / `AgentConversationPurgeResult` | schema / _type_ | typed deletion/refusal outcomes; active refusal includes run IDs |
|
|
281
|
-
| `AgentConversationPurgedError` | class | runtime mutation rejected because its conversation ID is permanently purged |
|
|
282
|
-
| `AgentConversationPurgeDriver` | _type_ | optional `driver.conversations` capability: serialized tombstone read and atomic removal of all owned records |
|
|
283
|
-
| `projectAgentHistory` | function | asynchronously project canonical records and resolved multimodal files into provider-valid AI SDK messages |
|
|
284
|
-
| `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
|
|
285
|
-
| `AgentModelCatalogSchema` / `AgentModelCatalog` | schema / _type_ | provider-neutral complete/partial model catalog with separately sourced popularity, metrics, prices and observation time |
|
|
286
|
-
| `AgentModelCatalogEntrySchema` / `AgentModelCatalogEntry` | schema / _type_ | one canonical provider model descriptor with optional price, popularity and metric evidence |
|
|
287
|
-
| `AgentModelPriceSchema` / `AgentModelPrice` | schema / _type_ | normalized per-token input/output pricing and source currency |
|
|
288
|
-
| `AgentModelPopularitySchema` / `AgentModelPopularity` | schema / _type_ | independently sourced ranked popularity observation with window and timestamp |
|
|
289
|
-
| `AgentModelMetricSchema` / `AgentModelMetric` | schema / _type_ | independently sourced benchmark measurement with provenance and observation time |
|
|
290
|
-
| `AgentModelCatalogProvider` | _type_ | abortable live catalog loader supplied by a provider adapter or application |
|
|
291
|
-
| `AgentModelSearchInputSchema` / `AgentModelSearchInput` | schema / _type_ | bounded catalog text query and result ceiling |
|
|
292
|
-
| `AgentModelSearchResultSchema` / `AgentModelSearchResult` | schema / _type_ | exact bounded catalog projection with total match count |
|
|
293
|
-
| `searchAgentModelCatalog` | function | deterministic bounded search over a loaded canonical catalog |
|
|
294
|
-
| `AgentModelSelectionSchema` / `AgentModelSelection` / `AgentModelSelectionStore` | schema / _type_ | durable per-conversation model choice; runtime resolvers receive run and snapshot to recover the model pinned to input metadata |
|
|
295
|
-
| `createMemoryAgentModelSelectionStore` | function | process-local selection reference adapter |
|
|
296
|
-
| `AgentConversationReader` | _type_ | optional bounded conversation-summary and message-history reader; `messages` takes `includeCompacted` and every page names its `compacted` ids; not part of the required runtime store contract |
|
|
297
|
-
| `AgentConversationSummarySchema` / `AgentConversationSummary` | schema / _type_ | bounded durable conversation list item with version, activity and preview |
|
|
298
|
-
| `AgentConversationPageSchema` / `AgentConversationPage` | schema / _type_ | cursor-paged conversation summaries |
|
|
299
|
-
| `AgentConversationMessagePageSchema` / `AgentConversationMessagePage` | schema / _type_ | cursor-paged durable message history |
|
|
300
|
-
| `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware signed context budget; irreducible reservation deficits are `oversized`, not compactable history |
|
|
301
|
-
| `SeedConversationInputSchema` / `SeedConversationInput` | schema / _type_ | atomic once-seeding input; partial imported identity sets are refused |
|
|
302
|
-
| `AgentSeedReceiptSchema` / `AgentSeedReceipt` | schema / _type_ | durable seed receipt retained beyond compaction and implemented by custom store drivers |
|
|
303
|
-
| `StepRunOptions` | _type_ | per-step options of the durability engine (`stitchkit/tools`) as the runtime drives it |
|
|
304
|
-
| `DURABILITY_STEP_EVENT_KIND` | const | the ledger event kind a recorded step is written under |
|
|
305
|
-
| `StepResultDecodeError` / `StepResultNotSerializableError` / `StepAbortedError` / `ParkRecordDecodeError` / `ParkAbortedError` | class | fail-closed decode of a recorded step or park, a body whose result cannot be serialized, an aborted step, and a park collapsed around an aborted waiter |
|
|
306
|
-
| `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
|
|
307
|
-
| `selectCompactableHistory` | function | which oldest whole complete turns may be summarised away — the half of compaction that needs no store (→ ADR 0142) |
|
|
308
|
-
| `SelectCompactableHistoryOptions` / `CompactableHistory` | _type_ | message list, retained-turn count and evidence policy in; `leadingSummary`, `compactable` and `retained` out |
|
|
309
|
-
| `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
|
|
310
|
-
| `AgentRuntimeStopPolicy` | _type_ | named stop rule persisted and published on policy stop; `when` receives the AI SDK `steps` plus the run context `prepareStep` gets (`context`, `run`, `signal`, `toolFenceLifecycle`, `contextUsage`) |
|
|
311
|
-
| `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
|
|
312
|
-
| `AgentContextOverflowError` | class | deliberate application budget refusal thrown from `loop.prepareStep`; terminalizes as `context_overflow` without classifying arbitrary error text |
|
|
313
|
-
| `AgentRuntimeRecordIds` | _type_ | optional caller-provided input, run and assistant IDs for stable application records |
|
|
314
|
-
| `AgentRuntimeAdmission` | _type_ | canonical committed input, assigned run, pending assistant projection, compatibility IDs and snapshot version |
|
|
315
|
-
| `AgentAdmissionEventSchema` | schema | post-commit admission projection; removes store rereads but does not imply exactly-once delivery |
|
|
316
|
-
| `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 |
|
|
317
|
-
| `AgentRuntimeRecoverOptions` | _type_ | bounded paged startup recovery with causal per-conversation scheduling, context resolver and explicit evidence policy |
|
|
318
|
-
| `AgentRuntimeAbandonInput` | _type_ | one known orphan identity, expected revision and mandatory stale-owner evidence for `runtime.abandon()` |
|
|
319
|
-
| `AgentRuntimeConflictError` | class | thrown when a store mutation loses to a concurrent writer — catchable by type from `stitchkit/agent-runtime` |
|
|
320
|
-
| `AgentRunUnsettledError` | class | rejects `result` when the store never answered: `.run` is still active with no executor, close it with `runtime.abandon()` |
|
|
321
|
-
| `AgentSessionCloseOptions` | _type_ | `gracePeriodMs` for natural settlement, then abort, then `forceTimeoutMs` for bounded settlement after it |
|
|
322
|
-
| `AgentSessionCloseResult` | _type_ | what `close()` achieved: `settled`, or `timedOut` with `remaining` runs still in flight. Only omitting `forceTimeoutMs` guarantees nothing is in flight on return |
|
|
323
|
-
| `AgentHistoryProjectionOptions` | _type_ | storage-neutral file resolver, explicit unresolved-file behavior, and how an interrupted turn reaches the model (`interruptedAssistant`) |
|
|
324
|
-
| `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) |
|
|
325
|
-
| `AgentRuntimeEventSchema` | schema | transient stream lifecycle plus post-commit admission/checkpoint/run-state/run-operation/terminal projections |
|
|
326
|
-
| `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 |
|
|
327
|
-
| `RecordRunOperationSchema` / `RecordRunOperation` | schema / _type_ | owner/fencing/revision-checked mutation of `AgentRun.lastOperation` |
|
|
328
|
-
| `createAgentObservability` | function | separate agent-run sink over the shared bounded observability lifecycle |
|
|
329
|
-
| `AgentProviderResponseSchema` / `AgentProviderResponse` | schema / _type_ | provider-assigned response ID and the optional upstream provider the model's own adapter resolves, shared by `step-finished.response` and durable `provider/response` facts |
|
|
330
|
-
| `AgentStepFinishedEventSchema` / `AgentStepFinishedEvent` | schema / _type_ | one completed provider step with required usage and provider response identity |
|
|
331
|
-
|
|
332
|
-
### Durable capability layer
|
|
333
|
-
|
|
334
|
-
| Export | Kind | Summary |
|
|
335
|
-
|--------|------|---------|
|
|
336
|
-
| `AgentConversationArchiveSchema` / `AgentConversationArchive` / `encodeAgentConversationArchive` / `decodeAgentConversationArchive` / `canonicalAgentJson` | schema / _type_ / functions | canonical validated conversation archive (`canonicalAgentJson` is `canonicalJson` with the node and byte limits at their maximum, reading `-0` as `0` like `z.json()`) with exact ledger events, recovery projection and durable spill payloads |
|
|
337
|
-
| `AgentStoreEventEnvelopeSchema` / `AgentStoreEventEnvelope` / `AgentStoreEventKindSchema` / `AgentStoreEventKind` / `AgentStoreEventPageSchema` / `AgentStoreEventPage` | schema / _type_ | append-only event envelope, closed current vocabulary (including `provider/request` and `provider/response`) and bounded page |
|
|
338
|
-
| `AppendAgentStoreEventSchema` / `AppendAgentStoreEvent` / `ReadAgentStoreEventsSchema` / `ReadAgentStoreEvents` | schema / _type_ | required append and bounded-read store contracts |
|
|
339
|
-
| `AgentStoreTransitionSchema` / `AgentStoreTransition` / `AgentStoreEventDraft` / `AgentStoreEventDecodeAccepted` / `AgentStoreEventDecodeIgnored` / `AgentStoreEventDecodeResult` / `decodeAgentStoreEvent` | schema / _types_ / function | normalized runtime mutations, append drafts and strict current/ignorable future-event decoding outcomes |
|
|
340
|
-
| `AgentStoreTransitionRecordSchema` / `AgentStoreTransitionRecord` | schema / _type_ | persisted journal payloads: full runtime mutations or hash-only streaming checkpoint observations with numeric part/byte counts; inspection does not authorize replay |
|
|
341
|
-
| `defineAgentProjection` / `AgentProjectionDefinition` / `AgentProjectionValue` / `createAgentProjectionRegistry` / `createSqliteAgentProjectionStore` | functions / _type_ | deterministic folds with persisted version and honest `uptoSeq` checkpoints |
|
|
342
|
-
| `agentSummaryProjection` / `agentUsageProjection` / `agentOutlineProjection` | constants | built-in list summary, provenance-carrying usage and request outline projections |
|
|
343
|
-
| `agentConversationCardProjection` / `agentStateSlotsProjection` / `agentScheduleSummaryProjection` | constants | focused card, durable-state and schedule lifecycle projections |
|
|
344
|
-
| `defineStateSlot` / `AgentStateSlotDefinition` / `AgentStateSlotStore` / `AgentStateSlotValue` / `AnyAgentStateSlot` / `createAgentStateSlotStore` / `renderAgentStateSlots` | functions / _types_ | typed durable state written as ledger events and injected on every provider request |
|
|
345
|
-
| `agentGoalStateSlot` / `agentTodoStateSlot` / `createAgentStateTools` | constants / function | built-in goal/todo state and bound `goal_*` / `todo_write` Agent tools |
|
|
346
|
-
| `AgentSandboxGradeSchema` / `AgentSandboxGrade` / `AgentSandboxRestrictionSchema` / `AgentSandboxRestriction` / `AgentProcessSandbox` | schema / _type_ | host-provided process sandbox capability and explicit restriction vocabulary |
|
|
347
|
-
| `probeAgentProcessSandbox` / `missingSandboxRestrictions` / `recordAgentSandboxProbe` | functions | probe cached per sandbox object (keep the sandbox long-lived), fail-closed required-gap calculation and durable probe record; a probe that throws or returns no valid grade resolves to `unavailable` with a fixed reason, its cause emitted as a process warning (the coding tools instead carry it as the refusal's `cause` to the mount's `onToolError` hook) |
|
|
348
|
-
| `AgentSandboxProcess` / `AgentSandboxOutputStream` | _types_ | structural child/output interface for optional lifecycle-owned `AgentProcessSandbox.spawn`, without Node ambient type dependencies |
|
|
349
|
-
| `AgentEventSearchResultSchema` / `AgentEventSearchResult` / `createSqliteAgentEventSearch` / `createAgentEventSearchTools` | schema / _type_ / functions | authorized FTS5 search with exact event addresses and `session_*` tools |
|
|
350
|
-
| `createSqliteAgentSpillStore` | function | durable content-address-checked artifact storage, bounded read/search, retention facts and archive participation |
|
|
351
|
-
| `AgentScheduleSchema` / `AgentSchedule` / `AgentScheduleService` / `AgentScheduleDeliveryOutcomeSchema` / `AgentScheduleDeliveryOutcome` / `createAgentScheduleService` / `createAgentScheduleTools` | schemas / _types_ / functions | durable `at`/`after`/timezone-explicit `every`, indexed retry eligibility, typed retry/terminal failure (any other returned value delivers), `maxAttempts` (default 1000) per occurrence — a one-off ends `failed`, a recurring schedule skips to its next slot — bounded dispatch and fenced stable occurrence identity (ADR 0202) |
|
|
352
|
-
| `AgentChildBudgetSchema` / `AgentChildBudget` / `AgentChildStateSchema` / `AgentChildState` / `AgentChildRecordSchema` / `AgentChildRecord` | schema / _type_ | durable child graph, bounded seed and measured budget state |
|
|
353
|
-
| `AgentChildBlockingKindSchema` / `AgentChildBlockingKind` / `AgentChildBlockingSourceSchema` / `AgentChildBlockingSource` / `AgentChildBlockingEventSchema` / `AgentChildBlockingEvent` / `AgentChildBlockingDecision` | schemas / _types_ | parent-owned request identities; responses discriminate approval decisions from JSON input values |
|
|
354
|
-
| `AgentChildHandle` / `AgentChildManager` / `SqliteAgentChildManagerConfig` / `createSqliteAgentChildManager` / `createAgentChildTools` / `agentChildBudgetStopPolicy` | _type_ / functions | host execution port, child lifecycle, cascade (given to `createAgentRuntime` as `children`), messaging, Agent tools, and the child runtime's own budget stop policy — `recordStepUsage` at every step boundary, `policy_stop` as `child-budget` when spent |
|
|
355
|
-
| `AgentToolDefinition` | _type_ | peer-neutral shape returned by the bound agent-only state, search, schedule and child tool factories |
|
|
356
|
-
| `AgentProviderStreamCutError` | class | explicit retryable provider stream truncation evidence |
|
|
357
|
-
|
|
358
|
-
### Complete runtime inventory
|
|
359
|
-
|
|
360
|
-
The entrypoint deliberately exports the schemas beside their inferred types so persistence and
|
|
361
|
-
transport adapters validate the same records. Runtime composition types are `AgentRuntime`,
|
|
362
|
-
`AgentRuntimeConfig`, `AgentRuntimeInput`, `AgentRuntimeProtocolInput`, `AgentRuntimeRunContext`,
|
|
363
|
-
`AgentRuntimeResult`, `AgentRuntimeInterruptInput`, `AgentRuntimeAbandonInput`, `AgentRuntimeRecoveryInput`,
|
|
364
|
-
`AgentRuntimeRecoveryDecision`, `AgentRuntimeRecoveryOutcome`, `AgentRuntimePublisher`,
|
|
365
|
-
`AgentInputPolicy`, `AgentStopReason`, `AgentCoordinatedRun`, `AgentRunTicket`,
|
|
366
|
-
`AgentSessionCoordinator`, `AgentCompactionContext`, `AgentCompactionResult` and
|
|
367
|
-
`StructuredCompactionConfig`.
|
|
368
|
-
|
|
369
|
-
A provider refusal is classified rather than phrased. `classifyProviderFailure` returns an
|
|
370
|
-
`AgentProviderFailure` — an `AgentProviderFailureReason` (`insufficient-credits`, `rate-limited`,
|
|
371
|
-
`model-unavailable`, `context-overflow`, `timeout`, `cancelled`, `unknown`), the provider's `status`
|
|
372
|
-
when it supplied one, whether the same request is `retryable` unchanged, and the `evidence` the
|
|
373
|
-
answer rests on: `status` is the provider stating its own answer, `message` is us reading its prose,
|
|
374
|
-
and `none` is an honest refusal to guess. The sentence a user reads stays with the application —
|
|
375
|
-
its tone and its decision about what to admit are not the core's to make. `isToolResultFailure`
|
|
376
|
-
recognises a failure carried inside a *successful* tool result, in both the bare and the
|
|
377
|
-
`{ value: … }` envelope. Both are plain functions and need no runtime. → ADR 0141
|
|
378
|
-
|
|
379
|
-
`normalizeOpenRouterUsage` is the same normalisation `openRouterProvider`
|
|
380
|
-
applies, exported so an application calling the SDK directly gets provenance-correct numbers
|
|
381
|
-
without adopting the runtime. Pass each completed SDK step's usage, including `raw`.
|
|
382
|
-
Token provenance comes from the original OpenRouter fields: missing fields remain
|
|
383
|
-
`unavailable`, even when the SDK substitutes zero. Aggregate usage without `raw` cannot
|
|
384
|
-
establish reported token counts. Explicit zero remains `provider-reported`; cost is never
|
|
385
|
-
estimated from token prices.
|
|
386
|
-
|
|
387
|
-
`AgentContextUsage` reaches every step through `AgentRuntimeRunContext.contextUsage`: how full the
|
|
388
|
-
model's context is, as `usedTokens` (an `AgentUsageValue`, so it carries the provenance that says
|
|
389
|
-
where the number came from) beside the model's declared `contextWindow`. It is the **last completed
|
|
390
|
-
step's prompt size**, not the run's cumulative input tokens — cumulative counts every step's prompt
|
|
391
|
-
again and is a multiple of the real fill. Before the first step lands there is no provider-reported
|
|
392
|
-
number and the provenance is `unavailable`, which is a different fact from zero. No fraction is
|
|
393
|
-
exposed: dividing is one line where it is rendered, and the output reserve belongs to the
|
|
394
|
-
consumer's prompt budget rather than to this layer.
|
|
395
|
-
|
|
396
|
-
Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentTerminalAcceptance`,
|
|
397
|
-
`AgentTerminalAcceptanceInput`, `hasAgentTerminalOutput`, `AgentRecordIdSchema`, `AgentRecordVersionSchema`,
|
|
398
|
-
`AgentTimestampSchema`, `AgentJsonObjectSchema`, `AgentProviderEnvelopeSchema`,
|
|
399
|
-
`AgentProviderEnvelope`, `AgentMessagePartSchema`, `AgentMessagePart`, `AgentTextPartSchema`,
|
|
400
|
-
`AgentReasoningPartSchema`, `AgentFilePartSchema`, `AgentSourcePartSchema`,
|
|
401
|
-
`AgentToolCallPartSchema`, `AgentToolResultPartSchema`, `AgentOpaquePartSchema`,
|
|
402
|
-
`AgentControlPartSchema`, `AgentMessageRoleSchema`, `AgentMessageStatusSchema`, `AgentMessage`,
|
|
403
|
-
`AgentAssistantPlaceholderSchema`, `AgentAssistantPlaceholder`, `AgentRunStateSchema`,
|
|
404
|
-
`AgentTerminalReasonSchema`, `AgentTerminalReason`, `AgentRunQueuePrioritySchema`,
|
|
405
|
-
`AgentRunQueuePriority`, `AgentRun`, `AgentSnapshot`,
|
|
406
|
-
`AgentUsageValueSchema`, `AgentCostValueSchema`, `AgentUsageSchema`, `AgentUsage` and
|
|
407
|
-
`AgentRunMetrics`.
|
|
408
|
-
|
|
409
|
-
`AgentProvenanceSchema` / `AgentProvenance` is the entrypoint's single vocabulary for **how a
|
|
410
|
-
number came to be known**: `provider-reported` (the provider stated it about a request it served),
|
|
411
|
-
`measured` (this process counted it exactly, before any request was made), `computed` (arithmetic
|
|
412
|
-
over other values — a sum of exact numbers is still `computed`), `estimated` (a heuristic) and
|
|
413
|
-
`unavailable` (not known, so `value` is absent, which is a different fact from a reported zero).
|
|
414
|
-
Each surface declares the subset it can produce: `AgentUsageValueSchema` and `AgentCostValueSchema`
|
|
415
|
-
describe a request that has already happened and never say `measured`; `AgentTokenCountSchema`
|
|
416
|
-
describes a prompt being composed and never says `provider-reported`. Every token count is an
|
|
417
|
-
integer — `AgentUsageValueSchema` and `AgentTokenCountSchema` refuse a fractional `value`, and a
|
|
418
|
-
provider figure that is not a whole number is normalised to `unavailable` rather than thrown.
|
|
419
|
-
`AgentCostValueSchema.value` stays fractional, because money is.
|
|
420
|
-
|
|
421
|
-
`runs.inputPolicy` takes `queue` (default), `inject`, `interrupt`, `interrupt-next` or `supersede`, or a function of
|
|
422
|
-
the raw input returning one. `inject` lets a run in flight take a newly arrived input into its prompt
|
|
423
|
-
at a step boundary and answer it too; the absorption is committed in the **same transaction** as that
|
|
424
|
-
run's terminal record, via `CommitRunTerminal.absorb`, so a run that ends any other way leaves an
|
|
425
|
-
ordinary queued successor. The absorbed run ends with `terminalReason: 'absorbed'`, run state
|
|
426
|
-
`'superseded'`, `absorbedIntoRunId` naming the run that answered it, and **no assistant message of
|
|
427
|
-
its own**; a submission on its idempotency key resolves through that pointer to the answer
|
|
428
|
-
(→ ADR 0113).
|
|
429
|
-
|
|
430
|
-
`interrupt-next` interrupts the active run, waits for its real settlement and then executes the new
|
|
431
|
-
input before ordinary pending work. Ordinary work is not dropped or re-admitted, and urgent work is
|
|
432
|
-
FIFO within its own class. `AgentRun.queuePriority` persists the pending class;
|
|
433
|
-
`AgentRun.executionSequence` persists the actual first-acquisition order, so recovery and canonical
|
|
434
|
-
history preserve the same `A → C → B` order across equal timestamps and scan pages (→ ADR 0127).
|
|
435
|
-
|
|
436
|
-
With `queue`, a durable successor admission is not prompt eligibility: the current executor sees
|
|
437
|
-
only records through its own run boundary. Snapshot history is normalized to causal turn order
|
|
438
|
-
(assigned input(s), assistant, then successor input(s)) even when the storage codec physically
|
|
439
|
-
appended the successor before the predecessor checkpoint.
|
|
440
|
-
|
|
441
|
-
`AgentRuntimeStore` has two **bounded** reads beside `loadSnapshot`:
|
|
442
|
-
`loadRun({ conversationId, runId })` returns an `AgentRunView` — the run, the conversation version it
|
|
443
|
-
was read at, and the retained answer once the run is terminal — or `undefined`; `listActiveRuns(conversationId)`
|
|
444
|
-
returns the runs that have not ended in durable execution/priority order. Neither reads history, so
|
|
445
|
-
neither grows with the length of the conversation, and neither needs anything new from
|
|
446
|
-
`AgentRuntimeStoreDriver`. `loadSnapshot` and every mutation result still carry the whole
|
|
447
|
-
conversation — that is what the store's reducer validates against, and what the runtime builds a
|
|
448
|
-
prompt from (→ ADR 0112).
|
|
449
|
-
|
|
450
|
-
`AgentRuntimeStore.purgeConversation` is optional; `purgeAgentConversation(store, input)` explicitly
|
|
451
|
-
handles unsupported stores. Official memory and initialized SQLite stores refuse active runs,
|
|
452
|
-
remove every owned payload and reserve the conversation ID permanently. Empty snapshots are not
|
|
453
|
-
deletion receipts. Consumer metadata, files, event logs and UI cache invalidation remain outside
|
|
454
|
-
the transaction. See [purging a conversation](../guide/agent-runtime.md#purging-a-conversation).
|
|
455
|
-
|
|
456
|
-
Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
|
|
457
|
-
`AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
|
|
458
|
-
`CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
|
|
459
|
-
`RequestRunInterrupt`, `RequestRunInterruptSchema`, `AgentRunView`, `AgentRunViewSchema`,
|
|
460
|
-
`runStateForTerminalReason`,
|
|
461
|
-
`ACTIVE_AGENT_RUN_STATES`, `RecoverAgentRun`, `ReplaceCompactedRange`,
|
|
462
|
-
`ReplaceCompactedRangeSchema`, `AgentStoreMutationResult`, `AgentStoreMutationResultSchema`,
|
|
463
|
-
`AgentStoreAppliedSchema`, `AgentStoreConflictSchema`, `AgentStoreDuplicateSchema`,
|
|
464
|
-
`AgentStoreNotFoundSchema`, `AgentAdmissionReceipt`, `AgentAdmissionReceiptSchema`,
|
|
465
|
-
`AgentRuntimeHead`, `AgentStoredRun`, `AgentStoreCompareAndSwapResult`, `AgentHistoryMutation`,
|
|
466
|
-
`AgentRecoverableDescriptor`, `AgentRecoverableDescriptorSchema`, `AgentRecoverablePage` and
|
|
467
|
-
`AgentRecoverablePageSchema`.
|
|
468
|
-
|
|
469
|
-
History and context-budget exports are `projectAgentHistoryDetailed`,
|
|
470
|
-
`AgentHistoryProjectionDecision`, `AgentHistoryProjectionResult`, `selectAgentHistory`,
|
|
471
|
-
`SelectAgentHistoryOptions`, `AgentHistoryBudgetDecision`, `AgentHistoryBudgetResult`,
|
|
472
|
-
`AgentPromptBudget`, `AgentPromptSection`, `AgentPromptSectionContext`, `AgentTokenCount`,
|
|
473
|
-
`AgentTokenCountSchema`, `ComposeAgentPromptOptions` and `ComposedAgentPrompt`. Whole-turn history
|
|
474
|
-
selection never splits a tool chronology and reports why every canonical record was retained or
|
|
475
|
-
removed. `AgentHistoryEvidencePolicy` is the shared opt-in for marked failed-assistant evidence in
|
|
476
|
-
projection, budgeting and structured compaction; `isAssistantHistoryEvidence` applies it. The
|
|
477
|
-
compatibility default omits it. Approval message schemas are
|
|
478
|
-
`AgentToolApprovalRequestPartSchema` and `AgentToolApprovalResponsePartSchema`.
|
|
479
|
-
|
|
480
|
-
With `failedAssistant: 'assistant-marked'`, `selectCompactableHistory` and
|
|
481
|
-
`structuredCompaction` admit a failed prefix only when the entire turn's tool chronology
|
|
482
|
-
is complete. `eligibleMessages` stays raw: the summarizer must use the same policy in
|
|
483
|
-
`projectAgentHistoryDetailed` and preserve non-success/uncertain outcomes in its summary.
|
|
484
|
-
Even an empty opted-in failed assistant carries a non-success marker in that projection.
|
|
485
|
-
Use `incompleteToolTurn: 'error'` to refuse a range the projector cannot represent.
|
|
486
|
-
Compaction never deletes canonical effect events or the retained terminal receipt;
|
|
487
|
-
chronological completeness does not settle an external effect. See the
|
|
488
|
-
[failed-prefix recovery contract](../guide/agent-runtime.md#recovering-a-prefix-after-a-failed-turn).
|
|
489
|
-
|
|
490
|
-
Model exports are `AgentLanguageModelProvider`, `AgentModelCapability`,
|
|
491
|
-
`AgentModelCapabilitySchema`, `AgentModelDescriptor`,
|
|
492
|
-
`AgentModelDescriptorSchema`, `AgentModelRegistry`, `AgentModelRegistryConfig`,
|
|
493
|
-
`AgentModelRegistrySnapshot`, `AgentModelRegistrySnapshotSchema`, `AgentModelSnapshotPolicy`,
|
|
494
|
-
`AgentResolvedModel` and `validateAgentModelSnapshot`. Registry `preflight` validates availability,
|
|
495
|
-
provider and required capabilities without constructing the model; runtime `models.preflight`
|
|
496
|
-
runs before durable admission.
|
|
497
|
-
|
|
498
|
-
Delivery exports are `AgentAdmissionEventSchema`, `AgentAttemptResetEventSchema`, `AgentCheckpointEventSchema`,
|
|
499
|
-
`AgentRunStateEventSchema`, `AgentRunOperationEventSchema`, `AgentTerminalEventSchema`,
|
|
500
|
-
`AgentTransientDeltaEventSchema`,
|
|
501
|
-
`AgentReasoningStartEventSchema`, `AgentReasoningDeltaEventSchema`,
|
|
502
|
-
`AgentReasoningEndEventSchema`, `AgentToolStatusEventSchema`, `AgentToolProgressEventSchema` (what a
|
|
503
|
-
running tool reported through `ctx.reportProgress`, transient and never part of the model's input),
|
|
504
|
-
`AgentRuntimeEvent`,
|
|
505
|
-
`AgentRuntimeEventCursor`, `AgentRuntimeEventCursorSchema`, `AgentRuntimeCursorAdvance`,
|
|
506
|
-
`advanceAgentRuntimeEventCursor`, `agentDurableEventId`, `AgentRuntimeEventSink`,
|
|
507
|
-
`AgentRuntimeEventSinkConfig` and `createAgentRuntimeEventSink`. Cursor gaps require a canonical
|
|
508
|
-
snapshot reload; the bounded sink isolates transport failure and supports a typed projection step.
|
|
509
|
-
|
|
510
|
-
Managed effects and operator telemetry additionally export `AgentToolFenceConfig`,
|
|
511
|
-
`AgentToolFenceContext`, `AgentObservability`, `AgentRunEvent`, `AgentRunEventSchema`,
|
|
512
|
-
`AgentRunStartedEvent`, `AgentRunStartedEventSchema`, `AgentStepFinishedEvent`,
|
|
513
|
-
`AgentStepFinishedEventSchema`, `AgentRunTerminalEvent`, `AgentRunTerminalEventSchema`,
|
|
514
|
-
`AgentRunSinkConfig`, `AgentRunSinkDrop` and `AgentRunSinkError`. A monotonic run `fencingToken`
|
|
515
|
-
may accompany checkpoint/terminal writes and tool context; internal causes are redacted unless an
|
|
516
|
-
operator-only observability sink explicitly opts in.
|
|
517
|
-
|
|
518
|
-
### Completion and agent invocation receipts
|
|
519
|
-
|
|
520
|
-
`createModelInvocationLedger` constructs a `ModelInvocationLedger` from
|
|
521
|
-
`ModelInvocationConfig`: the existing store and model registry, a 32-byte payload
|
|
522
|
-
key, required host authorization, optional bounded payload size and `onAttempt`.
|
|
523
|
-
`complete(input, context, signal?)` executes only the supplied prompt, with no tools
|
|
524
|
-
or framework instructions; repeated keys return the existing invocation ID. It
|
|
525
|
-
resolves `{ invocationId, outcome, text?, receiptError? }`: `receiptError` says a
|
|
526
|
-
receipt could not be written and never changes `outcome` or drops `text`.
|
|
527
|
-
`read(input, context)` returns a bounded event page shared by agent and completion;
|
|
528
|
-
`readPayload(input, context)` requires a separate authorization action to decrypt
|
|
529
|
-
one protected artifact. See the [guide and migration example](../guide/agent-runtime.md#plain-completions-and-shared-invocation-receipts).
|
|
530
|
-
|
|
531
|
-
| Export | Kind | Purpose |
|
|
532
|
-
| --- | --- | --- |
|
|
533
|
-
| `CompletionInvocationInputSchema` / `CompletionInvocationInput` | schema/type | strict prompt, model-attempt list, deadline, idempotency key and trace input |
|
|
534
|
-
| `ModelInvocationTraceSchema` / `ModelInvocationTrace` | schema/type | operation, purpose, project, parent trace and optional experiment/case/profile |
|
|
535
|
-
| `ModelInvocationRecordSchema` / `ModelInvocationRecord` | schema/type | discriminated invocation start/finish and per-attempt requested/sent/effective evidence |
|
|
536
|
-
| `ModelInvocationAttemptContext` | type | conversation, operation, invocation, attempt, step and optional agent run identity |
|
|
537
|
-
| `currentModelInvocationAttempt` | function | returns causal IDs inside the provider transport; undefined outside an invocation |
|
|
538
|
-
|
|
539
|
-
The runtime `invocations` option takes this ledger and a trace callback; it requires
|
|
540
|
-
the same store and preserves the existing tool loop. `AgentRuntimeStore.appendEventOnce`
|
|
541
|
-
is an optional atomic capability required by receipt admission, and
|
|
542
|
-
`AgentRuntimeStore.findEventOnce` reads the event it wrote by kind and key. Built-in
|
|
543
|
-
stores supply both; custom adapters must honor the once-only transaction contract.
|
|
544
|
-
A driver's optional `events.find(transaction, { conversationId, eventId })` makes
|
|
545
|
-
both one lookup instead of a scan of the conversation log.
|
|
783
|
+
Deciding *when* to compact is yours: `AgentRuntimeRunContext.contextUsage`
|
|
784
|
+
carries the last step's prompt size beside the model's window, and dividing them
|
|
785
|
+
is one line where the threshold is decided. The core does not own that ratio, on
|
|
786
|
+
purpose — see ADR 0142 for what it publishes and what it declines to.
|