A session is an append-only tree on disk under ~/.omp/agent/sessions/, grouped per working directory so two projects never share a history. Every turn is a node with a parent pointer; branching moves a leaf and appends from there, so the original timeline is always still in the file. For the on-disk schema, see Session format.
Session ids are time-ordered UUIDs (v7), so they sort by creation time and a short id prefix is enough to identify one with
-r.
Resume
Four flags cover the common cases:
omp -c # continue most recent in this cwd
omp -r # open a picker scoped to this project
omp -r 1f9d2a # resume by id prefix
omp --resume ./session.jsonl # resume an explicit file
omp --no-session # ephemeral; nothing written to disk-c prefers a per-terminal breadcrumb, so split panes and tmux windows in the same directory don’t step on each other. If the breadcrumb is missing it falls back to the newest session in the cwd, then starts fresh.
-r <prefix> looks up the id in the current project first, then globally. If the match lives somewhere else, omp prompts before forking it into the current cwd rather than silently changing directories on you. --session is an alias for --resume.
--fork <id|path> resumes a session into a brand-new file with a parentSession lineage marker, leaving the original untouched. Useful from scripts or one-shot runs:
omp --fork 1f9d2a # fork by id prefix
omp --fork ./session.jsonl # fork from an explicit file
--no-sessionruns ephemerally: nothing is persisted, and/fork,/export, and/shareare disabled for that run. Pair it with-pfor one-shot pipelines that must not leave traces on disk.
Full flag reference: CLI reference.
Navigate the tree (/tree)
/tree is the in-place navigator. It moves the leaf pointer to any earlier message in the current file — no new file, no fork — which is what you want when a turn went sideways or you need to scrub past a long tool detour.
● 1f9d2a user "rewrite the importer to stream"
└─● 1f9d2b assistant tool: read src/importer.ts
├─● 1f9d2c assistant edit src/importer.ts ← current leaf
│ └─● 1f9d2d user "add a test for the stream path"
└─● 1f9d2e assistant edit src/importer.ts (alt) ← branch B
└─◆ 1f9d2f [labeled: pre-refactor checkpoint]-
Type to fuzzy-search across messages; ←/→ pages through results.
-
Ctrl+O cycles the filter: default → no-tools → user-only → labeled-only → all.
-
Shift+L labels the highlighted entry. Labelled entries show up in the picker and survive compaction, so they’re the right tool for “come back here later” markers.
Branch vs fork
/branch stays in the same file and starts a new thread from a previous message — same id space, new leaf:
/branch # message selector opens; pick where to branch/fork clones the whole current session into a brand-new file with a parentSession lineage marker. The original is untouched — useful when you want to try a different approach without polluting the timeline:
/fork # pick a message; opens a new filePick
/branchwhen you want one file to be the canonical record of an exploration. Pick/forkwhen the alternative might get abandoned and you don’t want it cluttering the parent’s/treeview.
Compact (/compact)
/compact summarises the older half of the active branch and replaces it with a single summary entry; recent turns stay verbatim. Pass a focus to bias the summary, e.g. /compact Focus on the API changes. The file on disk is untouched — /tree still walks back into the pre-compaction history. Automatic triggers, configuration, and the three plan-mode approval paths live on the Memory & compaction page.
Browse from inside
Once you’re in a session, a handful of slash commands cover housekeeping without ever leaving the TUI.
| Command | What it does |
|---|---|
/resume | Open the session picker for the current project. |
/session info | Print id, path, parent lineage, and stats. |
/session delete | Delete the current file and return to the picker. |
/new | Start a fresh session without touching the current one. |
/drop | Delete the current session and start a new one. |
/rename <title> | Set the human label shown in pickers. |
/move <path> | Rebind the session to a different working directory. |
Full slash inventory and key chords: Slash commands.
Export
/export [path] writes a self-contained HTML rendering of the current session — header, entries, system prompt, tool schemas — and opens it in your browser. omp --export <session.jsonl> [output] does the same without starting an interactive session, which is what you want for batch-rendering archived files.
/dump copies a plaintext transcript to the clipboard: system prompt, active model, tool definitions, every message, and tool results. /copy opens a picker of smaller slices — the last agent message, individual code blocks, and recent commands the agent ran.
Share
/share exports to a temp HTML, then runs your custom share handler at ~/.omp/agent/share.{ts,js,mjs} if one exists. With no handler it falls back to a secret GitHub gist via gh and opens the result through gistpreview.github.io.
Custom-handler failures do not fall back to gist — the gist path runs only when no handler is configured. If your handler throws,
/sharereports the error and stops.
Custom share handler
Drop a default-exported function at ~/.omp/agent/share.ts (or .js / .mjs) and /share calls it instead of the gist fallback. The signature:
// ~/.omp/agent/share.ts
export type CustomShareFn = (
htmlPath: string,
) => Promise<{ url?: string; message?: string } | string | undefined>Return a string (or { url }) and omp shows the URL and opens it in the browser. Return undefined and omp assumes your handler did its own UX.
Worked example: upload to S3
// ~/.omp/agent/share.ts
import { execFileSync } from "node:child_process"
import { basename } from "node:path"
const BUCKET = "s3://my-team-omp-shares"
const PUBLIC_BASE = "https://shares.my-team.dev"
export default async function share(htmlPath: string) {
const key = `${Date.now()}-${basename(htmlPath)}`
execFileSync("aws", ["s3", "cp", htmlPath, `${BUCKET}/${key}`, "--acl", "public-read"], {
stdio: "inherit",
})
const url = `${PUBLIC_BASE}/${key}`
return { url, message: `Uploaded ${key} (${BUCKET})` }
}Hand off to a teammate
End the turn cleanly with /handoff [focus]: it writes a structured wrap-up summarising state, open threads, and next steps. The receiver reads that entry first and knows exactly where you left off without scrolling the whole transcript.
Then pick a transport:
Gist (default)
/share renders to HTML and uploads it as a secret gist via gh. Zero setup if
gh is already authenticated.
Custom handler
Drop a default export at ~/.omp/agent/share.{ts,js,mjs} and /share
routes through it instead.
Raw file
For a fully editable hand-off, send the JSONL from
~/.omp/agent/sessions/<cwd-dir>/<timestamp>_<id>.jsonl directly. The receiver resumes
against it:
omp --resume ./handoff.jsonl
The JSONL file is the canonical record; HTML is just a rendering. If you want the receiver to keep iterating, send the JSONL. HTML is for read-only review.
Recipes
Reattach after a disconnect
SSH session dropped mid-turn. The agent is still writing — -c picks the most recent session in the cwd and replays the streaming tail:
ssh box
cd ~/work/api
omp -c # streams the in-flight assistant turn from where it left offSnapshot before a refactor
You’re about to ask for something risky. Mark the current leaf so you can return to it later:
/tree # opens navigator at the current leaf
# highlight the last user turn, press Shift+L
> pre-refactor # label the bookmark
# Esc back to the prompt; do the risky thing.
# Later, if it goes sideways:
/tree # filter to labeled-only with Ctrl+O, find "pre-refactor"
/branch # branches from the bookmark, original timeline preservedFork to try a different approach
Fork from the last user turn, swap models, give it ten turns, and abandon if it doesn’t land:
/fork # copies the whole session into a new file
/model # switch to the model you want to evaluate
> redo this using streams instead of buffers
# If it works: /handoff and /share the new file.
# If it doesn't: omp -r → pick the original session, keep going.Force a focused compact before a hand-off
/compact Focus on the importer streaming bug and the fix in src/importer.ts
/handoff The streaming importer now copes with empty rows; remaining work is the test for the partial-flush path.See Memory & compaction for when compaction fires automatically, and Session format for the on-disk JSONL schema.\n