Session Management Architecture in Kimi CLI: How `src/kimi_cli/session.py` Orchestrates Conversation State

The session management architecture in src/kimi_cli/session.py relies on a Session dataclass that coordinates immutable identifiers, mutable SessionState, JSONL wire logs, and async filesystem helpers within a per-work-directory .kimi/sessions/ hierarchy.

In the MoonshotAI/kimi-cli repository, the session management architecture in src/kimi_cli/session.py centralizes all conversation state logic. This module defines how the CLI tracks multi-turn history, persists metadata, and manages context across different working directories. Understanding these internals is essential for anyone extending the CLI or debugging synchronization between the terminal UI and the web API.

Core Components of the Session Architecture

The Session Dataclass

The foundation of the architecture is the Session dataclass defined at lines 22-45. It stores immutable metadata—including the session ID, work directory, and file paths—alongside a mutable SessionState object that holds runtime settings.

Helper properties extend this core definition. The dir property (lines 48-54) returns the session folder path and lazily creates it on demand. The subagents_dir property (lines 55-60) provides a dedicated sub-folder for persisted sub-agents.

Filesystem Layout and Session Directories

All session data lives under the canonical path <work_dir>/.kimi/sessions/<session_id>/. This per-directory isolation lets the CLI maintain separate conversation histories for distinct projects.

The Session object exposes two key directory properties:

  • dir – creates the session folder if missing (lines 48-54).
  • subagents_dir – creates a nested directory for sub-agent storage (lines 55-60).

Persistent State and Wire Logging

Runtime mutability is handled by SessionState, which stores user-adjustable flags such as custom titles, archive flags, and plan mode. The save_state() method (lines 84-98) persists this state to disk via kimi_cli.session_state.save_session_state.

Every session also owns a WireFile field (lines 35-36). This line-oriented JSONL log records every wire event—such as TurnBegin and tool calls—providing an append-only event stream that the UI and title-refresh logic replay.

Context Files and Empty Session Detection

Message history is stored in a simple context.jsonl file containing objects with role and content fields. The is_empty() method (lines 62-82) inspects this file to determine whether it contains any non-internal messages, which lets the CLI filter out idle or abandoned sessions during listing operations.

Session Lifecycle and Interaction Flow

Creating a Session with Session.create()

According to the source code, Session.create() (lines 129-181) handles the full bootstrap sequence. It canonicalizes the KaosPath, ensures the work-directory metadata exists, generates a UUID, creates the session folder, initializes an empty context.jsonl, seeds the wire log, writes metadata, and finally calls refresh() to derive the initial title.

from pathlib import Path
from kaos.path import KaosPath
from kimi_cli.session import Session

async def new_session_example():
    work_dir = KaosPath.unsafe_from_local_path(Path("/my/project"))
    session = await Session.create(work_dir)
    print(f"New session ID: {session.id}")
    print(f"Session title: {session.title}")

Loading and Refreshing Sessions with Session.find()

Session.find() (lines 184-224) is the inverse operation. It validates the work directory, invokes _migrate_session_context_file (lines 309-320) to move legacy <id>.jsonl files into the modern folder layout, verifies the session folder and context file, loads the persisted SessionState, and calls refresh() (lines 106-123).

The refresh() method walks the WireFile until it encounters the first TurnBegin event, extracting a short user prompt to use as the session title unless a custom title is already set.

Listing Active Sessions and Continuing Work

Session.list() (lines 226-275) enumerates every sub-folder—and any legacy .jsonl file—under the work directory’s sessions_dir. It filters out empty contexts, loads each Session, refreshes titles, and sorts results by updated_at with the most recent first.

from pathlib import Path
from kaos.path import KaosPath
from kimi_cli.session import Session

async def list_sessions_example():
    work_dir = KaosPath.unsafe_from_local_path(Path("/my/project"))
    sessions = await Session.list(work_dir)
    for s in sessions:
        print(f"{s.id[:8]} – {s.title} (updated {s.updated_at})")

For quick resumption, Session.continue_() (lines 288-306) reads the work-directory metadata’s last_session_id and forwards it to Session.find().

from pathlib import Path
from kaos.path import KaosPath
from kimi_cli.session import Session

async def continue_last():
    work_dir = KaosPath.unsafe_from_local_path(Path("/my/project"))
    session = await Session.continue_(work_dir)
    if session:
        print(f"Resuming session {session.id} titled '{session.title}'")
    else:
        print("No prior session found.")

Saving State and Deleting Sessions

Concurrency safety is built into save_state() (lines 84-98). Before writing, the method reloads the on-disk state to avoid overwriting changes from concurrent web API access.

Deletion is handled by an async delete() method that recursively removes the entire session directory.

await session.delete()   # async – removes the whole folder

Integration with Supporting Modules

The session module does not operate in isolation. It imports persistence helpers from adjacent packages:

  • kimi_cli.session_state – defines SessionState and its load/save routines.
  • kimi_cli.metadata – manages global work-directory metadata, including tracking the last_session_id.
  • kimi_cli.wire.file and kimi_cli.wire.types – supply the WireFile wrapper and event types such as TurnBegin.
  • kimi_cli.utils.logging – provides the centralized logger used throughout the session code.

These modules collectively implement a robust, filesystem-backed model that supports concurrent UI access while keeping state consistent and recoverable.

Summary

  • src/kimi_cli/session.py defines a Session dataclass that binds immutable metadata to mutable SessionState and directory helpers.
  • Sessions are physically isolated under <work_dir>/.kimi/sessions/<session_id>/ with a context.jsonl, wire log, and state file.
  • Lifecycle helpers—create, find, list, continue_, and delete—are async class methods that centralize filesystem and metadata handling.
  • save_state() reloads disk state before writing to prevent clobbering concurrent edits.
  • A silent migration utility moves legacy flat files into the current nested directory layout.

Frequently Asked Questions

How does src/kimi_cli/session.py store session data on disk?

Each session occupies a dedicated folder under <work_dir>/.kimi/sessions/<session_id>/. This directory contains context.jsonl for messages, a line-oriented JSONL wire log for events, and a persisted SessionState file for settings such as custom titles and plan mode.

What is the WireFile used for in the session architecture?

WireFile is an append-only JSONL log managed by kimi_cli.wire.file. It records wire-level events like TurnBegin. The Session.refresh() method replays this log to extract the first user prompt and auto-generate a session title when no custom title exists.

How does the session manager prevent concurrent state overwrites?

The save_state() method at lines 84-98 reloads the on-disk SessionState before persisting new values. This merge-first strategy prevents the CLI from overwriting changes made by the web API or other concurrent processes.

What happens to legacy session files when loading a session?

Session.find() automatically invokes _migrate_session_context_file (lines 309-320) to silently move legacy <id>.jsonl files into the modern per-session folder structure. This ensures backward compatibility without manual intervention.

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 →