Sessions are stored under ~/.omp/agent/sessions/<cwd-dir>/<timestamp>_<id>.jsonl, grouped
per working directory. One file is one session; one line is one entry. Entries are never rewritten in
place — the file is safe to tail -f while a session is live and safe to copy mid-turn.
For the user-facing resume / branch / share workflow, see Sessions.
Entry envelope
Every line is a JSON object with a type discriminator, an 8-character id, a parentId threading it into the tree, and a timestamp. The leaf isn’t an entry — it’s an implicit pointer at the newest reachable id.
| Field | Type | Notes |
|---|---|---|
type | string | Discriminator; see entry types below. |
id | string (8 hex) | Random 8-hex, collision-checked within the file. |
parentId | string | null | Parent entry id; null for the first message. |
timestamp | ISO 8601 | UTC, second precision or finer. |
Entry and session ids
Entry ids are random 8-character hex strings, collision-checked within the file. Session ids are time-ordered UUIDs (v7), so lexicographic sort matches creation order and a short prefix is enough for --resume. Branch and fork operations preserve entry ids; a fork copies the entries verbatim into a new file, gives the header a fresh session id, and records the source in parentSession.
Session header
The first line of every file is a session header carrying the schema version, working directory, optional title, and (on forks) a parentSession lineage marker.
{
"type": "session",
"version": 3,
"id": "1f9d29",
"timestamp": "2026-05-14T10:12:03Z",
"cwd": "/Users/me/src/api",
"title": "refactor importer"
}| Field | Type | Notes |
|---|---|---|
type | "session" | Header discriminator. |
version | number | Schema version; current is 3. |
id | string | Session id; matches the filename. |
cwd | string | Absolute working directory at session start. |
title | string? | Human label shown in pickers; set by /rename. |
parentSession | string? | Lineage marker: the source session id (fork) or session file path (branch into a new file). |
Message entries
A message entry wraps the agent message verbatim, with its role and a content array. Tool calls ride inside the assistant message’s content as toolCall blocks; each tool result is its own message entry with role: "toolResult", threaded into the tree as a child like any other entry.
{"type":"message","id":"1f9d2a0b","parentId":null,"timestamp":"…","message":{"role":"user","content":[{"type":"text","text":"refactor the importer to stream"}]}}
{"type":"message","id":"1f9d2b0c","parentId":"1f9d2a0b","timestamp":"…","message":{"role":"assistant","content":[{"type":"text","text":"Reading the file first."},{"type":"toolCall","id":"toolu_01","name":"read","arguments":{"path":"src/importer.ts"}}],"usage":{…},"stopReason":"toolUse"}}
{"type":"message","id":"1f9d2c0d","parentId":"1f9d2b0c","timestamp":"…","message":{"role":"toolResult","toolCallId":"toolu_01","toolName":"read","content":[{"type":"text","text":"…file contents…"}],"isError":false}}Content block types
| Block | Role | Shape |
|---|---|---|
text | any | { type, text } |
toolCall | assistant | { type, id, name, arguments } — id is the tool-call id; arguments is the JSON the model emitted. |
toolResult message | own entry | { role, toolCallId, toolName, content, isError } — toolCallId matches the toolCall.id; content is a content array (typically a single text block). |
thinking | assistant | { type, thinking } — extended-thinking output when the provider supports it. |
Other entry types
| Type | What it records |
|---|---|
model_change | Active model swapped (/model or role cycle). Carries model and role. |
mode_change | Plan mode toggled on or off. |
thinking_level_change | Extended-thinking budget changed for subsequent turns. |
service_tier_change | Provider service tier (priority, batch) changed. |
label | Bookmark on another entry. Carries targetId and label; points at an entry id, and compaction never deletes entries, so labels survive it. |
compaction | Marks where compaction replaced older turns. Carries summary, firstKeptEntryId, and tokensBefore. |
branch_summary | Summary of an abandoned branch, written when /tree moves the leaf away. |
session_init | Subagent sessions only; captures the system prompt, initial task, tool list, and any output schema for debugging/replay. |
custom / custom_message | Extension state — opaque payloads written by hooks, custom tools, or plugins. |
ttsr_injection | Records which TTSR rule names have been injected, so rules fire once per branch. |
mcp_tool_selection | Housekeeping — which MCP tools were exposed for this session. |
A worked example
A trimmed session showing the header, two turns with a tool call, a model change, a label, and a compaction:
{"type":"session","version":3,"id":"019700f1-1a2b-7c3d-8e4f-aabbccddeeff","timestamp":"2026-05-14T10:12:03Z","cwd":"/Users/me/src/api","title":"refactor importer"}
{"type":"message","id":"1f9d2a0b","parentId":null,"timestamp":"…","message":{"role":"user","content":[{"type":"text","text":"refactor the importer to stream"}]}}
{"type":"message","id":"1f9d2b0c","parentId":"1f9d2a0b","timestamp":"…","message":{"role":"assistant","content":[{"type":"text","text":"Reading the file first."},{"type":"toolCall","id":"toolu_01","name":"read","arguments":{"path":"src/importer.ts"}}],"stopReason":"toolUse"}}
{"type":"message","id":"1f9d2c0d","parentId":"1f9d2b0c","timestamp":"…","message":{"role":"toolResult","toolCallId":"toolu_01","toolName":"read","content":[{"type":"text","text":"…file contents…"}],"isError":false}}
{"type":"model_change","id":"1f9d2d0e","parentId":"1f9d2c0d","timestamp":"…","model":"anthropic/claude-opus","role":"default"}
{"type":"label","id":"1f9d2e0f","parentId":"1f9d2d0e","timestamp":"…","targetId":"1f9d2a0b","label":"pre-refactor"}
{"type":"compaction","id":"1f9d2f10","parentId":"1f9d2e0f","timestamp":"…","summary":"…","firstKeptEntryId":"1f9d2c0d","tokensBefore":48211}Reading a session as a file
From a shell, omp -p 'read /path/to/session.jsonl' renders the file with hashline anchors, so you can quote any entry by id and feed the line back into a follow-up turn without reformatting. See CLI reference for --export and other headless flags.\n