Files
triggerdotdev--trigger.dev/docs/ai-chat/patterns/version-upgrades.mdx
Eric Allam 80bb600cd5 docs(ai-chat): AI Agents documentation for v4.5 (#3226)
## Summary

Lands the full AI Agents documentation surface alongside the v4.5
release candidate of `@trigger.dev/sdk`. Covers `chat.agent` end to end
— defining agents, lifecycle hooks, the frontend transport, sub-agents,
recovery from cancel/crash/OOM, AI Prompts integration — and the
Sessions primitive that backs it.

## Coverage

- **Conceptual**: Overview, Quick Start, How it works.
- **Building agents**: Backend (`chat.agent` / `chat.createSession` /
raw primitives), Lifecycle hooks, Frontend transport, Server-side
`AgentChat`, Sessions reference, `chat.local` state primitive,
TypeScript types.
- **Features**: AI Prompts integration, Fast starts (Preload + Head
Start), Compaction, Pending Messages (steering), Background Injection
(`chat.inject` + `chat.defer`), Actions (undo / regenerate / edit),
Error handling.
- **Patterns (13)**: Sub-agents, Branching conversations, Code sandbox,
Database persistence, Persistence and replay, HITL, Tool result
auditing, Large payloads, Agent skills, OOM resilience, Recovery boot,
Trusted edge signals, Version upgrades.
- **Reference**: API Reference, Client Protocol (wire format), Testing
harness (`mockChatAgent`), MCP server tools, Upgrade guide, Changelog.

## Structure changes

- Top-level nav: AI → **Agents**, with sub-groups for *Building agents /
Features / Patterns / Reference*.
- New RC banner snippet on every page links to the supported AI SDK
versions table on the API Reference.
- All examples use Anthropic with `stopWhen: stepCountIs(15)`.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:59 +01:00

173 lines
7.4 KiB
Plaintext

---
title: "Version upgrades"
sidebarTitle: "Version upgrades"
description: "Gracefully migrate suspended chat agents to a new deployment using chat.requestUpgrade() and the continuation mechanism."
---
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
<RcBanner />
Chat agent runs are pinned to the worker version they started on. When you deploy a new version, suspended runs resume on the **old** code. If your deploy includes breaking changes (new tools, changed schemas, updated API contracts), this can cause issues.
`chat.requestUpgrade()` lets the agent opt out of the current run so the transport triggers a new one on the latest version.
## How it works
When `chat.requestUpgrade()` is called in `onTurnStart` or `onValidateMessages`:
1. `run()` is **skipped** — no response is generated on old code
2. The agent calls the server-side `endAndContinueSession` endpoint, which atomically swaps the Session's `currentRunId` to a freshly-triggered run on the latest deployment (optimistic-claim against `currentRunVersion`)
3. The new run picks up the conversation and produces the response
4. The transport's existing SSE subscription to `session.out` keeps receiving chunks across the swap — no client-side reconnect
The new run lives on the **same Session** as the old one. `chatId` is the durable identity; only the underlying `currentRunId` rotates. The audit log records the new run with `reason: "upgrade"`.
When called from inside `run()` or `chat.defer()`, the current turn completes normally first and the run exits afterward. The next message triggers the continuation on the same session.
```mermaid
sequenceDiagram
participant User
participant Transport
participant RunV1 as Run (v1)
participant RunV2 as Run (v2)
User->>Transport: send message
Transport->>RunV1: input stream
RunV1->>RunV1: onTurnStart → requestUpgrade()
RunV1-->>Transport: trigger:upgrade-required
RunV1->>RunV1: exit (run() never called)
Transport->>RunV2: trigger new run (continuation, same message)
RunV2-->>Transport: response stream
Transport-->>User: response (seamless)
```
## Contract versioning
Define an explicit version for the contract between your frontend and agent. The frontend sends a `protocolVersion` via `clientData`, and the agent declares which versions it supports. When a breaking change ships (new tools, changed data parts, updated response format), bump the version.
This gives you full control — the frontend can be backwards-compatible across multiple agent versions, and the agent only upgrades when it sees a version it doesn't support.
```tsx title="app/components/Chat.tsx"
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
import { useChat } from "@ai-sdk/react";
export function Chat() {
const transport = useTriggerChatTransport({
task: "my-chat",
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
startSession: ({ chatId, clientData }) =>
startChatSession({ chatId, clientData }),
// Bump this when you ship a breaking change to the chat UI or tools
clientData: { userId: user.id, protocolVersion: "v2" },
});
const { messages, sendMessage } = useChat({ transport });
// ...
}
```
On the agent side, declare which versions the current code supports:
```ts
import { chat } from "@trigger.dev/sdk/ai";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
// The set of frontend protocol versions this agent code supports.
// When you deploy a breaking change, remove old versions from this set.
const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
export const myChat = chat
.withClientData({
schema: z.object({
userId: z.string(),
protocolVersion: z.string(),
}),
})
.agent({
id: "my-chat",
onTurnStart: async ({ clientData }) => {
if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
chat.requestUpgrade();
}
},
run: async ({ messages, signal }) => {
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
},
});
```
The transport includes `clientData` in every payload — both the initial trigger and subsequent records on the session's `.in` channel — so the agent always has the current value.
This pattern is useful when:
- Your frontend is backwards-compatible across several agent versions, but occasionally ships breaking changes
- You want explicit control over when upgrades happen rather than upgrading on every deploy
- Multiple frontend versions may be active at the same time (e.g., users with cached tabs)
## Auto-detect from build ID (Next.js / Vercel)
For automatic upgrade on every deploy, pass your platform's build ID via `clientData` instead of a manual version. The agent stores the ID from the first message and upgrades when it changes:
```tsx title="app/components/Chat.tsx"
// Vercel sets this at build time, or use your own build ID
const APP_VERSION = process.env.NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID
?? process.env.NEXT_PUBLIC_BUILD_ID
?? "dev";
export function Chat() {
const transport = useTriggerChatTransport({
task: "my-chat",
accessToken: ({ chatId }) => mintChatAccessToken(chatId),
startSession: ({ chatId, clientData }) =>
startChatSession({ chatId, clientData }),
clientData: { userId: user.id, appVersion: APP_VERSION },
});
// ...
}
```
```ts title="trigger/chat.ts"
const initialAppVersion = chat.local<{ version: string }>({ id: "appVersion" });
export const myChat = chat
.withClientData({
schema: z.object({
userId: z.string(),
appVersion: z.string(),
}),
})
.agent({
id: "my-chat",
onBoot: async ({ clientData }) => {
initialAppVersion.init({ version: clientData.appVersion });
},
onTurnStart: async ({ clientData }) => {
if (clientData?.appVersion && clientData.appVersion !== initialAppVersion.version) {
chat.requestUpgrade();
}
},
run: async ({ messages, signal }) => {
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
},
});
```
This upgrades on **every** deploy, not just breaking changes. Good for fast-moving projects where you always want the latest code.
## Other agent types
- **`chat.agent()`** and **`chat.createSession()`** — use `chat.requestUpgrade()` as shown above
- **`chat.customAgent()`** — you control the turn loop, so just `return` from `run()` when you want to exit
## Interaction with recovery boot
`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.
## See also
- [Lifecycle hooks](/ai-chat/lifecycle-hooks) — where `onTurnStart` and `onChatResume` fit in the turn cycle
- [Recovery boot](/ai-chat/patterns/recovery-boot) — the sibling hook for mid-stream interruptions (does NOT fire on `requestUpgrade`)
- [Database persistence](/ai-chat/patterns/database-persistence) — how continuations interact with session state
- [Client Protocol](/ai-chat/client-protocol#step-4-handle-continuations) — how clients handle continuations at the wire level