Serialization ↗
noOriginal Documentation
Documentation Index#
Fetch the complete documentation index at: https://docs.ag-ui.com/llms.txt Use this file to discover all available pages before exploring further.
Serialize event streams for history restore, branching, and compaction in AG-UI
Serialization#
Serialization in AG-UI provides a standard way to persist and restore the event stream that drives an agent–UI session. With a serialized stream you can:
- Restore chat history and UI state after reloads or reconnects
- Attach to running agents and continue receiving events
- Create branches (time travel) from any prior run
- Compact stored history to reduce size without losing meaning
This page explains the model, the updated event fields, and practical usage patterns with examples.
Core Concepts#
- Stream serialization – Convert the full event history to and from a portable representation (e.g., JSON) for storage in databases, files, or logs.
- Event compaction – Reduce verbose streams to snapshots while preserving semantics (e.g., merge content chunks, collapse deltas into snapshots).
- Run lineage – Track branches of conversation using a
parentRunId, forming a git‑like append‑only log that enables time travel and alternative paths.
Updated Event Fields#
The RunStarted event includes additional optional fields:
type RunStartedEvent = BaseEvent & {
type: EventType.RUN_STARTED
threadId: string
runId: string
/** Parent for branching/time travel within the same thread */
parentRunId?: string
/** Exact agent input for this run (may omit messages already in history) */
input?: AgentInput
}These fields enable lineage tracking and let implementations record precisely what was passed to the agent, independent of previously recorded messages.
Event Compaction#
Compaction reduces noise in an event stream while keeping the same observable outcome. A typical implementation provides a utility:
declare function compactEvents(events: BaseEvent[]): BaseEvent[]Common compaction rules include:
- Message streams – Combine
TEXT_MESSAGE_*sequences into a single message snapshot; concatenate adjacentTEXT_MESSAGE_CONTENTfor the same message. - Tool calls – Collapse tool call start/content/end into a compact record.
- State – Merge consecutive
STATE_DELTAevents into a single finalSTATE_SNAPSHOTand discard superseded updates. - Run input normalization – Remove from
RunStarted.input.messagesany messages already present earlier in the stream.
Branching and Time Travel#
Setting parentRunId on a RunStarted event creates a git‑like lineage. The
stream becomes an immutable append‑only log where each run can branch from any
previous run.
gitGraph
commit id: "run1"
commit id: "run2"
branch alternative
checkout alternative
commit id: "run3 (parent run2)"
commit id: "run4"
checkout main
commit id: "run5 (parent run2)"
commit id: "run6"Benefits:
- Multiple branches in the same serialized log
- Immutable history (append‑only)
- Deterministic time travel to any point
Examples#
Basic Serialization#
// Serialize event stream
const events: BaseEvent[] = [...];
const serialized = JSON.stringify(events);
await storage.save(threadId, serialized);
// Restore and compact later
const restored = JSON.parse(await storage.load(threadId));
const compacted = compactEvents(restored);Event Compaction#
Before:
[
{ type: "TEXT_MESSAGE_START", messageId: "msg1", role: "user" },
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg1", delta: "Hello " },
{ type: "TEXT_MESSAGE_CONTENT", messageId: "msg1", delta: "world" },
{ type: "TEXT_MESSAGE_END", messageId: "msg1" },
{ type: "STATE_DELTA", patch: { op: "add", path: "/foo", value: 1 } },
{ type: "STATE_DELTA", patch: { op: "replace", path: "/foo", value: 2 } },
]After:
[
{
type: "MESSAGES_SNAPSHOT",
messages: [{ id: "msg1", role: "user", content: "Hello world" }],
},
{
type: "STATE_SNAPSHOT",
state: { foo: 2 },
},
]Branching With parentRunId#
// Original run
{
type: "RUN_STARTED",
threadId: "thread1",
runId: "run1",
input: { messages: ["Tell me about Paris"] },
}
// Branch from run1
{
type: "RUN_STARTED",
threadId: "thread1",
runId: "run2",
parentRunId: "run1",
input: { messages: ["Actually, tell me about London instead"] },
}Normalized Input#
// First run includes full message
{
type: "RUN_STARTED",
runId: "run1",
input: { messages: [{ id: "msg1", role: "user", content: "Hello" }] },
}
// Second run omits already‑present message
{
type: "RUN_STARTED",
runId: "run2",
input: { messages: [{ id: "msg2", role: "user", content: "How are you?" }] },
// msg1 omitted; it already exists in history
}Implementation Notes#
- Provide SDK helpers for compaction and (de)serialization.
- Store streams append‑only; prefer incremental writes when possible.
- Consider compression when persisting long histories.
- Add indexes by
threadId,runId, and timestamps for fast retrieval.
See Also#
- Concepts: Events, State Management
- SDKs: TypeScript encoder and core event types