CodeDocs Vault

Session Management

The session system provides persistent conversation storage with incremental saving, session resume, and format migration.

Overview

Directory: vibe/core/session/

vibe/core/session/
├── session_logger.py     # SessionLogger - creation, saving
├── session_loader.py     # SessionLoader - finding, loading
└── session_migration.py  # v1→v2 format migration

Session Directory Structure

Each session is stored as a directory:

~/.vibe/logs/session/
├── session_20240115_143022_a1b2c3d4/
│   ├── meta.json        # Session metadata
│   └── messages.jsonl   # Messages (one JSON object per line)
├── session_20240115_150511_e5f6g7h8/
│   ├── meta.json
│   └── messages.jsonl
└── ...

Directory name format: {prefix}_{YYYYMMDD_HHMMSS}_{session_id[:8]}

SessionLogger

File: vibe/core/session/session_logger.py:26

Constructor

class SessionLogger:
    def __init__(self, session_config: SessionLoggingConfig, session_id: str) -> None:

Initialization

SessionLogger.__init__() [session_logger.py:27]
    │
    ├── If disabled:
    │   Set all paths to None, return
    │
    ├─1─► Set save_dir from config [session_logger.py:40]
    │
    ├─2─► Store session_id and start time [session_logger.py:42-43]
    │
    ├─3─► Create save directory [session_logger.py:45]
    │     save_dir.mkdir(parents=True, exist_ok=True)
    │
    ├─4─► Compute session directory path [session_logger.py:46]
    │     save_folder property → {prefix}_{timestamp}_{short_id}
    │
    └─5─► Initialize metadata [session_logger.py:47]
          _initialize_session_metadata()

SessionMetadata

File: vibe/core/types.py

class SessionMetadata(BaseModel):
    session_id: str
    start_time: str           # ISO format
    end_time: str | None      # Updated on each save
    git_commit: str | None    # HEAD commit hash
    git_branch: str | None    # Current branch name
    username: str             # From getpass.getuser()
    environment: dict         # Contains working_directory

Saving Interactions

Method: save_interaction() at session_logger.py:197

save_interaction(messages, stats, base_config, tool_manager, agent_profile)
    │
    ├─1─► Guard: skip if disabled or no session_dir [session_logger.py:205-209]
    │
    ├─2─► Create session directory [session_logger.py:212-217]
    │     session_dir.mkdir(parents=True, exist_ok=True)
    │
    ├─3─► Read old metadata for message count [session_logger.py:220-232]
    │     old_total_messages = old_metadata["total_messages"]
    │
    ├─4─► Compute new messages (incremental) [session_logger.py:235-237]
    │     non_system = [m for m in messages if m.role != Role.system]
    │     new_messages = non_system[old_total_messages:]
    │
    ├─5─► Append new messages to messages.jsonl [session_logger.py:243]
    │     persist_messages(messages_data, session_dir)
    │
    ├─6─► Build metadata dump [session_logger.py:246-279]
    │     Includes: session_metadata, stats, title, total_messages,
    │     tools_available, config, agent_profile, system_prompt
    │
    ├─7─► Persist metadata atomically [session_logger.py:281]
    │     persist_metadata(metadata_dump, session_dir)
    │
    └─8─► Cleanup old temp files [session_logger.py:287]
          cleanup_tmp_files()

Atomic File Writes

Method: persist_metadata() at session_logger.py:148

persist_metadata(metadata, session_dir) [session_logger.py:148-176]
    │
    ├─1─► Create temp file in session_dir [session_logger.py:153-158]
    │     NamedTemporaryFile(suffix=".json.tmp", dir=session_dir)
    │
    ├─2─► Write JSON and fsync [session_logger.py:161-163]
    │
    ├─3─► Atomic replace [session_logger.py:165]
    │     os.replace(temp_file, metadata_filepath)
    │
    └─4─► Cleanup temp file on failure [session_logger.py:170-176]

Incremental Message Appending

Method: persist_messages() at session_logger.py:178

@staticmethod
async def persist_messages(messages: list[dict], session_dir: Path) -> None:
    messages_filepath = session_dir / "messages.jsonl"
    async with await AsyncPath(messages_filepath).open("a", encoding="utf-8") as f:
        for message in messages:
            await f.write(json.dumps(message, ensure_ascii=False) + "\n")
            await f.flush()
            os.fsync(f.wrapped.fileno())

Messages are appended (not rewritten), and each line is fsynced individually for durability.

Session Reset

Method: reset_session() at session_logger.py:289

Called after history clearing or compaction:

