Processing Unknown Session Formats
This guide is a fallback for session files that do not match known formats (Claude Code JSONL, Codex rollout JSONL). Use it when you encounter a new tool or an unrecognized file structure.
Step 1: Identify the format#
Check the file extension and first few lines:
- JSONL (
.jsonl): One JSON object per line. Parse each line independently - JSON (
.json): Single JSON document. Could be an array of messages or a nested conversation object - Markdown (
.md): Likely a conversation export with## Human/## Assistantheaders or similar - Plain text (
.txt,.log): Look for turn separators (blank lines,---, timestamps) - SQLite (
.sqlite,.db): Database with conversation tables. List tables first, then query
For JSONL, check each line for a type, role, or kind field. The presence of certain fields reveals the format:
type: "user"/type: "assistant"+message.content-> Claude Code formattype: "response_item"/type: "event_msg"-> Codex formatrole: "user"/role: "assistant"without a wrapper type -> Raw API conversation logtype: "human"/type: "ai"-> LangChain/LangSmith format
Step 2: Classify each record#
For any conversation format, records fall into these universal categories:
Signal (extract)#
-
Human messages -- What the user asked, decided, or directed. These explain intent, requirements, and constraints. Look for:
- Records with
role: "user"ortype: "human"ortype: "user_message" - Text that reads like natural language instructions, questions, or feedback
- Short messages (under ~500 chars) are almost always signal
- Records with
-
AI reasoning text -- Explanations, analyses, decisions, and summaries the model produced. Look for:
- Records with
role: "assistant"and text content (not tool calls) - Fields named
text,content,message,output,response - Text that explains why something was done, not what command was run
- Records with
-
Final/summary responses -- The model's synthesized answer after a chain of tool use. Look for:
- The last assistant message before the next human message
- Fields named
final_response,last_message,summary,result - These are typically the densest signal per byte
-
Error messages and failures -- What went wrong and why. Look for:
- Records containing
error,failed,exception,traceback - These often reveal important constraints, gotchas, or architectural issues
- Records containing
Noise (skip)#
-
File contents returned by tools -- Already in the repo. Look for:
- Records with
type: "tool_result",type: "function_call_output", ortype: "tool_output" - Content that looks like source code (imports, function definitions, indented blocks)
- Long strings (>2KB) that are clearly file dumps
- Size clue: If a record is >10KB, it is almost certainly a tool result, not reasoning
- Records with
-
Tool invocations -- What commands were run. Operational, not knowledge. Look for:
- Records with
type: "tool_use",type: "function_call", ortype: "tool_call" - Fields named
name,arguments,input,command - Exception: file edit commands may contain the what of a change (summarize those)
- Records with
-
System prompts and instructions -- Same across sessions. Look for:
- Records with
role: "system"orrole: "developer" - Content wrapped in XML tags (
<instructions>,<context>,<rules>) - Long preambles about model behavior, tool availability, permissions
- Records with
-
Metadata and telemetry -- Session infrastructure. Look for:
- Token counts, usage statistics, rate limits
- Timestamps, UUIDs, session IDs (useful for linking but not knowledge)
- Permission changes, mode switches, checkpoint markers
-
Base64-encoded data -- Images, files, binary content. Look for:
- Long strings matching
[A-Za-z0-9+/=]{1000,}or data URIs - Fields named
image,data,base64,source.data
- Long strings matching
-
Duplicate records -- Many formats log the same event multiple ways. Look for:
- Records sharing an ID field (
call_id,tool_use_id,request_id) - The same text appearing in both a streaming record and a final record
- Records sharing an ID field (
Summarize (compress)#
- Sequences of tool calls -- "Read 15 files in src/lib/" is better than 15 individual read records
- Repetitive status updates -- "Still searching..." x10 -> "Searched extensively"
- Build/test output -- "Tests: 58/58 passed" not the full test runner output
- File edit details -- "Modified auth.ts: added token validation" not the full diff
Step 3: Estimate signal ratio#
Before processing the full file, sample it:
- Take the first 20 records, middle 20, and last 20
- Classify each as signal / noise / summarize
- Measure bytes in each category
Typical ratios from known formats:
- Claude Code: 3-15% signal, 85-97% noise (tool results + wrapper overhead dominate)
- Codex: 10-19% signal, 50-70% noise, 20-30% ambiguous (reasoning encrypted, compacted records)
- Raw API logs: 30-50% signal (no tool overhead, just conversation)
- Chat exports (markdown): 60-80% signal (already cleaned by the export process)
Step 4: Extract#
For each signal record, extract:
- Who said it: human or AI
- What they said: the text content
- When: timestamp if available
- Context: what came before (the preceding human message gives context to an AI response)
Structure the output as a sequence of turns:
[timestamp] HUMAN: <message>
[timestamp] AI: <response>
[timestamp] HUMAN: <follow-up>
[timestamp] AI: <response>
Step 5: Handle unknowns gracefully#
If you cannot classify a record:
- If it is <1KB, include it (small records are cheap to carry)
- If it is >10KB, skip it (large unclassified records are almost always tool output)
- If it contains natural language prose, include it
- If it contains code, JSON, or structured data, skip it
Privacy checks#
Before outputting extracted content, scan for:
- API keys: patterns like
sk-,ghp_,Bearer,token: " - Passwords: fields named
password,passwd,secret - Personal data: email addresses, IP addresses, phone numbers
- File paths: may reveal usernames (e.g.,
/Users/johndoe/) - JWT tokens: strings matching
eyJ...
Flag these but do not include them in extracted output.