# How to Use the Transcript Package in Kimi-Code: A Complete Guide to Session State Management

> Master Kimi-Code session state management with the transcript package. Learn to use TranscriptStore and AgentTranscript for immutable data layers and pure reducer updates. A complete guide.

- Repository: [Moonshot AI/kimi-code](https://github.com/MoonshotAI/kimi-code)
- Tags: how-to-guide
- Published: 2026-08-14

---

**The `@moonshot-ai/transcript` package provides an isomorphic, immutable data layer for storing every turn, step, and frame in a Kimi session through `TranscriptStore` and `AgentTranscript` with pure reducer-based state updates.**

This guide covers the core architecture, practical usage patterns, and implementation details of the **transcript package** in the [MoonshotAI/kimi-code](https://github.com/MoonshotAI/kimi-code) repository. Whether you're building server-side agents or browser-based inspection UIs, this engine-agnostic package handles session state without runtime dependencies.

---

## Core Architecture of the Transcript Package

The **transcript package** organizes session data into four composable layers. Understanding these layers is essential for effective integration.

### TranscriptStore: Session-Level Container

Located in [`packages/transcript/src/store/transcriptStore.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/store/transcriptStore.ts), `TranscriptStore` serves as the root container for a Kimi session:

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

const store = new TranscriptStore('session-123');
const mainTx = store.ensureAgent('main', { agentId: 'main', type: 'main' });

```

Key capabilities:

- **Lazy creation** — `ensureAgent()` instantiates `AgentTranscript` only when first accessed
- **Roster management** — `agents()` returns current agent descriptors; `onRosterChange()` streams updates to clients
- **Fan-out support** — server implementations use roster listeners to broadcast agent lifecycle events

### AgentTranscript: Per-Agent Immutable Store

The L1 store in [`packages/transcript/src/store/agentTranscript.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/store/agentTranscript.ts) maintains a single agent's complete history:

```typescript
export class AgentTranscript {
  apply(ops: readonly TranscriptOperation[]): AppliedOps;
  snapshot(window?: { tailTurns: number }): AgentTranscriptSnapshot;
  listPendingInteractions(): readonly InteractionId[];
  getItems(): readonly TranscriptItem[];
  getTurn(turnId: string): TranscriptTurn | undefined;
}

```

Critical properties:

- Holds **one immutable `AgentState`** (`#state` private field)
- **Copy-on-write semantics** — `apply()` processes operation batches atomically
- **Self-consistent snapshots** — earlier snapshots remain valid regardless of subsequent operations

### applyOperation: The Pure Reducer

All state mutations flow through [`packages/transcript/src/ops/apply.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/ops/apply.ts):

```typescript
export function applyOperation(state: AgentState, op: TranscriptOperation): ApplyResult {
  switch (op.op) {
    case 'reset':          return applyReset(state, op);
    case 'turn.upsert':    return applyTurnUpsert(state, op.turn);
    case 'step.upsert':    return applyStepUpsert(state, op.turnId, op.step);
    case 'frame.upsert':   return applyFrameUpsert(state, op);
    case 'append':         return applyAppend(state, op);
    // … additional operations
  }
}

```

Design characteristics:

- **Stateless function** — never mutates input `state`; returns new `AgentState` with structural sharing
- **Idempotent ops** — `turn.upsert`, `step.upsert`, `frame.upsert` produce identical results on replay
- **Non-idempotent `append`** — may return `gap` when offset exceeds local buffer, requiring resynchronization

---

## Working with Transcript Operations

### Creating Turn, Step, and Frame Hierarchies

The **transcript package** models conversation as nested entities: **Turn** → **Step** → **Frame**:

```typescript
mainTx.apply([
  // Create a turn representing user input
  { 
    op: 'turn.upsert', 
    turn: { 
      kind: 'turn', 
      turnId: 't1', 
      ordinal: 1, 
      state: 'running', 
      origin: { kind: 'user' } 
    } 
  },
  // Add a processing step within the turn
  { 
    op: 'step.upsert', 
    turnId: 't1', 
    step: { 
      kind: 'step', 
      stepId: 't1.1', 
      turnId: 't1', 
      ordinal: 1, 
      state: 'running' 
    } 
  },
  // Attach a text frame to capture assistant output
  { 
    op: 'frame.upsert', 
    turnId: 't1', 
    stepId: 't1.1',
    frame: { 
      kind: 'text', 
      frameId: 't1.1.f1', 
      role: 'assistant', 
      text: '' 
    } 
  },
]);

```

Operation types are defined in [`packages/transcript/src/ops/operation.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/ops/operation.ts).

### Streaming Text with Append Operations

Real-time token streaming uses the `append` operation with offset tracking:

```typescript
const result = mainTx.apply([
  { 
    op: 'append', 
    target: { 
      type: 'frame', 
      turnId: 't1', 
      stepId: 't1.1', 
      frameId: 't1.1.f1' 
    },
    offset: 0,
    text: 'First chunk' 
  },
]);

// Later, append at the correct offset
const result2 = mainTx.apply([
  { 
    op: 'append', 
    target: { type: 'frame', turnId: 't1', stepId: 't1.1', frameId: 't1.1.f1' },
    offset: 11,  // length of "First chunk"
    text: ' second chunk' 
  },
]);

// Handle synchronization gaps
if (result2.gap) {
  console.error('Gap detected:', result2.gap); // { expected: 11, got: <offset> }
  // Request fresh snapshot via 'reset' operation
}

```

### Using appendAtOffset for Client-Side Buffering

For web clients managing local buffers, the **transcript package** exports `appendAtOffset`:

```typescript
import { appendAtOffset } from '@moonshot-ai/transcript';

// Successful append
const ok = appendAtOffset('hello', 5, ' world');
// => { text: 'hello world', changed: true }

// Gap condition (client ahead of server)
const gap = appendAtOffset('hello', 10, '!');
// => { text: 'hello', changed: false, gap: { expected: 5, got: 10 } }

```

This utility in [`packages/transcript/src/ops/apply.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/ops/apply.ts) mirrors the web client's "alignDelta" semantics without requiring full store instantiation.

---

## Reading State and Managing Snapshots

### Accessing Current Data

```typescript
// Full conversation history (ordered by ordinal)
const items: readonly TranscriptItem[] = mainTx.getItems();

// Direct turn lookup
const turn = mainTx.getTurn('t1');
const step = turn?.steps.find(s => s.stepId === 't1.1');
const frame = step?.frames[0];

// Auxiliary entities
const task = mainTx.getTask('task-42');
const prompt = mainTx.getPrompt('prompt-7');
const metadata = mainTx.getMeta();

```

### Windowed Snapshots for Pagination

Large transcripts require memory-efficient access patterns:

```typescript
// Full snapshot (all turns)
const full = mainTx.snapshot();

// Partial snapshot — newest 50 turns only
const recent = mainTx.snapshot({ tailTurns: 50 });

if (recent.hasMoreOlder) {
  // UI should offer "load more" action
}

```

The `paginate` helper in [`packages/transcript/src/pagination/paginate.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/pagination/paginate.ts) provides cursor-based iteration for streaming implementations.

---

## Handling Pending Interactions

The **transcript package** tracks asynchronous user interactions (approvals, clarifications, etc.):

```typescript
// Submit interaction awaiting user response
mainTx.apply([
  { 
    op: 'interaction.upsert',
    interaction: { 
      interactionId: 'ia-1', 
      interactionKind: 'approval',
      toolCallId: 'call-99', 
      state: 'pending' 
    } 
  },
]);

// Check pending queue
console.log(mainTx.listPendingInteractions()); // ['ia-1']

// Record resolution
mainTx.apply([
  { 
    op: 'interaction.upsert',
    interaction: { 
      interactionId: 'ia-1', 
      interactionKind: 'approval',
      toolCallId: 'call-99', 
      state: 'approved'  // or 'rejected'
    } 
  },
]);

console.log(mainTx.listPendingInteractions()); // []

```

Interaction state management enables reliable human-in-the-loop workflows without blocking the main event loop.

---

## Extending with Custom Views

For custom UI implementations, register frame renderers:

```typescript
import { registerView } from '@moonshot-ai/transcript';

// Register component for 'mermaid' diagram frames
registerView('mermaid', (frame) => {
  return <MermaidRenderer diagram={frame.diagram} />;
});

// Register handler for custom tool output
registerView('custom-tool', (frame, context) => {
  return <ToolOutputView data={frame.output} agent={context.agentId} />;
});

```

The registry in [`packages/transcript/src/view/registry.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/view/registry.ts) decouples frame type definitions from presentation logic.

---

## Critical Implementation Details

### Immutability Guarantees

- Never mutate arrays from `getItems()`, `getTurn()`, or snapshot objects
- Always treat `AgentState` as opaque — use only `apply()` for modifications
- Structural sharing in `applyOperation` minimizes memory overhead for large sessions

### Idempotency and Causal Ordering

| Operation | Idempotent | Notes |
|-----------|-----------|-------|
| `reset` | Yes | Replaces entire state |
| `turn.upsert` | Yes | Keyed by `turnId` |
| `step.upsert` | Yes | Keyed by `stepId` |
| `frame.upsert` | Yes | Keyed by `frameId` |
| `append` | **No** | Offset-sensitive; may produce gaps |

Replay safety enables optimistic updates and network partition recovery.

### Gap Detection Protocol

When `apply()` returns `gap: true`:

1. Halt further `append` operations for that frame
2. Request `reset` operation from authoritative source (server or persisted log)
3. Re-apply pending local operations against new baseline

This protocol ensures eventual consistency without global locks.

---

## Complete Working Example

```typescript
import { TranscriptStore, appendAtOffset } from '@moonshot-ai/transcript';

async function demonstrateTranscriptPackage() {
  // Initialize session store
  const store = new TranscriptStore('demo-session');
  
  // Subscribe to agent roster changes
  const rosterSub = store.onRosterChange((agents) => {
    console.log('Active agents:', agents.map(a => a.agentId));
  });

  // Create main agent transcript
  const tx = store.ensureAgent('main', { 
    agentId: 'main', 
    type: 'main',
    name: 'Assistant'
  });

  // Build conversation structure
  tx.apply([
    { 
      op: 'turn.upsert', 
      turn: { 
        kind: 'turn', 
        turnId: 't-001', 
        ordinal: 1, 
        state: 'completed', 
        origin: { kind: 'user' } 
      } 
    },
    { 
      op: 'step.upsert', 
      turnId: 't-001', 
      step: { 
        kind: 'step', 
        stepId: 't-001.s1', 
        turnId: 't-001', 
        ordinal: 1, 
        state: 'completed' 
      } 
    },
    { 
      op: 'frame.upsert', 
      turnId: 't-001', 
      stepId: 't-001.s1',
      frame: { 
        kind: 'text', 
        frameId: 't-001.s1.f1', 
        role: 'assistant', 
        text: 'I understand. Let me help with that.' 
      } 
    },
  ]);

  // Simulate streaming response
  let offset = 0;
  const chunks = ['Analyzing', ' your', ' request...'];
  
  for (const chunk of chunks) {
    const result = tx.apply([{
      op: 'append',
      target: { type: 'frame', turnId: 't-001', stepId: 't-001.s1', frameId: 't-001.s1.f1' },
      offset,
      text: chunk
    }]);
    
    if (result.gap) {
      console.error('Synchronization gap detected');
      break;
    }
    offset += chunk.length;
  }

  // Read and verify final state
  const turn = tx.getTurn('t-001');
  const finalText = turn?.steps[0]?.frames[0];
  console.log('Final frame:', finalText);

  // Cleanup
  rosterSub.dispose();
}

demonstrateTranscriptPackage();

```

Expected output:

```

Active agents: ['main']
Final frame: { kind: 'text', frameId: 't-001.s1.f1', role: 'assistant', text: 'I understand. Let me help with that.Analyzing your request...' }

```

---

## Summary

- **`TranscriptStore`** — session root that lazily creates `AgentTranscript` instances and manages agent roster subscriptions via `onRosterChange()`
- **`AgentTranscript`** — immutable per-agent store with `apply()` for operations, `snapshot()` for reads, and `listPendingInteractions()` for async workflows
- **`applyOperation`** — pure reducer in [`packages/transcript/src/ops/apply.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/ops/apply.ts) that processes all state changes with copy-on-write semantics
- **`append`** — the only non-idempotent operation; requires gap detection and resynchronization protocol
- **Pagination & Views** — `paginate` helper and `registerView` extensibility for production UIs

---

## Frequently Asked Questions

### What makes the transcript package "isomorphic"?

The **transcript package** contains zero runtime dependencies on browser APIs or Node.js-specific modules. It operates purely on in-memory data structures, allowing identical code to run in Kap-Server (Node.js), browser inspection UIs, and test environments. All I/O concerns (networking, persistence) are handled by external callers through the operation-based API.

### How does the transcript package handle concurrent edits?

Concurrency is managed through optimistic updates with conflict detection. The `append` operation includes an `offset` parameter — if the local state has diverged from the authoritative source, `apply()` returns a `gap` object containing `{ expected: <length>, got: <offset> }`. The caller must then request a `reset` operation to obtain fresh state before continuing.

### What's the difference between `apply()` and `snapshot()`?

`apply()` **modifies** state by applying one or more `TranscriptOperation` objects, returning `AppliedOps` with any gap signals. `snapshot()` **reads** state without modification, returning an immutable `AgentTranscriptSnapshot` suitable for UI rendering or persistence. For windowed access, pass `{ tailTurns: N }` to snapshot only recent history.

### Where are the model types (TurnHeader, StepHeader, etc.) defined?

All domain types live in `packages/transcript/src/model/`: [`turn.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/turn.ts) defines `TurnHeader` and `StepHeader`; [`frame.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/frame.ts) contains `TranscriptFrame` and variants; [`ids.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/ids.ts) provides branded identifier types. These are re-exported from [`packages/transcript/src/index.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/index.ts) for convenient importing.