AI Agent CLI Session Files Analysis¶
Last Updated: 2025-12-17
Overview¶
This document compares session file storage mechanisms across three AI coding assistants: Claude Code, Codex (OpenAI), and Gemini CLI. The focus is on chat history readability for potential UI integration.
Detailed Documentation¶
| Agent | Files Overview | Schema Details |
|---|---|---|
| Claude Code | claude-session-files.md | claude-session-file-schema.md |
| Codex | codex-session-files.md | codex-session-file-schema.md |
| Gemini | gemini-session-files.md | gemini-session-file-schema.md |
Quick Comparison¶
| Feature | Claude Code | Codex | Gemini |
|---|---|---|---|
| Location | ~/.claude/projects/ |
~/.codex/sessions/ |
~/.gemini/tmp/ |
| Format | JSONL | JSONL | JSON |
| File per session | Yes | Yes | Yes |
| Can read chat | ✅ Yes | ✅ Yes | ✅ Yes |
| Parsing complexity | Medium | High | Low |
Chat Readability Summary¶
✅ All Three Support Chat History Reading¶
| Agent | User Messages | Assistant Messages | Tool Calls | Tool Results |
|---|---|---|---|---|
| Claude | type: "user" |
type: "assistant" |
tool_use in content |
Separate user message |
| Codex | event_msg.user_message |
response_item.message |
function_call |
function_call_output |
| Gemini | type: "user" |
type: "gemini" |
Inline toolCalls |
Inline in toolCalls.result |
Parsing Complexity Ranking¶
1. Gemini - Easiest ⭐¶
// Simple JSON parse
const session = JSON.parse(fs.readFileSync(path));
const messages = session.messages; // Direct access
Pros:
- Single JSON file (not JSONL)
- Only 2 message types (user, gemini)
- Flat structure
- Tool results inline
Cons: - Must load entire file into memory - File references expanded inline (noise in user content)
2. Claude Code - Medium¶
// JSONL parsing required
const lines = fs.readFileSync(path, 'utf-8').split('\n');
const messages = lines
.filter(line => line)
.map(line => JSON.parse(line))
.filter(entry => entry.type === 'user' || entry.type === 'assistant');
Pros: - Clear message types - Tool calls in same message as text - Good for streaming (line-by-line)
Cons:
- Multiple message types to handle
- Need to match tool_use with tool_result by ID
- Compact summaries need special handling
3. Codex - Most Complex¶
// JSONL + nested payload
const lines = fs.readFileSync(path, 'utf-8').split('\n');
const messages = lines
.filter(line => line)
.map(line => JSON.parse(line))
.filter(entry =>
entry.type === 'response_item' &&
entry.payload?.type === 'message'
)
.map(entry => ({
role: entry.payload.role,
content: entry.payload.content[0]?.text
}));
Pros: - Most detailed logging (every event) - Token tracking per response - Git snapshots for undo
Cons:
- 10+ entry types
- Deep nesting (payload.content[0].text)
- Need to distinguish event_msg vs response_item
- Function calls/outputs in separate entries
Recommended Integration Approach¶
For UI Integration (like Claude Code UI)¶
| Priority | Agent | Effort | Notes |
|---|---|---|---|
| 1 | Gemini | Low | Simple JSON, can reuse concepts |
| 2 | Claude | Medium | Already implemented |
| 3 | Codex | High | Complex filtering needed |
Parsing Strategy¶
- Gemini: Direct JSON parse, map messages
- Claude: JSONL parse, filter by type, handle tool matching
- Codex: JSONL parse, filter by type+payload.type, extract from nested structure
Key Insights¶
What Each Agent Stores¶
| Data Type | Claude | Codex | Gemini |
|---|---|---|---|
| User text | ✅ | ✅ | ✅ |
| Assistant text | ✅ | ✅ | ✅ |
| Tool calls | ✅ | ✅ | ✅ |
| Tool results | ✅ | ✅ | ✅ |
| Token usage | ✅ | ✅ | ✅ |
| Git state | ✅ | ✅ | ❌ |
| Session metadata | ✅ | ✅ | ✅ |
| Reasoning | ❌ | ✅ (encrypted) | ✅ (empty) |
Session Discovery¶
| Agent | How to List Sessions |
|---|---|
| Claude | Glob ~/.claude/projects/*/*.jsonl |
| Codex | Glob ~/.codex/sessions/**/*.jsonl or parse history.jsonl |
| Gemini | Glob ~/.gemini/tmp/*/chats/*.json |
Context Compression (Compact) Mechanisms¶
All three tools support context compression to manage long conversations. Their approaches differ significantly in storage strategy and data visibility.
Quick Comparison¶
| Aspect | Claude Code | Codex | Gemini |
|---|---|---|---|
| Command | Auto (context limit) | Auto | /compress |
| Storage Location | Same session file | Same session file | New session file |
| Summary Visibility | Plaintext | Encrypted | Not stored locally |
| Marker Type | isCompactSummary: true |
type: "compacted" |
type: "info" (empty) |
| Encryption | No | Yes | N/A |
Claude Code: Inline Plaintext Summary¶
Mechanism: Claude inserts a special user message containing the conversation summary directly in the session file.
Storage Structure:
{
"type": "user",
"message": {
"role": "user",
"content": "Here is a summary of our conversation: ..."
},
"isCompactSummary": true
}
Characteristics:
- Summary is fully readable in plaintext
- Appears as a synthetic "user" message
- Followed by compact_boundary system message
- Original conversation entries remain in file (but truncated from context)
Pros: - Transparent: users can read exactly what the AI remembers - Debuggable: easy to verify summary quality - Single file: no need to track multiple files
Cons: - Privacy: summary content visible in local files - File size: old messages still stored (not deleted)
Codex: Inline Encrypted Summary¶
Mechanism: Codex replaces conversation history with an encrypted compacted entry containing a replacement_history array.
Storage Structure:
{
"timestamp": "2025-12-15T23:47:53.813Z",
"type": "compacted",
"payload": {
"message": "",
"replacement_history": [
{ "type": "message", "role": "user", "content": "..." },
{ "type": "message", "role": "assistant", "content": "..." },
{
"type": "compaction",
"encrypted_content": "eyJ0eXAiOiJKV1QiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..."
}
]
}
}
Characteristics:
- Summary stored as encrypted JWT in compaction.encrypted_content
- Recent messages preserved in plaintext
- Followed by context_compacted event marker
- Server decrypts content when resuming session
Pros: - Privacy: summary content not readable locally - Security: sensitive code/context protected - Compact: older entries replaced, not appended
Cons: - Opaque: cannot verify what AI remembers - Server-dependent: summary only accessible via Codex API - Debugging: harder to troubleshoot context issues
Gemini: Session Split (Server-Side)¶
Mechanism: Gemini creates a new session file after compression; no summary is stored locally.
Storage Structure:
Before compress (original session):
{
"sessionId": "cb7326d0-...",
"startTime": "2025-12-16T06:35:09.158Z",
"messages": [
// ... full conversation ...
{ "type": "info", "content": "" } // compress marker
]
}
After compress (new session file):
{
"sessionId": "cb7326d0-...", // same sessionId
"startTime": "2025-12-17T11:06:07.158Z", // new startTime
"messages": [
// fresh conversation only
]
}
Characteristics:
- Empty info message marks compression point
- New file created with same sessionId but new startTime
- No local summary - context managed entirely server-side
- High input token count suggests server sends compressed context
Pros: - Clean files: each session file stays small - Privacy: no summary exposed locally - Simple parsing: no special compact handling needed
Cons:
- Fragmented: conversation split across files
- Opaque: no visibility into compressed context
- Linkage: must match sessionId across files to trace history
Architectural Comparison¶
┌─────────────────────────────────────────────────────────────────┐
│ CLAUDE CODE │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ session.jsonl │ │
│ │ ├── user: "message 1" │ │
│ │ ├── assistant: "response 1" │ │
│ │ ├── ... │ │
│ │ ├── user: "Here is summary..." (isCompactSummary: true) │◄── PLAINTEXT
│ │ ├── system: compact_boundary │ │
│ │ ├── user: "new message" │ │
│ │ └── assistant: "new response" │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ CODEX │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ session.jsonl │ │
│ │ ├── session_meta │ │
│ │ ├── event_msg: user_message │ │
│ │ ├── response_item: message │ │
│ │ ├── compacted: { │ │
│ │ │ replacement_history: [ │ │
│ │ │ { type: "compaction", │ │
│ │ │ encrypted_content: "eyJ..." } ◄─── ENCRYPTED │ │
│ │ │ ] │ │
│ │ │ } │ │
│ │ ├── context_compacted │ │
│ │ ├── event_msg: user_message (new) │ │
│ │ └── response_item: message (new) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ GEMINI │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ session-old.json │ │ session-new.json │ │
│ │ sessionId: "abc123" │ │ sessionId: "abc123" │ │
│ │ ├── user: "message 1" │ │ ├── user: "new msg" │ │
│ │ ├── gemini: "response" │ │ └── gemini: "response" │ │
│ │ ├── ... │ │ │ │
│ │ └── info: "" ◄── MARKER │ │ ◄─── NO SUMMARY │ │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │ ▲ │
│ └──────── same sessionId ──────┘ │
│ │
│ Summary: handled entirely server-side (not stored locally) │
└─────────────────────────────────────────────────────────────────┘
Design Philosophy Comparison¶
| Philosophy | Claude | Codex | Gemini |
|---|---|---|---|
| Transparency | High - user can read summary | Low - encrypted | None - not stored |
| Privacy | Low - plaintext on disk | High - encrypted | High - server-only |
| Debuggability | Easy | Hard | Medium |
| File Management | Single file grows | Single file, compacted | Multiple small files |
| Server Dependency | Low | High (for decryption) | High (for context) |
Recommendations for Integration¶
If building a session viewer:
-
Claude: Display
isCompactSummarymessages with special styling (e.g., collapsible summary block) -
Codex: Show "Context Compressed" placeholder for
type: "compacted"entries; cannot display actual summary -
Gemini: Link sessions by
sessionId; show timeline across split files; indicate compress points viatype: "info"
Additional Analysis: Codex & Gemini Deep Dive¶
Codex: Extended File Structure¶
Beyond session files, Codex maintains several additional data stores:
Global History (~/.codex/history.jsonl)¶
A global log of all user inputs across all sessions:
{"session_id":"019ad40c-...","ts":1764494465,"text":"帮我分析下..."}
{"session_id":"019ad420-...","ts":1764495481,"text":"开始实现"}
Use cases: - Cross-session search (find past commands) - User behavior analytics - Session discovery by content
Configuration (~/.codex/config.toml)¶
model = "gpt-5.2"
model_reasoning_effort = "xhigh"
[projects."/path/to/project"]
trust_level = "trusted"
[notice]
"hide_migration_prompt" = true
Key settings:
- model: Default model selection
- model_reasoning_effort: Reasoning intensity (low/medium/high/xhigh)
- projects.*: Per-project trust levels
- notice.*: UI notification preferences
TUI Logs (~/.codex/log/codex-tui.log)¶
2025-11-30T09:21:05Z INFO spawning ghost snapshot task
2025-11-30T09:21:06Z INFO ghost commit captured: 2e611a12...
2025-11-30T09:21:12Z INFO ToolCall: shell_command {"command":"ls",...}
2025-11-30T09:23:06Z WARN stream disconnected - retrying turn (1/5)...
Logged events: - Ghost snapshot creation (git state capture) - Tool call invocations with arguments - Stream connection status and retries - Model selection per session
Version Tracking (~/.codex/version.json)¶
{
"latest_version": "0.73.0",
"last_checked_at": "2025-12-17T10:54:43.943Z",
"dismissed_version": null
}
Codex: Session Event Types¶
Codex sessions contain rich event metadata:
| Event Type | Description | Key Fields |
|---|---|---|
session_meta |
Session init | id, cwd, cli_version, instructions, git |
response_item |
Messages | type, role, content |
event_msg.user_message |
User input | message, images |
event_msg.agent_reasoning |
AI thinking | text (reasoning content) |
event_msg.token_count |
Token usage | info.total_token_usage, rate_limits |
event_msg.entered_review_mode |
Review started | target.branch |
event_msg.exited_review_mode |
Review done | review_output.findings[] |
event_msg.context_compacted |
Compression marker | (marker only) |
compacted |
Compression data | replacement_history[] |
Reasoning Storage¶
Codex stores AI reasoning as agent_reasoning events:
{
"type": "event_msg",
"payload": {
"type": "agent_reasoning",
"text": "**Identifying potential bugs**\n\nI found possible issues..."
}
}
Token Tracking¶
Detailed breakdown per response:
{
"info": {
"total_token_usage": {
"input_tokens": 16293,
"cached_input_tokens": 4480,
"output_tokens": 368,
"reasoning_output_tokens": 256,
"total_tokens": 16661
},
"model_context_window": 258400
},
"rate_limits": {
"primary": { "used_percent": 0.0, "window_minutes": 300 },
"secondary": { "used_percent": 0.0, "window_minutes": 10080 }
}
}
Gemini: Extended File Structure¶
Configuration Files¶
| File | Content | Example |
|---|---|---|
settings.json |
Auth & features | {"security":{"auth":{"selectedType":"vertex-ai"}}} |
state.json |
UI state | {"defaultBannerShownCount": 2} |
google_accounts.json |
Account info | {"active": null, "old": []} |
installation_id |
UUID | d32f7d70-001a-491c-b711-6f9e0afe3e3e |
Project-Level Files¶
Each project (~/.gemini/tmp/{projectHash}/) contains:
| File | Description |
|---|---|
chats/session-*.json |
Session conversations |
logs.json |
User input history |
logs.json:
Gemini: Session Event Types¶
| Field | Type | Description |
|---|---|---|
type |
user/gemini/info |
Message role |
thoughts |
ThoughtEntry[] |
Reasoning (usually empty) |
tokens |
TokenInfo |
Token breakdown |
model |
string |
Model (e.g., gemini-2.5-flash) |
toolCalls |
ToolCall[] |
Inline tool invocations |
Thoughts/Reasoning¶
When enabled, Gemini stores structured reasoning:
{
"thoughts": [
{
"subject": "Analyzing Code Revisions",
"description": "I'm currently focused on...",
"timestamp": "2025-12-16T06:35:15.023Z"
}
]
}
vs Codex: Gemini uses structured subject/description, Codex uses freeform agent_reasoning text.
Token Tracking¶
{
"tokens": {
"input": 22629,
"output": 805,
"cached": 0,
"thoughts": 3174,
"tool": 0,
"total": 26608
}
}
Unique: Gemini tracks thoughts tokens separately.
Feature Comparison Matrix¶
| Feature | Claude | Codex | Gemini |
|---|---|---|---|
| File Format | JSONL | JSONL | JSON |
| Global History | history.jsonl (global) |
history.jsonl (global) |
logs.json (per-project) |
| Config File | settings.json |
config.toml |
settings.json |
| Model Config | Per session | Global + trust levels | Global only |
| Reasoning Storage | Not stored | agent_reasoning events |
thoughts[] array |
| Token Breakdown | usage object |
Detailed + rate limits | Includes thoughts |
| Tool Calls | Separate entries | function_call events |
Inline toolCalls[] |
| Git Integration | gitCurrentState |
git + ghost snapshots |
Not stored |
| Debug Logs | debug/{session}.txt |
codex-tui.log |
Not exposed |
| Usage Stats | stats-cache.json |
Not exposed | Not exposed |
| Plugin System | Full (marketplaces) | Not exposed | Not exposed |
| Planning Mode | plans/*.md |
Not exposed | Not exposed |
| Shell Snapshots | shell-snapshots/*.sh |
Not exposed | Not exposed |
| File History | file-history/ |
Ghost snapshots | Not stored |
| Feature Flags | Statsig | Not exposed | Not exposed |
| IDE Integration | ide/*.lock |
Not exposed | Not exposed |
Summary: Unique Strengths¶
Claude Code:
- Most comprehensive data storage - plugins, plans, skills, stats, IDE integration
- Plaintext compact summaries (transparent)
- Rich usage analytics (stats-cache.json with daily/hourly breakdowns)
- Full plugin marketplace system
- Planning mode with auto-named markdown files
- Shell environment snapshots
- Per-file version history with hash-based naming
- IDE integration locks (VS Code, JetBrains)
- Feature flags via Statsig
Codex:
- Most detailed token/rate limit tracking per response
- Ghost snapshots for git undo
- Global search via history.jsonl
- TUI logs for debugging
- Per-project trust levels in config
- Review mode events with confidence scores
Gemini:
- Simplest structure (single JSON per session)
- Structured thinking with subject/description
- Separate thoughts token tracking
- Clean session splitting on compress
- Server-side compression (no local summary)
Conclusion¶
All three agents store readable chat history. Gemini is easiest to parse, followed by Claude, with Codex being the most complex due to its event-based architecture.
For building a unified UI: 1. Start with Gemini (simplest) 2. Adapt Claude patterns (already working) 3. Add Codex support last (most filtering logic needed)