# How the Kimi Code Transcript System Manages Chat History and Replay

> Discover how the Kimi Code transcript system uses a two-layer architecture for synchronized, immutable chat history and real-time replay.

- Repository: [Moonshot AI/kimi-code](https://github.com/MoonshotAI/kimi-code)
- Tags: internals
- Published: 2026-07-25

---

**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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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:

```typescript
// 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.

```typescript
// 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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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

```typescript
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:

```typescript
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`](https://github.com/MoonshotAI/kimi-code/blob/main/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`](https://github.com/MoonshotAI/kimi-code/blob/main/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.