Messages and Parts

Understand canonical Messages, Parts, pagination, and live Mutations

session.messages() reads stable SQLite snapshots, paginated on Message boundaries. Use subscribe() for live changes.

const page = await session.messages({ limit: 50 });
for (const message of page.items) render_message(message);
interface SessionMessagePage {
  items: SessionMessage[];
  total: number;
  start_sequence?: number;
  end_sequence?: number;
  next_before_sequence?: number;
  has_more: boolean;
}

total is the number of real Messages in the Session, not items.length. limit defaults to 50 and is capped at 200.

Two top-level Message types

type SessionMessage = SessionUserMessage | SessionAgentMessage;
roleMeaningTop-level state
userUser input or an internal recovery instructionNo state field; complete when written
agentAgent output and runtime recordsstate: "streaming" | "done"

A User Message does not distinguish prompt from steer. Whether it starts a Turn or joins the current Turn is represented by turn_id and decided by SessionLoop, not duplicated in message content.

An Agent Message's state only answers whether Parts can still be appended. Success, stop, and failure belong to the Turn result. Persisted stop and failure reasons use an error Part.

A Session has one two-party Message sequence ordered by sequence. Tools, Interactions, Actions, and Errors are Agent Parts rather than independent Messages. Multiple model Steps in an ordinary Tool Loop may append to the same Agent Message; model-produced Parts carry their source step_id.

Action ownership rule: while the target Turn still has a streaming Agent Message, the Action Part is appended directly to that Message and shares its canonical identity (for example, changing the model or approval mode mid-run). Only when the Session is idle, the target Turn has no Agent Message yet, or the existing Message is already closed does an Action fall back to a standalone Agent Message containing just the Action Part.

User Parts

typeKey fieldsMeaning
texttextUser body
contexttag, contextSemantically tagged model context
fileurl, media_type, filenameA user-submitted file
datadata_type, data, data_idPersisted host/UI data not sent to the model

User Parts have no streaming state. A context Part becomes safely tagged text only at the model boundary:

// Canonical User Part
{ part_id: "user-context:1", sequence: 1, type: "context", tag: "reference", context: "Earlier message" }

// Model Content
{ type: "text", text: "<reference>Earlier message</reference>" }

visibility: "internal" uses the same User Message shape. Recovery instructions that affect the next Step are persisted first and then read from canonical history by the Composer; there is no separate runtime-input protocol.

Agent Parts

typeKey fieldsMeaning
texttext, stateVisible output, streaming -> done
reasoningtext, stateReasoning output
tooltool_call_id, tool_name, state, input, outputComplete Tool lifecycle
interactioninteraction_id, status, request, responseAsync approval or questions
fileurl, media_type, filenameAgent file result
datadata_type, data, data_idStructured UI data
actionaction_id, action_type, state, titleSession operation state
errorscope, code, message, recoverableSession or Turn error

Every Part has a stable part_id and immutable sequence. A Tool state transition updates the same Part instead of creating duplicate request/result messages. A Tool and its related Interaction transition commit in one checkpoint transaction.

Streaming and persistence boundary

Events drive live rendering
Memory assembles the stream
The database stores stable semantic checkpoints

Model stream start, delta, and finish events first update the current Agent Message's in-memory projection and publish part / delta Mutations. Ordinary chunks do not write SQLite. When a model Step completes, the SDK validates Part identity and commits the complete Step snapshot in one transaction.

Tool transitions, Interaction changes, Actions/Errors, and Message closure are durable semantic checkpoints and may commit independently. A committed message Mutation replaces the live projection with a complete snapshot.

Ordering, revision, and reconnects

Every Message has a stable message_id, immutable sequence, and increasing revision. Clients should:

  1. Subscribe and buffer Mutations before loading the initial snapshot.
  2. Merge complete message snapshots by message_id + revision.
  3. Apply part and delta only while the live stream is continuous.
  4. Reload snapshots after disconnect instead of assuming Deltas can be replayed.

An interrupted, unfinished Step projection may be discarded. The Session closes the persisted streaming Agent Message as done and appends an Error Part, so SQLite is authoritative after reconnect.

Loading older history

let page = await session.messages({ limit: 50 });
while (page.has_more && page.next_before_sequence !== undefined) {
  page = await session.messages({
    before_sequence: page.next_before_sequence,
    limit: 50,
  });
  prepend_messages(page.items);
}

By default, only visibility: "visible" Messages are returned. Pass include_internal: true for debugging or audit. Composer Policy summaries are derived data: they consume no Message sequence and never appear in messages().

See Reconnect and sync for synchronization and Metadata and storage for persistence details.