WebSocket Transcript Protocol Reconnection and Sync in Kimi Code

The Kimi Code WebSocket transcript protocol implements an exponential backoff reconnection strategy with cursor-based replay, allowing clients to resume real-time operation streams from the last known batch watermark or trigger a full REST resync when the server-side journal expires.

The MoonshotAI/kimi-code repository delivers real-time transcript streams over the /api/v1/ws WebSocket endpoint using a dedicated control channel that guarantees durability across network interruptions. This system separates volatile transcript operations from durable session events, ensuring clients can automatically recover from dropped connections without losing critical state. Understanding how this WebSocket transcript protocol handles reconnection and synchronization reveals a robust architecture designed for production-grade real-time collaboration.

Client-Side Reconnection Flow with Exponential Backoff

The client implementation in apps/kimi-inspect/src/transcript/ws.ts manages connection lifecycle through a stateful wrapper that detects failures and orchestrates recovery.

Detecting Connection Drops and Scheduling Reconnect

When the underlying socket closes, the TranscriptWs class clears its internal this.ws reference and invokes scheduleReconnect() unless the closure was intentional. The reconnection strategy uses exponential backoff starting at 500 ms and doubling with each attempt, capping at 10_000 ms (10 seconds). This calculation appears in the source as:

delay = Math.min(this.reconnectDelayMs * 2 ** (this.reconnectAttempt - 1), 10_000)

Once the delay elapses, the connect() method instantiates a fresh WebSocket using new this.WsCtor(this.wsUrl, protocols) and reattaches all event listeners, creating a completely new transport while preserving the client's session context.

Handshake and Subscription Resumption

Upon establishing a new socket, the client immediately transmits a client_hello control frame containing the client_id and desired subscriptions. This is followed by a critical subscribe_v2 frame that carries two key pieces of state:

  • Transcript grade map: Specifies granularity levels (e.g., {agentId: 'block'} or 'delta') for each agent
  • Cursor watermark: Optional transcript_since parameter reflecting the latest operation batch sequence known to the client

The client then awaits an ack control frame matching the subscribe_v2 message ID. The source code checks this condition with if (!this.subscribeV2Acked && frame.id === this.subscribeV2Id), firing onReconnected() only after server confirmation. This ensures the transcript stream is fully attached before the client processes new operations.

Server-Side Replay and Resync Logic

The server implementation in packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts handles reconnection through serialized control processing and optional operation replay.

Control Frame Serialization and Session Attachment

All incoming control frames enter a controlQueue to guarantee ordering during high-concurrency scenarios. When processing client_hello, the server validates tokens and records the handshake via onClientHello() before forwarding to attachSession().

The subscribe_v2 handler parses the payload using transcriptSubscribeV2PayloadSchema, extracting the session_id, transcript grades, and optional transcript_since cursor. The attachSession() method then registers the connection with the SessionEventBroadcaster, passing the cursor to the replay subsystem if present.

Operation Replay and Fallback Mechanisms

The replay() method attempts to satisfy the reconnection request by calling broadcaster.getBufferedSince() to retrieve missed operations from the in-memory journal. Two outcomes are possible:

  • Successful replay: If the requested cursor range exists in the journal, the server streams the buffered operations followed by a transcript seed (baseline) via flushTranscriptSeed(), then sends an ack with status 0
  • Journal gap: If the cursor predates the journal's retention window, the server emits a resync_required control frame containing the session_id, forcing the client to perform a full REST transcript fetch

This fallback mechanism ensures clients never receive stale or partial state when the ephemeral operation buffer cannot satisfy the request.

Synchronization Guarantees and Durability

The protocol provides distinct durability semantics for different event types:

  • Durable ordering: Non-volatile events (session events, agent events) travel through the standard subscribe path with guaranteed delivery of every envelope in order
  • Volatile transcript ops: Ephemeral operations reside only in an in-memory journal with limited TRANSCRIPT_OPS_JOURNAL_CAPACITY. Reconnection replay succeeds only while the cursor remains within this window
  • Grade preservation: The subscribe_v2 channel exclusively carries transcript grade specifications, ensuring granularity preferences persist across re-attachments without accidental fallback to default settings
  • Idempotent unsubscription: The unsubscribe_v2 frame removes specific transcript streams without affecting legacy event subscriptions, allowing granular control over data flow

Implementation Example

The following client-side instantiation demonstrates automatic reconnection with cursor tracking:

import { TranscriptWs } from './transcript/ws';

const ws = new TranscriptWs({
  url: 'http://localhost:58627',
  token: 'YOUR_BEARER_TOKEN',
  sessionId: 's123',
  agentId: 'main',
  getSince: () => transcriptStore.latestSeq(),
  handlers: {
    onOps: (agent, ops, meta) => store.appendOps(agent, ops, meta),
    onResyncRequired: () => fetchFullTranscript(),
    onReconnected: () => console.log('Transcript stream re‑attached'),
  },
});

On the server, the subscription handling logic manages replay and acknowledgment:

private async onSubscribeV2(frame: InboundFrame): Promise<void> {
  const parsed = transcriptSubscribeV2PayloadSchema.safeParse(frame.payload ?? {});
  if (!parsed.success) {
    this.sendFrame(buildAck(frame.id ?? '', 1, 'invalid subscribe_v2 payload', {}));
    return;
  }
  const sid = parsed.data.session_id;

  await this.attachSession(
    sid,
    undefined,
    this.subscriptions.get(sid)?.agentFilter,
    parsed.data.transcript,
    parsed.data.transcript_since,
    { accepted: [], resyncRequired: [], serverCursors: {} },
  );

  this.sendFrame(buildAck(frame.id ?? '', 0, 'success', { 
    accepted: [], 
    not_found: [], 
    resync_required: [], 
    cursors: {} 
  }));
}

Summary

  • Exponential backoff reconnection: Client-side logic implements a capped exponential backoff starting at 500ms to prevent thundering herd scenarios while minimizing downtime
  • Cursor-based resumption: The transcript_since parameter allows clients to request replay of specific operation batches from the server's in-memory journal
  • Graceful degradation: When the journal cannot satisfy a cursor request, the resync_required frame triggers a full REST fetch, ensuring consistency at the cost of bandwidth
  • Control channel isolation: The subscribe_v2 protocol separates transcript grades from durable event subscriptions, maintaining granularity preferences across reconnections
  • Serialized processing: Server-side control frame queuing guarantees ordered handling of client_hello and subscribe_v2 messages during concurrent reconnections

Frequently Asked Questions

What happens if the client's transcript_since cursor is too old?

If the requested cursor predates the server's TRANSCRIPT_OPS_JOURNAL_CAPACITY window, the replay() method in wsConnectionV1.ts cannot retrieve the missing operations. The server emits a resync_required control frame, and the client's onResyncRequired handler must perform a full REST transcript fetch to obtain a complete baseline before continuing.

How does the protocol prevent duplicate operations during reconnection?

The server streams buffered operations only once during the attachSession() replay phase, followed immediately by an ack frame. The client ignores any operations until this acknowledgment arrives, ensuring the replay window closes atomically before new real-time operations flow. The cursor-based sequencing ensures each operation batch carries a unique watermark for idempotent client-side processing.

What is the difference between subscribe and subscribe_v2?

The legacy subscribe path handles durable session and agent events with guaranteed ordering, while subscribe_v2 exclusively manages transcript grade specifications and volatile operation streams. The v2 channel supports granular control over operation granularity (block vs. delta levels) and carries the transcript_since cursor necessary for replay resumption.

How does the client know when reconnection is complete?

The client tracks the subscribeV2Id sent in the subscribe_v2 frame and awaits a matching ack response from the server. The condition frame.id === this.subscribeV2Id in the message handler triggers onReconnected(), signaling that the transcript stream is attached and the connection has fully recovered from the interruption.

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 →