Catalog PyPI
PACKAGE EXPLORERView in registry

agent-session-bridge

PyPI

Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.

ai-agentsclaude-codeclicodexsessiontui
Versions collected1
Direct dependencies2From release 0.5.0
Last observed20 Oct, 23:20 UTC

README

Version 0.5.0 documentation

session-bridge

test PyPI Python License: MIT

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:

HarnessSession 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.

Supported versions

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.

HarnessLast verified againstVerification
Claude Code2.1.212Live claude --resume recall of user AND assistant sentinels, from claude, codex, and hermes sources
CodexCodex 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
Hermesstate.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
Python3.11 – 3.13CI 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.

Why

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.

How it works

source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶ writer ─▶ target.jsonl
                                        │
                                        └─▶ resume handshake (prepended system message)
  • IR (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 (readers/) normalize each harness into the IR.
  • Writers (writers/) render the IR into a target harness and emit a ConversionReport naming every asymmetry that could not transfer losslessly.
  • Handshake (handshake.py) turns detected pending state + conversion notes into a resume preamble injected as the first message of the resumed session.

Install

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.

Usage

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:

session-bridge TUI walkthrough: pick a session, inspect, dry-run, write

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.

What transfers, and what doesn't

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):

  1. Thread topology: only Claude Code has parentUuid branches. Converting away flattens forks; converting in synthesizes a linear chain.
  2. Reasoning signatures: provider-bound, so reasoning survives as summary text.
  3. Tool schemas: only Hermes stores them; reconstructed from invoked names otherwise.
  4. Base/system instructions: only Codex stores them.
  5. Queued user input: only Claude Code records it, so it surfaces in the handshake.
  6. Permission/sandbox posture: richest in Codex, absent in Hermes.
  7. Per-turn model switches: Hermes stores a single session model.

Getting a converted session recognized by the target

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).

HarnessHow to place itResumes from file alone?
Claude Code (2.1.x)Write to ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl, then claude --resume <uuid> launched from the matching cwdYes
HermesValid filename in ~/.hermes/sessions/ is not enoughNo (needs a SQLite session-store row)
Codexregister-codex writes a rollout and its threads index row in state_5.sqliteYes (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:

  • a real 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.

Known limitations

  • Codex registration is schema-validated and regression-tested against an isolated threads store; live recall was verified with Codex CLI 0.145.0. Future Codex schema changes still need the same authenticated acceptance check.
  • Queued-input detection is conservative: it may over-report undelivered input rather than silently drop it (the safe direction for resume). Enqueue/dequeue matching is scoped per sessionId.
  • Pending-state resumption produces a handshake for a human/agent to act on; it does not itself re-execute open tool calls.
  • Failed tool results: Codex and Hermes have no native error flag, so is_error is preserved as a [tool error] text prefix (and reported) rather than a field.
  • Empty-content messages: writers preserve the turn to keep message count stable, but a fully empty turn does not round-trip back through the Codex/Hermes readers' content guards. This is documented, and not observed in real data.

Development

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.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.5.03 Aug 20262

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
connectwithprakash/agent-session-bridge

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 23:20 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:6d380d68965aa26fecce0a7441626f217de34a2c4f472cd8b9c38ba7bd48b3de