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_directorySaving 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
--continue/-c: Resume most recent session--resume <id>: Resume specific session (supports partial ID matching)
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:
compact(): Save before compactionclear_history(): Save before clearing
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 |