# How to Debug Session Issues Using the Kimi Code Transcript System

> Debug session issues effectively with the Kimi Code transcript system. Inspect live stores, rebuild snapshots, and monitor operation streams to resolve synchronization errors. Learn how today.

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

---

**The Kimi Code transcript system serves as the single source of truth for session state, exposing APIs to inspect live stores, rebuild cold snapshots, and monitor operation streams to diagnose missing turns, journal gaps, and synchronization errors.**

The transcript system in the [MoonshotAI/kimi-code](https://github.com/MoonshotAI/kimi-code) repository captures every interaction within a session—from turns, steps, and frames to attachments, todos, and metadata—providing immutable state management through a hierarchy of specialized stores and services. When sessions exhibit unexpected behavior such as missing turns, out-of-order operations, or stale agent data, developers can leverage the transcript APIs to trace exactly how the engine constructed the current state and where synchronization may have failed.

## Understanding the Transcript Architecture

### TranscriptStore (Per-Session Root)

Located in [`packages/transcript/src/store/transcriptStore.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/store/transcriptStore.ts), the **TranscriptStore** acts as the root container for each session. It lazily creates an `AgentTranscript` for every participating agent and maintains the agent roster through methods like `ensureAgent()`, which guarantees the existence of an agent transcript, and `agents()`, which exposes roster changes and provides snapshots.

### AgentTranscript (Per-Agent L1 Store)

Found in [`packages/transcript/src/store/agentTranscript.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/transcript/src/store/agentTranscript.ts), the **AgentTranscript** holds the immutable `AgentState` and applies operations via `apply()`. It emits change events through `onChange` and provides read-only access through `getItems()`, `getTurn(id)`, and `snapshot(options)`. Crucially, the `apply()` method returns `{ accepted, gap }`, where the `gap` property indicates missing sequence numbers that could signal truncated journals or missed appends.

### TranscriptService (Server Layer)

Located in [`packages/kap-server/src/services/transcript/transcriptService.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/kap-server/src/services/transcript/transcriptService.ts), the **TranscriptService** bridges the engine to persistent storage and manages three critical paths:

- **Live path**: `forSessionLive()` creates or retrieves active stores; `whenReady()` waits for back-fill completion before the store is considered ready.
- **Cold path**: `readColdSnapshot()` rebuilds state from persisted `wire.jsonl` via the `snapshotReader` in [`packages/kap-server/src/snapshot/snapshotReader.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/kap-server/src/snapshot/snapshotReader.ts); `readColdRoster()` restores agents from [`state.json`](https://github.com/MoonshotAI/kimi-code/blob/main/state.json).
- **Op handling**: `onSessionOps()` subscribes to mapped operation batches; `getOpsSince()` returns journal slices for catch-up; `journalOps()` maintains per-agent sequence watermarks; `healEndedTurns()` reconciles persisted turns after completion to recover missing frames.

## Debugging Workflow

### 1. Determine Live vs. Cold State

First, check if the session is actively managed in memory or requires file-based reconstruction.

```typescript
const store = transcriptService.forSessionLive(sessionId);
if (store) {
  // Live session: operations are being journaled in real-time
} else {
  // Cold session: must fall back to file-based snapshot
}

```

### 2. Inspect the Live Transcript

Access specific agent data to verify turn counts and recent activity using the `AgentTranscript` API.

```typescript
const agent = store.ensureAgent(agentId);
console.log('Turn count:', agent.getItems().filter(i => i.kind === 'turn').length);
console.log('Specific turn:', agent.getTurn('t123'));
console.log('Snapshot (last 20 turns):', agent.snapshot({ tailTurns: 20 }));

```

### 3. Examine the Op-Batch Journal

Verify operation continuity using sequence watermarks to detect truncation.

```typescript
const watermark = transcriptService.getSeqWatermark(sessionId, agentId);
const catchup = transcriptService.getOpsSince(sessionId, agentId, watermark - 10);
if (catchup?.complete) {
  console.log('Recent ops:', catchup.batches);
} else {
  console.warn('Journal truncated – request full refresh');
}

```

### 4. Subscribe to Live Operations

Monitor real-time operation streams to detect missing or unexpected sequences as they arrive.

```typescript
const disposable = transcriptService.onSessionOps(sessionId, (event, seq) => {
  console.log(`Seq ${seq} – ${event.agentId} ops:`, event.ops);
});
// Cleanup subscription when done investigating
disposable?.dispose();

```

### 5. Force a Cold Rebuild

Compare live state against persisted history to identify divergence or staleness.

```typescript
const snapshot = await transcriptService.readColdSnapshot(sessionId, agentId);
if (snapshot) {
  console.log('Cold turn count:', snapshot.items.filter(i => i.kind === 'turn').length);
}

```

### 6. Detect Journal Gaps

Identify missing append operations when targets are already closed, which prevents placement.

```typescript
const result = agent.apply([]);
if (result.gap) {
  console.warn('Append gap detected:', result.gap);
}

```

### 7. Heal Ended Turns

Trigger reconciliation for recently completed turns to recover tool frames that may have been missed during live projection.

```typescript
// Replace 'ordinal' with the completed turn's ordinal number
await transcriptService['healEndedTurns'](sessionId, agentId, new Set([ordinal]));

```

### 8. Verify Agent Roster

Check persisted agent descriptors when UI pickers appear empty or missing expected participants.

```typescript
const roster = await transcriptService.readColdRoster(sessionId);
console.log('Agent roster:', roster);

```

## Common Debug Scenarios

**Missing Recent Turns**
Likely caused by journal capacity limits (`TRANSCRIPT_OPS_JOURNAL_CAPACITY = 2000`). Call `getOpsSince()` with a `sinceSeq` near the current watermark; if `complete` returns `false`, the batches have been evicted and you must request a full snapshot via `readColdSnapshot()`.

**Duplicate Turns**
Indicates a race condition where back-fill occurred after a live `turn.upsert`. Compare the live `AgentTranscript` snapshot with the cold snapshot; the `healTurnOps` logic (lines 992-1040) ensures live state wins on overlapping fields while merging persisted data.

**Tool Result Never Appears**
Occurs when the projector missed the frame during a mid-turn attachment, persisting the result only to disk. Verify that `journalOps` recorded the batch, then run `healEndedTurns()` manually; the `healTurnOps` function (lines 731-791) will re-read persisted turns and re-emit missing tool frames.

**Empty Roster Picker**
The live store has not been seeded with the persisted roster. Ensure `backfillMain()` succeeded (it calls `store.describeAgent()` for each persisted agent at lines 329-342). If the roster remains empty, inspect the output of `readColdRoster()` to verify the underlying data exists.

**Gap Reported on `apply()`**
An `append` operation could not be placed because the target turn was already closed. Inspect the `gap` object returned from `apply()` (see `AgentTranscript.apply` at lines 61-68); the gap target indicates which specific turn was missing from the sequence.

## Practical Code Examples

### Retrieve the Last Five Turns from a Live Session

```typescript
const store = transcriptService.forSessionLive(sessionId);
if (!store) throw new Error('Session not live');

const transcript = store.ensureAgent(agentId);
const recent = transcript.snapshot({ tailTurns: 5 });
console.log('Last 5 turns:', recent.items.filter(i => i.kind === 'turn'));

```

### Detect Sequence Number Gaps in Real-Time

```typescript
let expected = 1;
transcriptService.onSessionOps(sessionId, (event, seq) => {
  if (seq !== expected) {
    console.warn(`Gap detected: expected ${expected}, got ${seq}`);
  }
  expected = seq + 1;
});

```

### Compare Live State Against Cold Rebuild

```typescript
const live = transcriptService.forSessionLive(sessionId)!.ensureAgent(agentId);
const liveSnap = live.snapshot();

const coldSnap = await transcriptService.readColdSnapshot(sessionId, agentId);
if (coldSnap) {
  console.log('Live vs cold turn counts:',
    liveSnap.items.filter(i => i.kind === 'turn').length,
    'vs',
    coldSnap.items.filter(i => i.kind === 'turn').length);
}

```

### Trigger Manual Post-Turn Healing

```typescript
// Assuming turn ordinal 42 just completed
await (transcriptService as any).healEndedTurns(sessionId, agentId, new Set([42]));
console.log('Heal complete – missing frames should be recovered');

```

### Retrieve Cold Session Roster

```typescript
const roster = await transcriptService.readColdRoster(sessionId);
if (roster) {
  roster.forEach(d => console.log(`${d.agentId} – ${d.type ?? 'sub'}`));
}

```

## Summary

- The transcript system in MoonshotAI/kimi-code provides immutable state tracking through `TranscriptStore`, `AgentTranscript`, and `TranscriptService`, with clear separation between live in-memory state and cold persisted storage.
- Use `forSessionLive()` to distinguish between active and cold sessions, falling back to `readColdSnapshot()` from [`packages/kap-server/src/snapshot/snapshotReader.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/kap-server/src/snapshot/snapshotReader.ts) when the journal is truncated or the session is not resident.
- Monitor operation integrity via `getOpsSince()` and `onSessionOps()` to detect sequence gaps, and inspect the `gap` property returned by `AgentTranscript.apply()` to identify missing append targets.
- Apply the `healEndedTurns()` mechanism to recover tool frames and turns that may have been missed during live projections, particularly when projectors fail to attach mid-turn results.
- Validate agent roster consistency using `readColdRoster()` and ensure `backfillMain()` completes successfully when UI components fail to display expected agents.

## Frequently Asked Questions

### What does the `apply()` method return when there's a gap in operations?

The `apply()` method in `AgentTranscript` returns an object containing `accepted` and `gap` properties. When a gap exists—typically because an append operation targets a turn that has already closed—the `gap` field contains metadata identifying the missing sequence target, allowing you to pinpoint exactly where the operation stream broke down.

### How does the system handle missing tool results that occur during mid-turn attachments?

When projectors miss frames during active turn processing, the system persists these results to disk only, omitting them from the live transcript. To recover them, invoke `healEndedTurns()` after the turn completes; the `healTurnOps` logic (lines 731-791) reads the persisted `wire.jsonl` and re-emits the missing tool frames into the live transcript store.

### Why would the op journal return incomplete when calling `getOpsSince()`?

The journal maintains a fixed capacity of 2000 operations defined by `TRANSCRIPT_OPS_JOURNAL_CAPACITY`. If the requested range exceeds this buffer, `getOpsSince()` returns `complete: false`, indicating that older batches have been evicted and you must request a full cold snapshot via `readColdSnapshot()` rather than relying on incremental catch-up.

### What causes duplicate turns to appear in the transcript?

Duplicates typically result from race conditions where a back-fill operation executes after a live `turn.upsert` has already populated the store. The transcript service resolves these conflicts through `healTurnOps` (lines 992-1040), which ensures that live state takes precedence on overlapping fields while safely merging non-conflicting persisted data.