Messages and Parts
Read Active and Segment history and understand Messages, Parts, and sequence
Messages and Parts
session.messages() returns every persisted Message in the current Active set. It does not take a limit; subscribe() complements the snapshot with live changes.
const page = await session.messages();
for (const message of page.items) {
render_message(message);
}It returns:
interface SessionMessagePage {
items: SessionMessage[];
total: number;
source: "active" | "segment";
start_sequence?: number;
end_sequence?: number;
next_before_sequence?: number;
has_more: boolean;
}total is the total number of real Messages created by the Session, not items.length. source identifies whether the result came from Active or one closed Segment.
Four top-level Message types
type | Meaning | Typical presentation |
|---|---|---|
user | User input or in-flight steering | User bubble and attachments |
assistant | One continuous generated response | Text, reasoning, tools, files |
action | Runtime activity such as a model change or compaction | Status notice |
error | A displayable Session or Turn error | Error callout and retry entry point |
A Session has one top-level Message sequence ordered by sequence. Tool calls are neither a second Timeline nor top-level Messages: they live in an Assistant Message's parts, so text -> tool -> text is persisted in its real generation order.
An ordinary Tool Loop, Provider continuation, or recovery retry does not create another Assistant Message. It appends ordered Parts to the current Assistant. Only a new User Message—including an in-flight steer—closes the preceding Assistant Message, after which later output creates the next Assistant Message. Model-step boundaries use step-start Parts and require no separate segment field.
SessionMessage is the persistence format; AI SDK UIMessage is the Executor and UI boundary format. Serializable User and Assistant UI parts can be projected into Session parts and restored as an equivalent UIMessage. action and error are Downcity Session top-level types and are never sent to the model.
If a Turn fails before producing any Assistant content, the Session writes one error Message and does not fabricate an Assistant reply from the error text. If partial content already exists, that Assistant is retained with status: "failed", followed by the structured error Message.
Assistant Parts
type | Key fields | Meaning |
|---|---|---|
text | text, state | Visible output, streaming -> done |
reasoning | text, state | Reasoning output; product decides whether to display it |
tool | tool_call_id, tool_name, state, metadata | Tool input, waiting, execution, and result |
interaction | interaction_id, interaction_type, status, request, response | Asynchronous user approval or questions |
file | url, media_type, provider_metadata | A file generated or referenced by the Assistant |
source | source_type, source_id, URL or document fields | An AI SDK URL or document source |
data | data_type, data, data_id | Persisted structured UI data |
step-start | No business fields | A boundary in a multi-step response |
Every Assistant Part has a stable part_id and a sequence that never changes after creation. UI chunks from each model step are the sole source of canonical order. The final step snapshot only validates order and enriches metadata; it cannot create, remove, or reorder Parts. A tool state change updates the same Part, so a UI should not create separate request and result rows.
Tool Provider metadata
A Tool Part preserves metadata attached to the tool call and tool result by the AI SDK Provider:
interface SessionAssistantToolPart {
title?: string;
tool_metadata?: JsonObject;
dynamic?: boolean;
call_provider_metadata?: ProviderMetadata;
result_provider_metadata?: ProviderMetadata;
provider_executed?: boolean;
preliminary?: boolean;
}These fields are opaque data required by capabilities such as Provider continuation. Applications may read and persist them unchanged, but should not modify Provider-specific fields. Older Sessions without these optional fields continue to load normally.
Text, reasoning, file, and source parts also preserve their own provider_metadata. Stream updates with the same data_id or source_id replace the existing Part without changing its sequence. An AI SDK data chunk with transient: true is delivered to the live UI but is not persisted in the Session.
Tool Parts do not carry a duplicate approval snapshot. User participation uses a separate Interaction Part:
interface SessionAssistantInteractionPart {
interaction_id: string;
interaction_type: "approval" | "question";
status: "pending" | "resolved" | "expired" | "cancelled";
request: SessionInteractionRequest;
response?: SessionInteractionResponse;
}A related Tool follows ready -> waiting-user -> running -> completed; denial, expiry, or
cancellation moves it to failed. The Tool and Interaction are committed in one Assistant
revision. Two Part Mutations with the same revision are two changes from one atomic snapshot,
not two independent transactions.
Order, revision, and visibility
Every Message has a stable message_id, a creation-time sequence that never changes, and a revision that increases with each complete snapshot update. A client must not replace a greater revision with a lower one.
At the end of each model step, the SDK validates the final UIMessage against the canonical chunks by Part count, type, order, and stable identity. The AI SDK can emit an empty placeholder Part for Text or Reasoning that has start/end events but no delta; the SDK ignores such Parts before validation and does not add them to canonical history. Any other final snapshot mismatch or canonical Part write failure fails the Turn. The SDK does not infer identity from text content and never publishes an incomplete completed history. When a client receives a complete message Mutation with a greater revision, it should replace the whole Message instead of retaining the older revision's Parts array.
messages() returns only visibility: "visible" Messages by default. Pass include_internal: true for debugging or audit. A Compact Summary is not a Message, consumes no sequence, and never appears in messages().
Loading older history
Call the method with no arguments first to load all Active Messages. When has_more is true, pass next_before_sequence back unchanged to read the immediately preceding whole Segment:
let page = await session.messages();
render_messages(page.items);
while (page.has_more && page.next_before_sequence !== undefined) {
page = await session.messages({
before_sequence: page.next_before_sequence,
});
prepend_messages(page.items);
}before_sequence must be a positive integer. Each historical read returns one complete Segment. There is no limit, cursor, offset, or slicing within a Segment. This keeps UI boundaries aligned with physical Compact boundaries and avoids reading cumulative Summaries as Messages.
Combining snapshots with live Mutations
- Use
messages()to establish the Active state. - Replace the targeted snapshot for a
messageorpartMutation. - Append new text or raw Tool input for a
deltaMutation. - Load older Segments one at a time with
next_before_sequence. - On disconnect, reload Active and merge by
message_id + revision.
See Reconnect and sync for synchronization and Metadata and storage for persistence details.