How to Debug Kimi-CLI Session Issues Using the Wire File and Session State Files
Kimi-CLI persists every interaction in state.json (high-level metadata) and wire.jsonl (message log), allowing you to inspect session health, trace missing tool calls, and replay conversations for deterministic debugging.
When a Kimi-CLI session behaves unexpectedly—whether it is missing a tool call, losing context, or getting stuck in a loop—the root cause often lies in how session data is persisted. According to the MoonshotAI/kimi-cli source code, the CLI stores session metadata in state.json and maintains a complete message audit trail in wire.jsonl, giving you two complementary entry points to diagnose issues.
Understanding Session Persistence Architecture
Kimi-CLI uses a dual-file persistence model that separates high-level session state from low-level message traffic.
The Session State File (state.json)
Located at <session-dir>/state.json, this file stores a JSON-encoded SessionState model containing high-level metadata such as approval flags, custom titles, todo lists, and archive status. As defined in src/kimi_cli/session_state.py (lines 12-44), this state is managed by the SessionState class and loaded via the load_session_state() function.
The Wire Log (wire.jsonl)
The wire.jsonl file (also in the session directory) acts as a line-delimited JSON log of every WireMessage sent or received. As implemented in src/kimi_cli/wire/file.py (lines 18-26), the first line contains a WireFileMetadata header with the wire-protocol version, followed by WireMessageRecord entries containing timestamps and message payloads.
Step-by-Step Debugging Workflow
To diagnose session issues, inspect both files using the following workflow.
1. Load and Inspect Session State
Call load_session_state(session_dir) to read state.json into a SessionState object. This reveals whether the session thinks it is archived, what todo items are pending, and whether any legacy metadata.json was migrated. According to src/kimi_cli/session_state.py (lines 99-127), this function handles path resolution and migration logic automatically.
2. Read and Analyze the Wire Log
Initialize WireFile(session_dir / "wire.jsonl") to iterate over stored records. Each WireMessageRecord contains the original message (tool calls, UI events, hook requests) plus a monotonic timestamp (src/kimi_cli/wire/file.py, lines 59-66). You can filter for specific event types like ToolCallPart or HookTriggered to trace when specific actions occurred.
3. Correlate Timestamps and Events
Because the wire file stores a monotonic timestamp for each record, you can line up events with high-level state changes captured in state.json. For example, if "plan mode" is missing in the state, check for a HookTriggered event that never arrived in the wire log, indicating a message delivery failure.
4. Detect Corrupted Files
Both persistence layers include error resilience. load_session_state falls back to defaults and logs a warning if state.json is malformed (lines 104-108 in session_state.py). Similarly, WireFile.iter_records() skips unparsable lines while logging failures, allowing you to spot truncated or malformed wire entries (src/kimi_cli/wire/file.py, lines 95-112).
5. Replay Sessions for Reproduction
The KimiSoul runtime can reconstruct sessions from the wire log for deterministic debugging. During replay, the server reads each WireMessage from the file and re-executes the same tool calls (src/kimi_cli/wire/server.py, lines 815-848). This is invaluable for reproducing race conditions or timing-dependent bugs.
Practical Code Examples
Run the following snippets in a Python REPL to inspect a session located at ~/.kimi/sessions/<hash>.
from pathlib import Path
from kimi_cli.session_state import load_session_state
from kimi_cli.wire.file import WireFile
# ----------------------------------------------------------------------
# 1️⃣ Load the high‑level session state
# ----------------------------------------------------------------------
session_dir = Path.home() / ".kimi" / "sessions" / "<hash>"
state = load_session_state(session_dir)
print("Session title:", state.custom_title or "(none)")
print("Todo items:", [(t.title, t.status) for t in state.todos])
print("Archived:", state.archived, "at", state.archived_at)
print("Wire protocol version:", state.wire_mtime)
# ----------------------------------------------------------------------
# 2️⃣ Iterate over the wire log
# ----------------------------------------------------------------------
wire = WireFile(session_dir / "wire.jsonl")
print("\nWire file header protocol version:", wire.version)
# Print the first few messages with timestamps
import itertools
for record in itertools.islice(wire.iter_records(), 10):
msg = record.to_wire_message()
print(f"{record.timestamp:.3f}s – {type(msg).__name__}: {msg}")
# ----------------------------------------------------------------------
# 3️⃣ Find a specific type of event (e.g., a tool call)
# ----------------------------------------------------------------------
from kimi_cli.wire.types import is_wire_message, ToolCallPart
matches = []
for rec in wire.iter_records():
msg = rec.to_wire_message()
# ToolCallPart is a payload for a tool request; adjust as needed
if hasattr(msg, "tool_call"):
matches.append((rec.timestamp, msg.tool_call))
if len(matches) >= 5:
break
print("\nFirst few tool calls:")
for ts, tc in matches:
print(f"{ts:.3f}s → {tc}")
# ----------------------------------------------------------------------
# 4️⃣ Detect malformed lines (helpful when the wire file is corrupted)
# ----------------------------------------------------------------------
for rec in wire.iter_records():
# The iterator skips bad lines, but we can count them via logging
pass
print("\nIf you saw warnings in the console, those lines were unparsable.")
Summary
- Session state is stored in
state.jsonand managed by theSessionStateclass insrc/kimi_cli/session_state.py, tracking metadata like todos and archive status. - Message history is logged to
wire.jsonlvia theWireFileclass insrc/kimi_cli/wire/file.py, with each record containing a timestamp and payload. - Debugging workflow involves loading the state, iterating over wire records with
iter_records(), correlating timestamps, and checking for corruption warnings. - Session replay is supported by
KimiSoulinsrc/kimi_cli/wire/server.py, enabling deterministic reproduction of issues from the wire log.
Frequently Asked Questions
Where does Kimi-CLI store session files?
Session files are stored in ~/.kimi/sessions/<hash>/, where <hash> is a unique session identifier. Each session directory contains both state.json (metadata) and wire.jsonl (message log).
What does the wire protocol version indicate?
The wire protocol version is stored in the WireFileMetadata header (first line of wire.jsonl) and indicates the schema version of the message format. The SessionState object tracks the last modification time of the wire file in its wire_mtime field to detect external changes.
How do I recover from a corrupted state.json file?
The load_session_state() function automatically falls back to default values and logs a warning if state.json is malformed or missing. You can manually delete the file to force regeneration, though you will lose custom titles and todo items. Archived sessions can be restored by toggling the archived flag back to false in the regenerated state.
Can I manually edit wire.jsonl to fix session issues?
While technically possible, manual editing is not recommended because the file uses a strict line-delimited JSON format with monotonic timestamps. Corrupted entries will be skipped by WireFile.iter_records(), but may cause the replay functionality in src/kimi_cli/wire/server.py to fail. Use the replay feature instead to recreate sessions programmatically.
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 →