How the Checkpointed Model System Enables Conversation Undo in Kimi-Code
The checkpointed model system supports conversation undo by storing operations to a write-ahead log, periodically creating immutable snapshots (checkpoints), and applying idempotent items.remove operations to logically delete targeted transcript items without rewinding state.
Kimi-Code implements a durable, restart-safe undo mechanism through its checkpointed model architecture. Every user interaction, AI response, and state mutation is captured as a granular operation (op) in a transcript stream. This design enables precise rewinding of conversation history through logical deletion rather than physical state reversal, as implemented in the MoonshotAI/kimi-code repository.
The Checkpointed Model Architecture
The transcript engine separates durability concerns into two layers:
- Checkpoints: Immutable snapshots of the entire transcript at designated points in time
- Write-Ahead Log (WAL): All operations occurring after the most recent checkpoint, stored sequentially
This separation, visible in packages/minidb/src/generation.ts, allows the system to reconstruct any historical state by loading a checkpoint and replaying WAL entries up to the desired point.
How Operations Represent State Changes
Every mutation to the conversation state is encoded as a TranscriptOperation. These ops are intentionally small and declarative, describing what changed rather than how to change it.
The core undo mechanism relies on the ItemsRemoveOp, defined in packages/transcript/src/ops/operation.ts:
export interface ItemsRemoveOp {
readonly op: 'items.remove';
readonly ids: readonly string[];
}
This operation is idempotent — applying it multiple times produces the same result as applying it once. The transcript store in packages/transcript/src/store/apply.ts handles this op by removing referenced items from the materialized state:
function applyOp(state, op) {
if (op.op === 'items.remove') {
for (const id of op.ids) {
delete state.items[id];
}
}
// …handle other op kinds
}
The Undo Flow: From API to State Mutation
When a user requests to undo, the system performs two distinct phases: identification and emission.
Phase 1: Identify Target Items
The session service queries the transcript to determine which items must disappear — turns, steps, frames, markers, or nested structures. From packages/kap-server/src/session/undo.ts:
function makeUndoOps(session, count) {
const idsToRemove = session.transcript.getLastIds(count);
return {
op: 'items.remove',
ids: idsToRemove,
} as ItemsRemoveOp;
}
Phase 2: Emit the Removal Operation
The generated ItemsRemoveOp is appended to the current TranscriptOpBatch and applied through AgentTranscript.apply, the single convergence point for all state changes in the system.
REST API for Conversation Undo
The protocol layer in packages/protocol/src/rest/session.ts exposes this functionality through a typed endpoint:
export const undoSessionRequestSchema = z.preprocess(
// default: undo one prompt if body is missing
);
export type UndoSessionRequest = z.infer<typeof undoSessionRequestSchema>;
export const undoSessionResponseSchema = z.object({
// … details of the new transcript window
});
Client-Side Usage Examples
Undo a single last prompt (no body required):
import { fetch } from '@moonshot-ai/klient';
await fetch('/v1/sessions/abc123:undo', {
method: 'POST',
// no body → defaults to `count: 1`
});
Undo multiple prompts explicitly:
await fetch('/v1/sessions/abc123:undo', {
method: 'POST',
body: JSON.stringify({ count: 2 })
});
Two Strategies for State Reconstruction
The checkpointed model enables undo through two equivalent paths:
| Strategy | Mechanism | Use Case |
|---|---|---|
| WAL truncation | Replay only WAL entries up to checkpoint, discard remainder | Bulk rollback, compaction scenarios |
| Logical removal | Append items.remove op to active batch |
Interactive undo, audit trail preservation |
Both strategies yield consistent earlier views. The logical removal approach is preferred for user-facing undo because it preserves complete operation history — critical for debugging, compliance, and collaborative features.
Persistence Across Restarts
The checkpointed model ensures undo works even after server restart:
- On startup, load the most recent checkpoint from durable storage
- Replay all WAL entries to reconstruct current state
- New
items.removeoperations continue appending to the WAL - Next checkpoint creation includes the undo operations themselves
This design eliminates the need for complex "undo stacks" or in-memory history buffers that would be vulnerable to process termination.
Key Source Files
| File | Responsibility |
|---|---|
packages/transcript/src/ops/operation.ts |
Defines ItemsRemoveOp and all transcript operation types |
packages/protocol/src/rest/session.ts |
REST schemas for POST /v1/sessions/{id}:undo |
packages/kap-server/src/session/undo.ts |
Translates undo requests into ItemsRemoveOp instances |
packages/minidb/src/generation.ts |
Checkpoint generation and WAL management |
packages/transcript/src/store/apply.ts |
Centralized operation application logic |
Summary
- The checkpointed model combines immutable snapshots with sequential WAL entries for durable conversation state
- Undo is implemented as an
items.removeoperation that logically deletes targeted items by ID - This approach is idempotent, auditable, and restart-safe — no in-memory state required
- The
AgentTranscript.applymethod serves as the single convergence point for all state mutations - Client access follows standard REST patterns through the
/v1/sessions/{id}:undoendpoint
Frequently Asked Questions
How does Kimi-Code handle undo after a server restart?
The system reconstructs state by loading the most recent checkpoint and replaying all subsequent WAL entries. Because items.remove operations are themselves stored in the WAL, undo history persists across restarts without special handling.
What makes the items.remove operation idempotent?
The operation contains an explicit list of IDs to delete. Applying it once or multiple times produces identical state — the second attempt finds no matching IDs and silently completes. This property simplifies error recovery and network retry logic.
Why use logical removal instead of WAL truncation for interactive undo?
Logical removal preserves complete operation history, enabling forensic analysis and collaborative conflict resolution. WAL truncation is reserved for administrative compaction operations where history loss is acceptable.
Can users undo an undo operation?
Yes. Since items.remove is itself a transcript operation, it can be targeted by subsequent undo requests. The system treats undo operations identically to user-generated content — they exist in the observable history and can be reversed through the same mechanism.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →