Chat Session Modeling
Chat sessions can be modeled at different layers of your architecture. The choice determines who owns message history, how long a workflow run stays active, and how clients reconnect after an interruption.
Workflow 5 applications should use AI SDK's WorkflowAgent for both patterns.
Single-turn workflows
Each user turn starts a new workflow run. The client or API owns conversation history and sends the complete UIMessage[] array with every request.
Workflow
Convert UI messages to model messages inside the workflow. WorkflowAgent writes durable ModelCallStreamPart values to the run stream.
API route
Convert the durable model-call stream to AI SDK UI chunks at the response boundary:
Client
WorkflowChatTransport reconnects when the HTTP connection ends before the workflow stream finishes:
Persist UIMessage[] in your application database. WorkflowAgent.stream() returns ModelMessage[], but there is no general conversion from model messages back to UI messages with all UI metadata intact.
Use the single-turn pattern when:
- Your application already owns chat history
- Each turn should run on the latest deployment
- You want a simple request-to-run mapping
- Approval responses or client-side tool results arrive as another message turn
Multi-turn workflows
A single workflow run can own the model-message history for the whole session. It waits on a Hook between turns, and external callers resume the Hook with the next message. The workflow run ID becomes the session identifier.
Create the Hook once, outside the loop. Recreating the same token on every turn causes a Hook conflict. Intermediate turns use preventClose: true with sendFinish: false; the workflow closes the durable stream and emits the final UI finish when the run returns.
Start and resume the session
Use one route to create the run and a second route to deliver follow-up messages:
Reconnect to the stream
WorkflowChatTransport counts transformed UI chunks, while the durable stream stores raw ModelCallStreamPart values. Always replay the raw stream from index 0 and apply the UI cursor in createModelCallToUIChunkTransform():
Persist UIMessage[] separately if the client must reconstruct user messages and UI metadata after a refresh. The workflow-owned ModelMessage[] history is the model's durable context, not a replacement for an application chat table.
Persist and display the full session
A multi-turn chat has three related records with different responsibilities:
- Workflow model history:
ModelMessage[]is the durable context sent back to the model on each turn. - Workflow run stream:
ModelCallStreamPartvalues contain durable model and tool output for live delivery and reconnection. - Application chat history:
UIMessage[]preserves user messages, display metadata, attachments, and application-specific parts.
When the user sends the first message, start the run, store its run ID with the application chat record, and connect to the run stream. For each follow-up, optimistically add and persist the user UIMessage, then resume chatMessageHook with the corresponding text. After a refresh, load the persisted UI messages and reconnect through the stream route above using the last persisted UI chunk cursor. If you persist only user messages, replay model output from the beginning and merge by stable message IDs.
Do not treat the run stream as the only chat database. WorkflowAgent deliberately stores raw model-call parts, and user messages resumed through a Hook are not automatically written to that output stream. Keeping the application history separate avoids synthetic stream markers and preserves UI information that cannot be reconstructed from ModelMessage[].
The reconnect route replays raw ModelCallStreamPart values from index 0 because raw parts and transformed UI chunks do not have matching indexes. uiStartIndex prevents already-delivered UI chunks from being sent to the client again, but the server still transforms the earlier raw history. For very long streams, split conversations into bounded runs until WorkflowAgent exposes a persisted raw-to-UI cursor mapping.
Use the multi-turn pattern when:
- One workflow should own the session's model context
- Backend events or other users need to inject messages through Hooks
- Full-session tracing is more important than running every turn on the newest deployment
- The application is prepared to manage a long-lived run and stream cursor
Choosing a pattern
| Consideration | Single-turn | Multi-turn |
|---|---|---|
| State ownership | Client or application database | Workflow for model context; application database for UI history |
| Deployment version | Latest deployment per turn | Deployment that started the run |
| Message injection | Start another run | Resume a Hook |
| Workflow complexity | Lower | Higher |
| Workflow time horizon | One model turn | Hours or longer |
| Observability scope | Per turn | Full session |
Multi-turn works well for new durable sessions. The workflow owns model context, accepts messages from users and backend systems through the same Hook, and provides one full-session trace.
Single-turn works well when adapting an existing architecture. If the application already manages message state and you want to adopt durable agents incrementally, one workflow run per turn requires fewer lifecycle changes and always uses the latest deployment.
Multiplayer chat sessions
The multi-turn pattern also supports messages from system events, external services, and multiple users. Every source resumes the same Hook; the workflow queues those messages and processes them between model turns.
Scheduled tasks, background jobs, or database triggers can inject updates into an active conversation:
A third-party webhook can notify the conversation about an external event:
Multiple authenticated users can participate in the same workflow-owned session. Include attribution when resuming the Hook:
To preserve structured attribution across refreshes, persist the corresponding UIMessage using the application-history approach described above.
Related documentation
- Building Durable AI Agents: Foundation guide for WorkflowAgent
- Message Queueing: Inject messages between model-call steps
- Resumable Streams: Reconnect to durable output
WorkflowAgent: AI SDK API for durable, resumable agents