Observed since 20 Oct, 23:20 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.
Local-first, cross-harness agent-session portability. Export a coding-agent session from one harness and resume it in another when the original hits a usage limit or otherwise stops.
Supports three harnesses today:
| Harness | Session store |
|---|---|
| Claude Code | ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl |
| Codex | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl |
| Hermes | ~/.hermes/sessions/<ts>_<id>.jsonl |
No cloud. Everything runs against files already on your disk.
session-bridge's real dependencies are other tools' storage formats, which can change without notice. This table states exactly what each reader/registrar was last verified against; registration paths validate the store schema at runtime and fail closed (no write) when it doesn't match.
| Harness | Last verified against | Verification |
|---|---|---|
| Claude Code | 2.1.212 | Live claude --resume recall of user AND assistant sentinels, from claude, codex, and hermes sources |
| Codex | Codex CLI 0.146.0 (state_5.sqlite) | Live codex resume recall of both-role sentinels from claude and hermes sources, and registered sessions listed in the resume picker |
| Hermes | state.db schema as of 2026-08-02 (sessions are db-only; JSONL exports no longer written) | Live hermes --resume replay with both-role recall, from claude and codex sources; db-only sessions read via export-hermes/TUI |
| Python | 3.11 – 3.13 | CI test matrix |
All six directed conversion pairs were live-verified on real installs on 2026-07-28 (each source harness resumed in each other target, with the model quoting both a user turn and one of its own prior replies).
A newer harness version usually still works (formats drift rarely), but treat
anything beyond this table as unverified: run session-bridge inspect first,
and expect SQLite registration to refuse cleanly if the schema moved.
Each harness writes an incompatible session log, and nothing bridges them.
Claude Code /export is lossy plain text, and OpenCode import/export is buggy
across versions. session-bridge normalizes any supported session into one
intermediate representation (IR), then renders it into another harness's shape.
It also carries the pending state (open tool calls, queued input) forward
through a resume handshake, so the receiving agent picks up deliberately
instead of guessing.
source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶ writer ─▶ target.jsonl
│
└─▶ resume handshake (prepended system message)
ir.py) is the union of what the three harnesses can express: threaded
messages with typed content blocks (text / reasoning / tool_call / tool_result),
session metadata, tool schemas, and explicit pending state.readers/) normalize each harness into the IR.writers/) render the IR into a target harness and emit a
ConversionReport naming every asymmetry that could not transfer losslessly.handshake.py) turns detected pending state + conversion notes
into a resume preamble injected as the first message of the resumed session.uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
brew install connectwithprakash/tap/session-bridge # or: Homebrew (TUI included)
uvx agent-session-bridge --help # or: no-install run
pip install 'agent-session-bridge[tui]' # or: plain pip
Installs the session-bridge command (docs use that name); an
agent-session-bridge alias is included, so either name runs the same CLI.
Drop [tui] for the CLI without the interactive TUI. From source:
cd session-bridge && uv sync --extra dev --extra tui # or: python3 -m pip install -e .
New here? TUTORIAL.md is a step-by-step walkthrough (find your
session file → inspect → convert → resume) with a real worked example. The
sections below are the quick reference.
Prefer a guided flow? The interactive TUI discovers sessions across all three stores, walks you through target/options, and shows conversion notes before anything is written:

