Skip to content

Commit 80bb600

Browse files
ericallamclaude
andauthored
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>
1 parent 0b5b817 commit 80bb600

45 files changed

Lines changed: 13588 additions & 70 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/ai-chat/actions.mdx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
title: "Actions"
3+
sidebarTitle: "Actions"
4+
description: "Custom commands sent from the frontend that mutate chat state without consuming a turn — undo, rollback, edit, regenerate."
5+
---
6+
7+
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8+
9+
<RcBanner />
10+
11+
## Overview
12+
13+
Custom actions let the frontend send structured commands (undo, rollback, edit, regenerate) that modify the conversation state. **Actions are not turns**: they fire `hydrateMessages` (if set) and `onAction` only. No turn lifecycle hooks (`onTurnStart` / `prepareMessages` / `onBeforeTurnComplete` / `onTurnComplete`), no `run()`, no turn-counter increment. The trace span is named `chat action`.
14+
15+
Actions wake the agent from suspension the same way a new message does, run their handler against the latest accumulator state, and emit a `trigger:turn-complete` chunk so the frontend's `useChat` knows the action has been applied.
16+
17+
## Defining an action handler
18+
19+
Define an `actionSchema` for validation and an `onAction` handler that uses [`chat.history`](/ai-chat/backend#chat-history) to modify state:
20+
21+
```ts
22+
import { z } from "zod";
23+
24+
export const myChat = chat.agent({
25+
id: "my-chat",
26+
actionSchema: z.discriminatedUnion("type", [
27+
z.object({ type: z.literal("undo") }),
28+
z.object({ type: z.literal("rollback"), targetMessageId: z.string() }),
29+
z.object({ type: z.literal("edit"), messageId: z.string(), text: z.string() }),
30+
]),
31+
32+
onAction: async ({ action }) => {
33+
switch (action.type) {
34+
case "undo":
35+
chat.history.slice(0, -2); // Remove last user + assistant exchange
36+
break;
37+
case "rollback":
38+
chat.history.rollbackTo(action.targetMessageId);
39+
break;
40+
case "edit":
41+
chat.history.replace(action.messageId, {
42+
id: action.messageId,
43+
role: "user",
44+
parts: [{ type: "text", text: action.text }],
45+
});
46+
break;
47+
}
48+
// returning void → side-effect-only, no model call
49+
},
50+
51+
run: async ({ messages, signal }) => {
52+
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
53+
},
54+
});
55+
```
56+
57+
**Lifecycle flow:** Wake → parse action against `actionSchema``hydrateMessages` (if set) → **`onAction`** → apply `chat.history` mutations → emit `trigger:turn-complete` → wait for next message.
58+
59+
## Returning a model response from an action
60+
61+
`onAction` can return a `StreamTextResult`, `string`, or `UIMessage` to produce a response. The returned stream is auto-piped to the frontend just like a normal turn, but the rest of the turn machinery (`onTurnStart`, `onTurnComplete`, etc.) still does not fire.
62+
63+
```ts
64+
onAction: async ({ action, messages }) => {
65+
if (action.type === "regenerate") {
66+
chat.history.slice(0, -1); // drop the last assistant
67+
return streamText({
68+
model: anthropic("claude-sonnet-4-5"),
69+
messages,
70+
stopWhen: stepCountIs(15),
71+
});
72+
}
73+
// other actions return void → side-effect only
74+
}
75+
```
76+
77+
This is useful for actions that both mutate state and want a fresh model response (regenerate-from-here, retry-with-different-style). Persistence is your responsibility inside `onAction` itself; you have access to the streamed response object.
78+
79+
## Gating actions on HITL state
80+
81+
If you have a [human-in-the-loop](/ai-chat/patterns/human-in-the-loop) tool waiting on `addToolOutput`, you usually want to refuse competing actions like `regenerate` until the answer arrives. [`chat.history.getPendingToolCalls()`](/ai-chat/backend#chat-history) gives you exactly that signal:
82+
83+
```ts
84+
onAction: async ({ action, messages, signal }) => {
85+
if (action.type === "regenerate") {
86+
if (chat.history.getPendingToolCalls().length > 0) return; // gated
87+
chat.history.slice(0, -1);
88+
return streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal });
89+
}
90+
},
91+
```
92+
93+
## Sending actions from the frontend
94+
95+
```ts
96+
// Browser — TriggerChatTransport
97+
const stream = await transport.sendAction(chatId, { type: "undo" });
98+
99+
// Server — AgentChat
100+
const stream = await agentChat.sendAction({ type: "rollback", targetMessageId: "msg-3" });
101+
```
102+
103+
The action payload is validated against `actionSchema` on the backend; invalid actions throw and surface as a stream error. The `action` parameter in `onAction` is fully typed from the schema.
104+
105+
<Note>
106+
For silent state changes that should never appear as a turn (e.g. injecting background context), use [`chat.inject()`](/ai-chat/background-injection) instead. Actions are explicit user-driven mutations; injections are agent-side context updates.
107+
</Note>
108+
109+
## See also
110+
111+
- [`chat.history`](/ai-chat/backend#chat-history) — the imperative API actions use to mutate state
112+
- [Sending actions from the frontend](/ai-chat/frontend#sending-actions)`transport.sendAction` ergonomics
113+
- [`hydrateMessages`](/ai-chat/lifecycle-hooks#hydratemessages) — fires before `onAction` when set
114+
- [Branching conversations](/ai-chat/patterns/branching-conversations) — pairs action handlers with backend-controlled history
115+
- [Human-in-the-loop](/ai-chat/patterns/human-in-the-loop) — gating fresh actions while a tool is waiting

0 commit comments

Comments
 (0)