Skip to content

Commit 53a2d94

Browse files
committed
docs(ai-chat): onRecoveryBoot fires only on mid-stream interruptions (no graceful exits)
1 parent 9ddda9b commit 53a2d94

3 files changed

Lines changed: 11 additions & 8 deletions

File tree

docs/ai-chat/lifecycle-hooks.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ description: "Hook into every stage of a chat agent's run: preload, turn start,
1919
| Scope | Fires when | Use for |
2020
| --- | --- | --- |
2121
| **Process** ([`onBoot`](#onboot)) | Every fresh worker boots — initial, preloaded, and reactive continuation (post-cancel/crash/`endRun`/upgrade). | Initialize `chat.local`, open per-process resources, re-hydrate state from your DB on continuation. |
22-
| **Recovery** ([`onRecoveryBoot`](#onrecoveryboot)) | Continuation boot where the dead run left state behind — a partial assistant on `session.out` or in-flight users on `session.in`. | Override the smart default — drop the partial, synthesize tool results, emit a recovery banner. |
22+
| **Recovery** ([`onRecoveryBoot`](#onrecoveryboot)) | Continuation boot where the dead run was mid-stream — a partial assistant survives on `session.out`. | Override the smart default — drop the partial, synthesize tool results, emit a recovery banner. |
2323
| **Chat** ([`onChatStart`](#onchatstart)) | First message of a chat's lifetime. Does NOT fire on continuation runs or OOM retries. | One-time DB rows for the chat, resources tied to the chat's lifetime. |
2424
| **Turn** ([`onTurnStart`](#onturnstart), [`onTurnComplete`](#onturncomplete), etc.) | Every turn. | Persist messages, post-process responses. |
2525

@@ -86,9 +86,9 @@ export const myChat = chat.agent({
8686

8787
## onRecoveryBoot
8888

89-
Fires once on a continuation boot when the dead predecessor left state behind — a partial assistant on `session.out`, in-flight user messages on `session.in`, or both. The runtime reconstructs context automatically via a smart default; this hook is the override path for policies that need something different.
89+
Fires once on a continuation boot when the dead predecessor was mid-stream — a partial assistant survives on `session.out`. The runtime reconstructs context automatically via a smart default; this hook is the override path for policies that need something different.
9090

91-
The hook does NOT fire when there's nothing to recover (clean continuation after `chat.endRun()`, fresh chat, OOM retry on top of a complete snapshot). It does NOT fire when [`hydrateMessages`](#hydratemessages) is registered (the customer owns persistence).
91+
The hook does NOT fire when there's no partial — clean continuations after `chat.endRun()` or `chat.requestUpgrade()`, fresh chats, OOM retries on top of a complete snapshot. Those paths dispatch any in-flight user message as a normal turn on the new run without involving the hook. It also does NOT fire when [`hydrateMessages`](#hydratemessages) is registered (the customer owns persistence).
9292

9393
```ts
9494
export const myChat = chat.agent({

docs/ai-chat/patterns/recovery-boot.mdx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,20 @@ export const myChat = chat.agent({
8282

8383
### Fires when
8484

85-
The hook fires once on a continuation boot, AFTER both stream tails have been read, AND only when there's recovered state to act on:
85+
The hook fires once on a continuation boot, AFTER both stream tails have been read, AND only when there's a partial assistant — the mid-stream-died signal:
8686

8787
```ts
88-
const shouldFire = partialAssistant !== undefined || inFlightUsers.length > 0;
88+
const shouldFire = partialAssistant !== undefined;
8989
```
9090

91+
In-flight users alone don't fire the hook. Graceful exits like `chat.requestUpgrade()` and `chat.endRun()` may leave an unacknowledged user on `session.in` (the message that triggered the upgrade, the next message after endRun), but no partial — that's a normal continuation, not recovery. The next message just dispatches as turn 1 on the new run via the normal session.in pump.
92+
9193
Skipped scenarios (where the hook does NOT fire):
9294

9395
- A clean continuation after `chat.endRun()` with no buffered follow-up.
9496
- A fresh chat (no continuation, attempt 1).
95-
- An OOM retry that booted onto a complete snapshot (no in-flight tail).
97+
- An OOM retry that booted onto a complete snapshot (no partial on the tail).
98+
- `chat.requestUpgrade()` graceful exit — predecessor ended cleanly before processing, no partial.
9699
- An agent with [`hydrateMessages`](/ai-chat/lifecycle-hooks#hydratemessages) registered. Customers using `hydrateMessages` own persistence — recovery decisions live in their own DB query.
97100

98101
### Event shape

docs/ai-chat/patterns/version-upgrades.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,11 @@ This upgrades on **every** deploy, not just breaking changes. Good for fast-movi
158158

159159
## Interaction with recovery boot
160160

161-
`chat.requestUpgrade()` is a graceful exit — the old run returns cleanly, never writing a partial assistant. The new continuation run boots with an empty `session.out` tail (no `partialAssistant`) and the upgrade-trigger message on `session.in`. The smart default in [Recovery boot](/ai-chat/patterns/recovery-boot) requires both a partial AND in-flight users to splice, so on the upgrade path it doesn't fire — the chain stays at `settledMessages` and the in-flight user dispatches as a fresh turn on the new version. `onRecoveryBoot` still fires (there's an in-flight user) if you want to emit an "upgraded" banner.
161+
`chat.requestUpgrade()` is a graceful exit — the old run returns cleanly, never writing a partial assistant. The new continuation run boots with an empty `session.out` tail and the upgrade-trigger message on `session.in`. The trigger message dispatches as turn 1 on the new version via the normal continuation-wait path. [`onRecoveryBoot`](/ai-chat/patterns/recovery-boot) does NOT fire on this path — the hook is reserved for mid-stream interruptions (cancel / crash / OOM) where a partial assistant exists on the tail.
162162

163163
## See also
164164

165165
- [Lifecycle hooks](/ai-chat/lifecycle-hooks) — where `onTurnStart` and `onChatResume` fit in the turn cycle
166-
- [Recovery boot](/ai-chat/patterns/recovery-boot) — the hook that also fires on the upgraded run
166+
- [Recovery boot](/ai-chat/patterns/recovery-boot) — the sibling hook for mid-stream interruptions (does NOT fire on `requestUpgrade`)
167167
- [Database persistence](/ai-chat/patterns/database-persistence) — how continuations interact with session state
168168
- [Client Protocol](/ai-chat/client-protocol#step-4-handle-continuations) — how clients handle continuations at the wire level

0 commit comments

Comments
 (0)