Skip to content

Field Manual

Session Stores

Session stores hold continuity: prompts, tool records, local state, memory, and resume metadata that can tie a timeline together.

Why it matters

Without session stores, an investigation may see final files but miss the sequence of decisions and tool actions that produced them.

Evidence to look for

  • Session IDs
  • Conversation JSONL or databases
  • Workspace paths
  • Tool-call transcripts
  • Resume markers
  • Local agent memory

Common pitfalls

  • Running tools that rotate or compact sessions
  • Collecting only visible chat export
  • Sharing raw stores that contain secrets

Session store formats by agent

Each agent uses a different storage format and directory structure. Understanding these differences is essential for locating, preserving, and parsing session evidence.

Codex CLI session store

Codex stores sessions as JSONL files organized by date. Each session file contains a session_meta header followed by event_msg records. Codex maintains three session directories: active sessions, archived sessions, and headless sessions.

Artifact Paths

Active sessions

~/.codex/sessions/YYYY/MM/*.jsonl

JSONL files organized by year and month.

Archived sessions

~/.codex/archived_sessions/*.jsonl

Completed sessions moved from active.

Headless sessions

~/.codex/headless/*.jsonl

Non-interactive agent runs.

Codex — JSONL session with session_meta header
JSON
{"type":"session_meta","timestamp":"2026-04-03T06:00:00Z","payload":{"source":"cli","model_provider":"fixture-provider","agent_nickname":"fixture-agent","model":"fixture-model"}}

{"type":"event_msg","timestamp":"2026-04-03T06:00:01Z","payload":{"type":"user_message","message":"Inventory the MCP configuration for this synthetic workspace."}}

{"type":"event_msg","timestamp":"2026-04-03T06:00:02Z","payload":{"type":"tool_call","tool_name":"mcp_inspector","command":"probe mcp servers","arguments":{"server":"filesystem","inventory_method":"tools/list"}}}

Codex sessions always start with a session_meta record. Respects $CODEX_HOME if set.

Claude Code session store

Claude Code stores sessions as JSONL files organized by project. Each project directory contains session files with alternating user and assistant message records.

Artifact Paths

Project sessions

~/.claude/projects/<project-hash>/*.jsonl

One directory per project. Each JSONL file is one session.

Claude — JSONL with user/assistant message records
JSON
{"type":"user","sessionId":"claude-tool-use","timestamp":"2026-04-27T12:10:00Z","message":{"role":"user","content":[{"type":"text","text":"Read the project README and summarize it."}]}}

{"type":"assistant","sessionId":"claude-tool-use","timestamp":"2026-04-27T12:10:01Z","message":{"role":"assistant","model":"claude-fixture-model","content":[{"type":"text","text":"I will read the public README only."},{"type":"tool_use","id":"toolu_fixture_read","name":"Read","input":{"file_path":"README.md"}}]}}

Claude Code sessions use type values of user and assistant at the top level. Tool calls and results are nested inside message.content arrays.

Gemini CLI session store

Gemini CLI stores each session as a single JSON file. Unlike JSONL-based agents, the entire session is one JSON object with a messages array.

Artifact Paths

Session files

~/.gemini/tmp/*.json

Single JSON file per session. Contains the full session in one object.

Gemini — single JSON object with messages array
JSON
{
  "sessionId": "gemini-uc001-tool-result",
  "projectHash": "fixture-project",
  "startTime": "2026-04-27T12:30:00Z",
  "lastUpdated": "2026-04-27T12:30:02Z",
  "model": "gemini-fixture-model",
  "messages": [
    { "type": "user", "content": "Check the repository status." },
    { "type": "tool_call", "name": "repo_status", "input": { "format": "json" } },
    { "type": "tool_result", "tool": "repo_status", "content": "status=ok" }
  ]
}

Gemini sessions are atomic JSON files. Session metadata (sessionId, projectHash, model) lives at the top level alongside the messages array.

OpenCode session store

OpenCode stores sessions in a SQLite database at ~/.local/share/opencode/opencode.db. The database has two tables Telltale reads: part (the current activity stream, with tool and text rows) and message (a legacy compat table with JSON blobs in the data column). Older installations also stored per-message JSON files under storage/message/. The SQLite database is the primary store; the JSON files are a legacy format. When both are present on the same host, Telltale prefers SQLite and drops the legacy JSON source.

Artifact Paths

Current (SQLite — part table)

~/.local/share/opencode/opencode.db

SQLite database, table part. Columns: id, message_id, session_id, time_created, time_updated, data (JSON). Cursor-based incremental ingestion. macOS: ~/Library/Application Support/opencode/opencode.db.

Current (SQLite — message table)

~/.local/share/opencode/opencode.db

SQLite database, table message. Slim schema: id, sessionID, data (JSON). Some live DBs also carry a top-level session_id column. Read in full (order by rowid).

Legacy (per-message JSON)

~/.local/share/opencode/storage/message/<session-id>/*.json

One JSON file per message or a JSON array per session directory. Dropped when SQLite is present on the same host.

OpenCode SQLite — part table schema and a tool row
SQL
CREATE TABLE part (
  id           TEXT PRIMARY KEY,
  message_id   TEXT NOT NULL,
  session_id   TEXT NOT NULL,
  time_created INTEGER NOT NULL,
  time_updated INTEGER NOT NULL,
  data         TEXT NOT NULL  -- JSON: {"type":"tool","tool":"bash","state":{"status":"completed","input":{"command":"cat .env"},"output":"..."}}
);

The part table is the current OpenCode activity stream. The data column holds JSON with type "tool" or "text". Telltale ingests part rows with cursor-based incremental logic: high-water mark on time_updated, bounded to 5,000 rows per scan, with a small overlap window so rows that transition from running to completed are not missed.

OpenCode SQLite — message table (legacy compat)
SQL
CREATE TABLE message (
  id         TEXT,
  sessionID  TEXT,
  data       TEXT  -- JSON blob with role, modelID, providerID, agent, time, type, tool_name, arguments, content
);

The message table is a legacy compat layer inside the SQLite database. Some live DBs also carry a top-level session_id column. Telltale reads it in full and normalizes the data JSON blob.

OpenCode — legacy per-message JSON (older installations)
JSON
[
  {
    "id": "msg-cred-1",
    "sessionID": "opencode-uc002-credential-publish",
    "type": "tool_call",
    "tool_name": "bash",
    "arguments": { "command": "cat ~/.aws/credentials" },
    "modelID": "fixture-model",
    "providerID": "fixture-provider",
    "time": "2026-04-28T10:00:00Z",
    "agent": "build"
  }
]

Legacy format: one JSON file per message under storage/message/. Tool calls use tool_name and arguments. Dropped when SQLite is present on the same host to avoid double-counting.

Roo Code and Kilo Code session store

Roo Code and Kilo Code store sessions as JSON files in VS Code extension globalStorage directories. Each task directory contains a ui_messages.json file with a flat JSON array of all events.

Artifact Paths

Roo Code

~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/tasks/<task-id>/ui_messages.json

VS Code extension storage. macOS: ~/Library/Application Support/Code/. Windows: %APPDATA%\Code\.

Kilo Code

~/.config/Code/User/globalStorage/kilocode.kilo-code/tasks/<task-id>/ui_messages.json

Same format as Roo Code. macOS: ~/Library/Application Support/Code/. Windows: %APPDATA%\Code\.

Roo Code — ui_messages.json flat array
JSON
[
  { "type": "user", "content": "Check the repository status." },
  { "type": "tool_call", "name": "repo_status", "input": { "format": "json" } },
  { "type": "tool_result", "tool": "repo_status", "content": "status=ok" }
]

The ui_messages.json file contains a flat JSON array. The array preserves chronological order. VS Code extension paths follow platform conventions for globalStorage.

Qwen CLI session store

Qwen CLI stores sessions as JSONL files organized by project. Each record carries agent, provider, and model fields at the root level alongside the event data.

Artifact Paths

Project sessions

~/.qwen/projects/**/*.jsonl