python3 -m pip install -e '.[tui]' # the TUI needs the optional textual dependency
session-bridge tui
Pick a session, then c to convert (with optional Claude Code placement) or
g to register it into Hermes's state.db or Codex's state_5.sqlite. Both
flows end in a plan screen — loss warnings, backup plan, and the equivalent
CLI command — before any file or database is touched; registration backs up
the store first by default, and the planning phase opens live stores
read-only.
Working with an agent instead? The repo ships an agent skill
(skills/session-handoff) that teaches
any harness's agent to hand off its current session to another harness.
Install it machine-wide into every harness found on the box:
session-bridge install-skill # symlinks into ~/.claude, ~/.codex, ~/.hermes skills dirs
session-bridge install-skill --copy # copies instead (survives uninstall, goes stale on upgrade)
Newer Hermes builds keep sessions only in state.db (no JSONL exports). The
TUI reads them from the database directly; for the CLI, export first:
session-bridge export-hermes <session-id> -o session.jsonl
session-bridge convert --from hermes session.jsonl --to claude-code ...
Inspect a session's structure:
session-bridge inspect --from claude-code ~/.claude/projects/<dir>/<uuid>.jsonl
Convert between harnesses:
session-bridge convert --from hermes --to claude-code SESSION.jsonl \
-o resumed.jsonl --handshake-out resume.md
Conversion notes (lossy asymmetries) are printed to stderr; the handshake is
prepended to the output by default (use --no-handshake to disable).
If the source stopped mid-turn with a tool call that never returned, that call
has no result, and a provider rejects a tool call with no matching result on the
next turn (OpenAI Responses returns a 400; Anthropic requires a tool_result).
Pass --stub-open-calls to append a synthetic interrupted result
([session interrupted...], marked as an error) for each genuinely-open call, so
the converted transcript is valid to resume; the report still discloses that the
call was interrupted.
The conversation core (user/assistant text, reasoning summaries, tool calls,
tool results, and call↔result linkage) transfers between all three harnesses.
The following are inherently lossy and are reported per conversion (see
docs/schema-reference.md for the full analysis):
parentUuid branches. Converting
away flattens forks; converting in synthesizes a linear chain.How a converted transcript becomes resumable differs per harness. Both cases are
verified on real installs (a session was round-tripped Claude Code → Hermes →
Claude Code and successfully resumed in a live claude process, recalling a fact
that existed only in the converted transcript).
| Harness | How to place it | Resumes from file alone? |
|---|---|---|
| Claude Code (2.1.x) | Write to ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl, then claude --resume <uuid> launched from the matching cwd | Yes |
| Hermes | Valid filename in ~/.hermes/sessions/ is not enough | No (needs a SQLite session-store row) |
| Codex | register-codex writes a rollout and its threads index row in state_5.sqlite | Yes (live-recall verified with Codex CLI 0.145.0) |
Claude Code resolves --resume <uuid> directly from the transcript file. The
one catch: the encoded-cwd directory name must match the directory you launch
claude from (note macOS symlinks like /tmp → /private/tmp; use the real
resolved path). No separate index write is needed. Pass --place-claude-cwd to
convert and session-bridge writes the transcript to the right place and prints
the exact resume command:
session-bridge convert --from hermes --to claude-code SESSION.jsonl \
--place-claude-cwd ~/Developer/myproject
# placed resumable session -> ~/.claude/projects/-Users-you-Developer-myproject/<uuid>.jsonl
# resume with: (cd ~/Developer/myproject && claude --resume <uuid>)
If a transcript already exists at the chosen --session-id, placement fails
rather than silently overwriting a recovered session; pass --force to replace
it deliberately.
Hermes stores sessions in a SQLite database (~/.hermes/state.db), across a
sessions row plus one messages row per turn; the .jsonl files are exports,
not the source of truth. Use session-bridge register to write those rows (it
backs up the DB first):
session-bridge register --from claude-code SESSION.jsonl \
--model moonshotai/kimi-k3 --title "resumed from claude code"
# backed up state.db -> ...
# registered session sb_... into ~/.hermes/state.db
# resume with: hermes --resume sb_...
Verified end-to-end against a real store: hermes --resume replays the registered
history and the model recalls it. Two things matter, both handled by the command:
started_at (set automatically) so the session isn't sorted below the
default hermes sessions list limit;--model must name a model Hermes has a provider for. A cross-harness source id
(e.g. an Anthropic claude-* id from a Claude Code session) that Hermes cannot
route makes the resumed turn fall back and lose context, so set --model to a
Hermes-configured model.Codex reading and round-trip are validated against a real tool-using session
(driven through OpenRouter): function_call / function_call_output / reasoning
(both summary and content[] shapes) parse correctly and round-trip identically.
Codex also indexes sessions in SQLite (state_5.sqlite, a threads row with a
rollout_path). Use register-codex to write a converted rollout and index it;
the command validates the local schema, takes a SQLite online-backup before the
mutation, publishes the rollout without overwriting another registration, then
indexes it in SQLite. It removes its own rollout if the index transaction fails;
a process crash can still leave an unindexed rollout, which Codex ignores.
session-bridge register-codex --from hermes SESSION.jsonl \
--cwd ~/Developer/myproject --title "resumed from Hermes"
# backed up Codex state_5.sqlite -> ...
# registered session <uuid> into ~/.codex/state_5.sqlite
# resume with: (cd ~/Developer/myproject && codex resume <uuid>)
This registration path is covered against an isolated Codex-shaped SQLite store
and was live-recall verified with Codex CLI 0.145.0: after codex exec resume
opened an imported session, the model returned a unique sentinel that existed
only in that transcript.
threads store; live recall was verified with Codex CLI 0.145.0. Future Codex
schema changes still need the same authenticated acceptance check.sessionId.is_error
is preserved as a [tool error] text prefix (and reported) rather than a field.python3 -m pytest # IR, three readers, writers/round-trips, handshake, placement, and both SQLite registrars
Before releases (or after a harness updates), run the live acceptance matrix against the real installed harnesses — it seeds sentinel sessions, bridges every pair, live-resumes each one, and cleans up after itself:
uv run --extra dev --extra tui python scripts/live_acceptance.py # full matrix
uv run --extra dev --extra tui python scripts/live_acceptance.py --structure-only # no LLM calls
Harnesses auto-update, so a weekly scheduled structure check catches a format break before it surfaces as a bug. On macOS:
bash scripts/install_weekly_check.sh # launchd job, Mondays 09:30, notifies on failure
Real captured sessions may contain secrets; fixtures/real/ is gitignored and
tests run only against synthetic, faithful fixtures.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.5.0 | 3 Aug 2026 | 2 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 23:20 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:6d380d68965aa26fecce0a7441626f217de34a2c4f472cd8b9c38ba7bd48b3de