How the Kimi Code Transcript System Manages Chat History and Replay

The Kimi Code transcript system maintains a synchronized, immutable history of chat sessions using a two-layer store architecture that applies operation-based mutations to enable real-time streaming and reliable replay across clients.

The transcript subsystem in the MoonshotAI/kimi-code repository is implemented within the @moonshot-ai/transcript package and serves as the single source of truth for all chat session data. It captures every aspect of an agent's execution—including turns, steps, frames, tasks, interactions, and global metadata—using a functional reducer pattern that ensures consistency between the server engine and multiple UI clients.

Two-Layer Architecture: L1 Store and L2 Root

The system separates concerns into two distinct layers to manage both individual agent state and multi-agent session coordination.

L1 Agent Store (AgentTranscript)

The AgentTranscript class in src/store/agentTranscript.ts maintains a pure, immutable snapshot of a single agent's state. All state changes occur through the apply() method, which delegates to the pure reducer function applyOperation defined in src/ops/apply.ts. This function accepts the current AgentState and a TranscriptOperation, returning a new state object along with a changed boolean flag.

L2 Root Store (TranscriptStore)

The TranscriptStore class in src/store/transcriptStore.ts manages a collection of per-agent AgentTranscript instances. It lazily initializes agent-specific stores via ensureAgent() and maintains a roster of active agents using the #descriptors private map containing AgentDescriptor objects. The store emits roster changes through onRosterChange listeners, driving UI components such as the agent picker.

Data Model and Schema Contracts

All data crossing process boundaries conforms to strict Zod schemas defined in src/contract/schema.ts, ensuring type safety across REST and WebSocket transports.

Discriminated Unions and Entities

The schema defines discriminated unions for core entities:

  • transcriptTurnSchema for conversation turns
  • transcriptStepSchema for execution steps
  • transcriptFrameSchema for content frames
  • transcriptMetaSchema for global state including goals, mode badges, and agent status

Operation Definitions

The transcriptOperationSchema defines every permissible mutation as a TranscriptOperation. Common operations include turn.upsert, step.upsert, frame.upsert, meta.merge, and append. Most operations are idempotent—replaying them produces identical state—except for append, which streams incremental text and detects gaps when the offset exceeds local buffer length.

State Convergence and the Reducer Pattern

When the server emits operation batches via WebSocket (transcript.ops) or REST reset, clients converge state through the pure reducer.

Applying Operations

The applyOperation function implements every operation type as a pure function:

// Simplified convergence flow
const result = agentTx.apply(ops); // ops: TranscriptOperation[]

if (result.gap) {
  // Offset mismatch detected, fetch fresh snapshot via REST
  fetch(`/api/v1/sessions/${sessionId}/transcript?agent_id=agent-main`);
}

Gap Detection and Recovery

The only non-idempotent operation, append, may report a gap when the text offset exceeds the locally known frame length. Upon detecting a gap, the client must fetch a fresh snapshot via the REST endpoint rather than attempting to reconcile the missing intermediate state.

Pagination and Windowed Resets

Full transcripts may contain thousands of turns, necessitating efficient pagination.

REST Pagination Interface

The GET /sessions/{id}/transcript endpoint returns paginated turns plus intervening markers or task references. The AgentTranscript.snapshot({ tailTurns }) method trims older turns while preserving a hasMoreOlder boolean flag, allowing the UI to implement infinite scroll history.

// Fetch newest 20 turns
fetch(`/api/v1/sessions/${sessionId}/transcript?agent_id=agent-main&page_size=20`)
  .then(r => r.json())
  .then(data => {
    // data.items contains turns + markers
    // data.has_more indicates additional older history
  });

Real-Time Synchronization and Replay

The system supports live streaming and connection resilience through sequence-numbered operation batches.

WebSocket Operation Streaming

Live updates flow from the Engine through TranscriptService (packages/kap-server/src/services/transcript/transcriptService.ts) to the WebSocket transcript.ops event. The client-side listener in apps/kimi-inspect/src/transcript/ws.ts feeds these operations into AgentTranscript.apply().

Sequence Numbers and Reconnection

Each operation batch carries a seq number (transcriptSeqSchema) serving as a per-agent watermark. Clients track the highest applied sequence; upon reconnection, they request GET /transcript/ops?since_seq=N. If the server's journal cannot satisfy the request (returning complete: false), the client falls back to a full REST reset.

Roster Management and Multi-Agent Support

TranscriptStore maintains the session roster via #descriptors, a map of AgentDescriptor objects containing agentId, type, and label properties. This roster enables server fan-out to specific agents and powers UI components like the agent picker, ensuring events route only to active agents.

Interaction and Tool Lifecycle

When tool frames are upserted via frame.upsert, the system simultaneously creates or updates the corresponding interaction entity through interaction.upsert. The applyItemsRemove function ensures that deleting a turn automatically cleans up anchored interactions—including pending approvals, questions, or tool calls—preventing orphaned UI states.

Practical Implementation Examples

Creating and Accessing Agent Transcripts

import { TranscriptStore } from '@moonshot-ai/transcript';

const store = new TranscriptStore('session-123');

// Lazily ensure agent transcript exists
const agentTx = store.ensureAgent('agent-main', {
  agentId: 'agent-main',
  type: 'main',
  label: 'Main Agent',
});

Streaming Incremental Content

Client-side LLM streaming generates append operations targeting specific frames:

const appendOp = {
  op: 'append',
  target: { 
    type: 'frame', 
    turnId: 'turn-5', 
    stepId: 'step-5.1', 
    frameId: 'frame-5.1.0' 
  },
  offset: 0,
  text: 'Hello, world!',
};

agentTx.apply([appendOp]); // Handles gap detection internally

Summary

  • The transcript system uses a two-layer architecture separating per-agent state (AgentTranscript) from session management (TranscriptStore).
  • All mutations flow through idempotent operations applied by the pure applyOperation reducer in src/ops/apply.ts.
  • Gap detection in append operations triggers REST snapshot refreshes when streaming offsets mismatch.
  • Sequence numbers enable reliable reconnection and incremental sync without full history transfers.
  • Zod schemas in src/contract/schema.ts enforce type safety across REST and WebSocket boundaries.
  • Automatic cleanup of interactions and tool frames prevents orphaned states when turns are removed.

Frequently Asked Questions

How does the transcript system handle network reconnections?

Clients maintain the highest seq number received from the transcriptSeqSchema. Upon reconnection, they request operations via GET /transcript/ops?since_seq=N. If the server cannot provide the missing operations (indicated by complete: false), the client automatically falls back to fetching a full snapshot via the REST transcript endpoint.

What makes most transcript operations idempotent?

Operations such as turn.upsert, step.upsert, and meta.merge specify complete entity states rather than deltas. Replaying these operations with identical IDs overwrites the same fields, producing deterministic results. The only exception is append, which modifies frame content incrementally and therefore cannot be safely replayed without offset validation.

How does pagination work for long chat histories?

The REST endpoint supports page_size parameters and the AgentTranscript.snapshot({ tailTurns }) method returns only recent turns while setting hasMoreOlder to true when additional history exists. The UI implements infinite scroll by fetching older pages when the user scrolls upward, merging results into the local AgentState via standard upsert operations.

What happens to pending tool approvals when a turn is deleted?

The applyItemsRemove operation automatically deletes any interaction entities anchored to the removed turn. This ensures that pending approvals, user questions, or incomplete tool calls are cleaned up immediately when their parent turn disappears, preventing stale UI states and orphaned server-side tasks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →