Skip to content

Commit b479ea8

Browse files
committed
docs(ai-chat): action turns become side-effect-only
Update the actions section of backend.mdx to reflect the new chat.agent semantics: actions fire hydrateMessages + onAction only, no turn lifecycle hooks, no run(). Documents the new return shapes on onAction (void / StreamTextResult / string / UIMessage) and adds a regenerate-from-here example for the model-response case. Closes TRI-9118 (docs portion).
1 parent e7fb447 commit b479ea8

1 file changed

Lines changed: 24 additions & 4 deletions

File tree

docs/ai-chat/backend.mdx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ export const myChat = chat.agent({
870870

871871
### Actions
872872

873-
Custom actions let the frontend send structured commands (undo, rollback, edit) that modify the conversation state before the LLM responds. Actions use the same input stream as messages, so they wake the agent from suspension and trigger a full turn.
873+
Custom actions let the frontend send structured commands (undo, rollback, edit) 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`.
874874

875875
Define an `actionSchema` for validation and an `onAction` handler that uses `chat.history` to modify state:
876876

@@ -901,6 +901,7 @@ export const myChat = chat.agent({
901901
});
902902
break;
903903
}
904+
// returning void → side-effect-only, no model call
904905
},
905906

906907
run: async ({ messages, signal }) => {
@@ -909,7 +910,26 @@ export const myChat = chat.agent({
909910
});
910911
```
911912

912-
**Lifecycle flow:** Wake → parse action against `actionSchema``hydrateMessages` (if set) → **`onAction`** → apply `chat.history` mutations → `onTurnStart``run()``onTurnComplete`
913+
**Lifecycle flow:** Wake → parse action against `actionSchema``hydrateMessages` (if set) → **`onAction`** → apply `chat.history` mutations → emit `trigger:turn-complete` → wait for next message.
914+
915+
#### Returning a model response from an action
916+
917+
`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.
918+
919+
```ts
920+
onAction: async ({ action, messages }) => {
921+
if (action.type === "regenerate") {
922+
chat.history.slice(0, -1); // drop the last assistant
923+
return streamText({
924+
model: openai("gpt-4o"),
925+
messages,
926+
});
927+
}
928+
// other actions return void → side-effect only
929+
}
930+
```
931+
932+
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.
913933

914934
On the frontend, send actions via the transport:
915935

@@ -921,10 +941,10 @@ const stream = await transport.sendAction(chatId, { type: "undo" });
921941
const stream = await agentChat.sendAction({ type: "rollback", targetMessageId: "msg-3" });
922942
```
923943

924-
The action payload is validated against `actionSchema` on the backend — invalid actions throw and abort the turn. The `action` parameter in `onAction` is fully typed from the schema.
944+
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.
925945

926946
<Note>
927-
Actions always trigger `run()` — the LLM responds to the modified state. For silent state changes that don't need a response (e.g. injecting background context), use [`chat.inject()`](/ai-chat/background-injection) instead.
947+
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.
928948
</Note>
929949

930950
### Chat history

0 commit comments

Comments
 (0)