Serialization

no
Summary: Serialize event streams for history restore, branching, and compaction in AG-UI

Original 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 adjacent TEXT_MESSAGE_CONTENT for the same message.
  • Tool calls – Collapse tool call start/content/end into a compact record.
  • State – Merge consecutive STATE_DELTA events into a single final STATE_SNAPSHOT and discard superseded updates.
  • Run input normalization – Remove from RunStarted.input.messages any 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#

Link last verified June 7, 2026. View original ↗
Source: AG-UI Protocol
Link last verified: 2026-02-26