def reset_session(self, session_id: str) -> None:
    self.session_id = session_id
    self.session_start_time = datetime.now().isoformat()
    self.session_dir = self.save_folder   # New directory
    self.session_metadata = self._initialize_session_metadata()

SessionLoader

File: vibe/core/session/session_loader.py:14

All methods are @staticmethod.

Finding Sessions

Method Purpose
find_latest_session(config) Find most recent valid session
find_session_by_id(id, config) Find session by ID (supports partial 8-char match)
does_session_exist(id, config) Check if session exists (less strict validation)

Session Validation

Method: _is_valid_session() at session_loader.py:15

_is_valid_session(session_dir) [session_loader.py:16-42]
    │
    ├─1─► Check meta.json and messages.jsonl exist [session_loader.py:21]
    │
    ├─2─► Validate metadata is valid JSON dict [session_loader.py:25-28]
    │
    └─3─► Validate messages: each line is valid JSON dict [session_loader.py:30-38]
          At least one message required

Loading Sessions

Method: load_session() at session_loader.py:110

load_session(filepath) [session_loader.py:110-157]
    │
    ├─1─► Read messages.jsonl [session_loader.py:112-126]
    │     Parse each line as JSON
    │
    ├─2─► Convert to LLMMessage objects [session_loader.py:136-138]
    │     Filter out system messages
    │
    ├─3─► Read meta.json [session_loader.py:141-155]
    │
    └─4─► Return (messages, metadata) tuple

Latest Session Selection

Method: latest_session() at session_loader.py:44

latest_session(session_dirs) [session_loader.py:45-66]
    │
    ├─1─► Get mtime of messages.jsonl for each session
    │
    ├─2─► Sort by mtime (newest first)
    │
    └─3─► Return first session that passes validation

Session Migration

File: vibe/core/session/session_migration.py

Migrates v1.x single-JSON format to v2.0 directory format.

Migration Flow

migrate_sessions_entrypoint(session_config) [session_migration.py:11]
    │
    └─► asyncio.run(migrate_sessions(session_config))
        │
        ├─1─► Find all {prefix}_*.json files in save_dir [session_migration.py:22]
        │
        └─2─► For each file:
              │
              ├─► Parse JSON → extract metadata + messages [session_migration.py:26-29]
              │
              ├─► Create session directory [session_migration.py:31-32]
              │   session_dir = save_dir / session_file.stem
              │
              ├─► Persist metadata and messages [session_migration.py:34-35]
              │   SessionLogger.persist_metadata(metadata, session_dir)
              │   SessionLogger.persist_messages(messages, session_dir)
              │
              └─► Delete original JSON file [session_migration.py:36]

Migration runs in a background daemon thread, spawned during AgentLoop.__init__().

Session Resume Flow

CLI Arguments

Resume in CLI

run_cli(args) [cli.py]
    │
    ├── If --continue:
    │   session_dir = SessionLoader.find_latest_session(config.session_logging)
    │
    ├── If --resume <id>:
    │   session_dir = SessionLoader.find_session_by_id(id, config.session_logging)
    │
    └── If session found:
        messages, metadata = SessionLoader.load_session(session_dir)
        → passed to run_textual_ui(loaded_messages=messages)

Resume in Agent

_initialize_agent() [app.py:406]
    │
    └── If loaded_messages:
        Filter out system messages
        agent.messages.extend(loaded_messages)

Integration with AgentLoop

The session logger is created during AgentLoop.__init__() and called after each conversation turn:

AgentLoop._conversation_loop()
    │
    └── After each LLM turn:
        session_logger.save_interaction(
            messages, stats, base_config,
            tool_manager, agent_profile
        )

Also called during:

Source File References

File Key Lines Description
session_logger.py:22-23 Constants METADATA_FILENAME, MESSAGES_FILENAME
session_logger.py:26-47 SessionLogger.__init__() Constructor
session_logger.py:49-58 save_folder Session directory path computation
session_logger.py:115-128 _initialize_session_metadata() Metadata creation
session_logger.py:148-176 persist_metadata() Atomic metadata write
session_logger.py:178-195 persist_messages() Incremental message append
session_logger.py:197-287 save_interaction() Main save method
session_logger.py:289-297 reset_session() Session reset
session_loader.py:14-157 SessionLoader Finding and loading sessions
session_loader.py:15-42 _is_valid_session() Session validation
session_loader.py:68-77 find_latest_session() Find most recent session
session_loader.py:79-85 find_session_by_id() Find by ID (partial match)
session_loader.py:110-157 load_session() Load messages and metadata
session_migration.py:11-41 migrate_sessions() v1→v2 migration