State And Projections
How storage envelopes, transport state, and public messages stay separated.
PAI uses different representations where the boundaries need different guarantees, but exposes only one ordinary application transcript.
| Representation | Audience | Purpose |
|---|---|---|
| Stored message envelope | Storage providers and runtime recovery | Thin authorization/storage wrapper around one complete SDK-native message |
| Runtime thread snapshot | Internal runtime workflows | Version-consistent messages, runs, queue, lease, and thread authority |
| Transport state/events | Client transport implementations | Authorized native messages, SDK chunks, control events, and repair |
ThreadState | Applications and React | Projected PaiMessage[] plus normalized run, queue, and thread state |
Public State
type ThreadState<TContract extends AgentContract = AgentContract> = {
thread: PaiThreadHead<TContract>;
messages: PaiMessage<TContract>[];
runs: RunState<TContract>[];
activeRunId: RunId | null;
queue: ThreadQueueView<TContract>;
usage: UsageSummary;
capabilities: ThreadCapabilityView;
error: ThreadErrorRecord | null;
};messages is the canonical render order. It contains AI SDK-native parts plus
PAI projection enrichments such as attachments, ToolData, suspension history,
and reasoning summaries. Raw reserved data-pai-* parts are consumed during
projection and never exposed as a parallel public grammar.
Runs stay normalized in state.runs. Group messages by
message.metadata.pai.runId only when a UI needs turn containers. Queued work
stays in state.queue.items until admission.
Transport Reduction
During generation, the runtime emits SDK UIMessageChunk values. The client
keeps a private long-lived native reducer for each active message, then projects
the resulting complete message. Snapshot repair replaces that private state and
clears transient overlays.
Application code should consume ThreadState, not transport events. A transport
can reconnect, replay a generation, or repair a gap without changing the public
message shape.
Stored Messages
One message document is a thin scope/visibility/private-metadata envelope around
one complete, strict-JSON SDK-major-pinned UIMessage. Run association, producer,
relation, status, timestamps, usage, and other public PAI facts live under
message.metadata.pai.
Storage providers remain contract-neutral. Core refines application metadata, application data parts, and static tool payloads against the active agent contract before messages enter application-facing state or model replay.
Control State
Runs, queue items, leases, authorization, and pending commands are normalized
control records rather than message metadata. Tool renderer runStatus and
action/error sidecars are derived from those facts without mutating native tool
parts.
This is the key ownership rule:
native UIMessage durable transcript authority
normalized controls run/queue/lease/action authority
PaiMessage projection consumer-only enrichment over both