JSONL files under project chats directories. Each record carries type, model, timestamp, sessionId, and usageMetadata.

OpenClaw session store

OpenClaw stores sessions as JSONL-like files in an agents directory. The discovery pattern matches any file containing .jsonl in the name, including archived and reset variants.

Artifact Paths

Agent sessions

~/.openclaw/agents/*.jsonl

JSONL files. Also discovers .jsonl.deleted, .jsonl.archived, and .jsonl.reset variants.

GitHub Copilot session store

GitHub Copilot CLI records sessions as plain text process logs in the repository directory. This is the most limited and lossy session store format.

Artifact Paths

Process logs

<repo>/logs/copilot/process-*.log

Plain text log files. Tool calls are embedded as JSON in log lines.

Copilot — process log with embedded function_call
TEXT
2026-04-27T16:16:57.841Z [INFO] Workspace initialized: copilot-uc001-tool-result (checkpoints: 0)
2026-04-27T16:16:57.847Z [INFO] Starting Copilot CLI: 1.0.36
2026-04-27T16:17:17.990Z [INFO] Accumulated output items (1): [{"arguments":"{\"format\":\"json\"}","call_id":"call_fixture_001","id":"c1","name":"repo_status","status":"completed","type":"function_call"}]
2026-04-27T16:17:45.200Z [INFO] Session completed.

Copilot process logs are the most lossy format. User intent, system instructions, and structured content are not preserved. Tool call arguments are stringified JSON.

Preservation priorities

When preserving session stores for investigation, follow these priorities:

  • Copy the raw files first. Do not parse, normalize, or transform before preserving. Use forensic copy tools when possible.
  • Record the source path and collection time. Provenance metadata is essential for defending the evidence chain.
  • Check for variant directories. Codex has active, archived, and headless sessions. OpenCode has both SQLite and legacy JSON. OpenClaw has .jsonl.archived and .jsonl.reset variants. Missing a variant means missing evidence.
  • Hash the preserved files. SHA-256 hashes let you verify that preserved copies have not been modified.
  • Do not run the agent or its tools on the preserved copy. Session stores can be modified by agent startup, compaction, or rotation.
  • Check for VS Code extension variants. Roo Code and Kilo Code store data under VS Code globalStorage paths that differ from CLI-based agents.