agent-session-bridge 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. agent_session_bridge-0.2.0/LICENSE +21 -0
  2. agent_session_bridge-0.2.0/PKG-INFO +288 -0
  3. agent_session_bridge-0.2.0/README.md +257 -0
  4. agent_session_bridge-0.2.0/pyproject.toml +53 -0
  5. agent_session_bridge-0.2.0/setup.cfg +4 -0
  6. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/PKG-INFO +288 -0
  7. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/SOURCES.txt +81 -0
  8. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/dependency_links.txt +1 -0
  9. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/entry_points.txt +3 -0
  10. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/requires.txt +6 -0
  11. agent_session_bridge-0.2.0/src/agent_session_bridge.egg-info/top_level.txt +1 -0
  12. agent_session_bridge-0.2.0/src/session_bridge/__init__.py +0 -0
  13. agent_session_bridge-0.2.0/src/session_bridge/_ids.py +43 -0
  14. agent_session_bridge-0.2.0/src/session_bridge/cli.py +473 -0
  15. agent_session_bridge-0.2.0/src/session_bridge/convert.py +130 -0
  16. agent_session_bridge-0.2.0/src/session_bridge/handshake.py +175 -0
  17. agent_session_bridge-0.2.0/src/session_bridge/ir.py +246 -0
  18. agent_session_bridge-0.2.0/src/session_bridge/place.py +98 -0
  19. agent_session_bridge-0.2.0/src/session_bridge/readers/__init__.py +0 -0
  20. agent_session_bridge-0.2.0/src/session_bridge/readers/_content.py +42 -0
  21. agent_session_bridge-0.2.0/src/session_bridge/readers/_jsonl.py +50 -0
  22. agent_session_bridge-0.2.0/src/session_bridge/readers/_pending.py +63 -0
  23. agent_session_bridge-0.2.0/src/session_bridge/readers/claude_code.py +226 -0
  24. agent_session_bridge-0.2.0/src/session_bridge/readers/codex.py +203 -0
  25. agent_session_bridge-0.2.0/src/session_bridge/readers/hermes.py +167 -0
  26. agent_session_bridge-0.2.0/src/session_bridge/skill_install.py +134 -0
  27. agent_session_bridge-0.2.0/src/session_bridge/skills/session-handoff/SKILL.md +119 -0
  28. agent_session_bridge-0.2.0/src/session_bridge/tui/__init__.py +0 -0
  29. agent_session_bridge-0.2.0/src/session_bridge/tui/actions.py +123 -0
  30. agent_session_bridge-0.2.0/src/session_bridge/tui/app.py +65 -0
  31. agent_session_bridge-0.2.0/src/session_bridge/tui/discovery.py +208 -0
  32. agent_session_bridge-0.2.0/src/session_bridge/tui/options.py +86 -0
  33. agent_session_bridge-0.2.0/src/session_bridge/tui/register.py +475 -0
  34. agent_session_bridge-0.2.0/src/session_bridge/tui/screens.py +710 -0
  35. agent_session_bridge-0.2.0/src/session_bridge/tui/summary.py +41 -0
  36. agent_session_bridge-0.2.0/src/session_bridge/writers/__init__.py +0 -0
  37. agent_session_bridge-0.2.0/src/session_bridge/writers/_common.py +288 -0
  38. agent_session_bridge-0.2.0/src/session_bridge/writers/claude_code.py +152 -0
  39. agent_session_bridge-0.2.0/src/session_bridge/writers/codex.py +154 -0
  40. agent_session_bridge-0.2.0/src/session_bridge/writers/codex_db.py +302 -0
  41. agent_session_bridge-0.2.0/src/session_bridge/writers/hermes.py +142 -0
  42. agent_session_bridge-0.2.0/src/session_bridge/writers/hermes_db.py +294 -0
  43. agent_session_bridge-0.2.0/tests/test_claude_reader.py +77 -0
  44. agent_session_bridge-0.2.0/tests/test_cli_tui.py +40 -0
  45. agent_session_bridge-0.2.0/tests/test_codex_db.py +317 -0
  46. agent_session_bridge-0.2.0/tests/test_codex_reader.py +87 -0
  47. agent_session_bridge-0.2.0/tests/test_handshake_convert_cli.py +84 -0
  48. agent_session_bridge-0.2.0/tests/test_hermes_db.py +196 -0
  49. agent_session_bridge-0.2.0/tests/test_hermes_reader.py +62 -0
  50. agent_session_bridge-0.2.0/tests/test_ir.py +89 -0
  51. agent_session_bridge-0.2.0/tests/test_place.py +55 -0
  52. agent_session_bridge-0.2.0/tests/test_review_fixes.py +153 -0
  53. agent_session_bridge-0.2.0/tests/test_review_round11_fixes.py +80 -0
  54. agent_session_bridge-0.2.0/tests/test_review_round12_fixes.py +98 -0
  55. agent_session_bridge-0.2.0/tests/test_review_round13_fixes.py +82 -0
  56. agent_session_bridge-0.2.0/tests/test_review_round14_fixes.py +102 -0
  57. agent_session_bridge-0.2.0/tests/test_review_round16_fixes.py +62 -0
  58. agent_session_bridge-0.2.0/tests/test_review_round17_fixes.py +61 -0
  59. agent_session_bridge-0.2.0/tests/test_review_round18_fixes.py +75 -0
  60. agent_session_bridge-0.2.0/tests/test_review_round18b_fixes.py +94 -0
  61. agent_session_bridge-0.2.0/tests/test_review_round1_fixes.py +225 -0
  62. agent_session_bridge-0.2.0/tests/test_review_round21_fixes.py +79 -0
  63. agent_session_bridge-0.2.0/tests/test_review_round22_fixes.py +107 -0
  64. agent_session_bridge-0.2.0/tests/test_review_round25_fixes.py +76 -0
  65. agent_session_bridge-0.2.0/tests/test_review_round27_fixes.py +100 -0
  66. agent_session_bridge-0.2.0/tests/test_review_round28_fixes.py +121 -0
  67. agent_session_bridge-0.2.0/tests/test_review_round2_fixes.py +214 -0
  68. agent_session_bridge-0.2.0/tests/test_review_round3_fixes.py +171 -0
  69. agent_session_bridge-0.2.0/tests/test_review_round4_fixes.py +187 -0
  70. agent_session_bridge-0.2.0/tests/test_review_round5_fixes.py +144 -0
  71. agent_session_bridge-0.2.0/tests/test_review_round6_fixes.py +58 -0
  72. agent_session_bridge-0.2.0/tests/test_review_round7_fixes.py +62 -0
  73. agent_session_bridge-0.2.0/tests/test_review_round8_fixes.py +71 -0
  74. agent_session_bridge-0.2.0/tests/test_review_round9_fixes.py +123 -0
  75. agent_session_bridge-0.2.0/tests/test_skill_install.py +88 -0
  76. agent_session_bridge-0.2.0/tests/test_stub_open_calls.py +171 -0
  77. agent_session_bridge-0.2.0/tests/test_tui_actions.py +121 -0
  78. agent_session_bridge-0.2.0/tests/test_tui_discovery.py +193 -0
  79. agent_session_bridge-0.2.0/tests/test_tui_options.py +91 -0
  80. agent_session_bridge-0.2.0/tests/test_tui_pilot.py +197 -0
  81. agent_session_bridge-0.2.0/tests/test_tui_register.py +505 -0
  82. agent_session_bridge-0.2.0/tests/test_tui_summary.py +62 -0
  83. agent_session_bridge-0.2.0/tests/test_writers.py +122 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prakash Chaudhary
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-session-bridge
3
+ Version: 0.2.0
4
+ Summary: Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another.
5
+ Author: Prakash Chaudhary
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/connectwithprakash/session-bridge
8
+ Project-URL: Repository, https://github.com/connectwithprakash/session-bridge
9
+ Project-URL: Changelog, https://github.com/connectwithprakash/session-bridge/blob/main/CHANGELOG.md
10
+ Project-URL: Project page, https://www.connectwithprakash.com/project/session-bridge
11
+ Keywords: ai-agents,claude-code,codex,session,cli,tui
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Provides-Extra: tui
29
+ Requires-Dist: textual>=1.0; extra == "tui"
30
+ Dynamic: license-file
31
+
32
+ # session-bridge
33
+
34
+ [![test](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml/badge.svg)](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
35
+ [![PyPI](https://img.shields.io/pypi/v/agent-session-bridge)](https://pypi.org/project/agent-session-bridge/)
36
+ [![Python](https://img.shields.io/pypi/pyversions/agent-session-bridge)](https://pypi.org/project/agent-session-bridge/)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
38
+
39
+ Local-first, cross-harness **agent-session portability**. Export a coding-agent
40
+ session from one harness and resume it in another when the original hits a usage
41
+ limit or otherwise stops.
42
+
43
+ Supports three harnesses today:
44
+
45
+ | Harness | Session store |
46
+ |---|---|
47
+ | Claude Code | `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl` |
48
+ | Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` |
49
+ | Hermes | `~/.hermes/sessions/<ts>_<id>.jsonl` |
50
+
51
+ No cloud. Everything runs against files already on your disk.
52
+
53
+ ## Supported versions
54
+
55
+ session-bridge's real dependencies are other tools' storage formats, which can
56
+ change without notice. This table states exactly what each reader/registrar was
57
+ last verified against; registration paths validate the store schema at runtime
58
+ and fail closed (no write) when it doesn't match.
59
+
60
+ | Harness | Last verified against | Verification |
61
+ |---|---|---|
62
+ | Claude Code | 2.1.x transcripts | Round-trip + live `claude --resume` recall of a converted-only fact |
63
+ | Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel |
64
+ | Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
65
+ | Python | 3.11 – 3.13 | CI test matrix |
66
+
67
+ A newer harness version usually still works (formats drift rarely), but treat
68
+ anything beyond this table as unverified: run `session-bridge inspect` first,
69
+ and expect SQLite registration to refuse cleanly if the schema moved.
70
+
71
+ ## Why
72
+
73
+ Each harness writes an incompatible session log, and nothing bridges them.
74
+ Claude Code `/export` is lossy plain text, and OpenCode import/export is buggy
75
+ across versions. session-bridge normalizes any supported session into one
76
+ intermediate representation (IR), then renders it into another harness's shape.
77
+ It also carries the *pending state* (open tool calls, queued input) forward
78
+ through a **resume handshake**, so the receiving agent picks up deliberately
79
+ instead of guessing.
80
+
81
+ ## How it works
82
+
83
+ ```
84
+ source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶ writer ─▶ target.jsonl
85
+
86
+ └─▶ resume handshake (prepended system message)
87
+ ```
88
+
89
+ - **IR** (`ir.py`) is the union of what the three harnesses can express: threaded
90
+ messages with typed content blocks (text / reasoning / tool_call / tool_result),
91
+ session metadata, tool schemas, and explicit pending state.
92
+ - **Readers** (`readers/`) normalize each harness into the IR.
93
+ - **Writers** (`writers/`) render the IR into a target harness and emit a
94
+ `ConversionReport` naming every asymmetry that could not transfer losslessly.
95
+ - **Handshake** (`handshake.py`) turns detected pending state + conversion notes
96
+ into a resume preamble injected as the first message of the resumed session.
97
+
98
+ ## Install
99
+
100
+ ```bash
101
+ uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
102
+ uvx agent-session-bridge --help # or: no-install run
103
+ pip install 'agent-session-bridge[tui]' # or: plain pip
104
+ ```
105
+
106
+ Installs the `session-bridge` command (docs use that name); an
107
+ `agent-session-bridge` alias is included, so either name runs the same CLI.
108
+ Drop `[tui]` for the CLI without the interactive TUI. From source:
109
+
110
+ ```bash
111
+ cd session-bridge && uv sync --extra dev --extra tui # or: python3 -m pip install -e .
112
+ ```
113
+
114
+ New here? [`TUTORIAL.md`](TUTORIAL.md) is a step-by-step walkthrough (find your
115
+ session file → inspect → convert → resume) with a real worked example. The
116
+ sections below are the quick reference.
117
+
118
+ ## Usage
119
+
120
+ Prefer a guided flow? The interactive TUI discovers sessions across all three
121
+ stores, walks you through target/options, and shows conversion notes **before**
122
+ anything is written:
123
+
124
+ ![session-bridge TUI walkthrough: pick a session, inspect, dry-run, write](docs/media/tui-demo.gif)
125
+
126
+ ```bash
127
+ python3 -m pip install -e '.[tui]' # the TUI needs the optional textual dependency
128
+ session-bridge tui
129
+ ```
130
+
131
+ Pick a session, then `c` to convert (with optional Claude Code placement) or
132
+ `g` to register it into Hermes's `state.db` or Codex's `state_5.sqlite`. Both
133
+ flows end in a plan screen — loss warnings, backup plan, and the equivalent
134
+ CLI command — before any file or database is touched; registration backs up
135
+ the store first by default, and the planning phase opens live stores
136
+ read-only.
137
+
138
+ Working with an agent instead? The repo ships an agent skill
139
+ ([`skills/session-handoff`](skills/session-handoff/SKILL.md)) that teaches
140
+ any harness's agent to hand off its current session to another harness.
141
+ Install it machine-wide into every harness found on the box:
142
+
143
+ ```bash
144
+ session-bridge install-skill # symlinks into ~/.claude, ~/.codex, ~/.hermes skills dirs
145
+ session-bridge install-skill --copy # copies instead (survives uninstall, goes stale on upgrade)
146
+ ```
147
+
148
+ Inspect a session's structure:
149
+
150
+ ```bash
151
+ session-bridge inspect --from claude-code ~/.claude/projects/<dir>/<uuid>.jsonl
152
+ ```
153
+
154
+ Convert between harnesses:
155
+
156
+ ```bash
157
+ session-bridge convert --from hermes --to claude-code SESSION.jsonl \
158
+ -o resumed.jsonl --handshake-out resume.md
159
+ ```
160
+
161
+ Conversion notes (lossy asymmetries) are printed to stderr; the handshake is
162
+ prepended to the output by default (use `--no-handshake` to disable).
163
+
164
+ If the source stopped mid-turn with a tool call that never returned, that call
165
+ has no result, and a provider rejects a tool call with no matching result on the
166
+ next turn (OpenAI Responses returns a 400; Anthropic requires a `tool_result`).
167
+ Pass `--stub-open-calls` to append a synthetic interrupted result
168
+ (`[session interrupted...]`, marked as an error) for each genuinely-open call, so
169
+ the converted transcript is valid to resume; the report still discloses that the
170
+ call was interrupted.
171
+
172
+ ## What transfers, and what doesn't
173
+
174
+ The conversation core (user/assistant text, reasoning summaries, tool calls,
175
+ tool results, and call↔result linkage) transfers between all three harnesses.
176
+ The following are **inherently lossy** and are reported per conversion (see
177
+ `docs/schema-reference.md` for the full analysis):
178
+
179
+ 1. **Thread topology:** only Claude Code has `parentUuid` branches. Converting
180
+ away flattens forks; converting in synthesizes a linear chain.
181
+ 2. **Reasoning signatures:** provider-bound, so reasoning survives as summary text.
182
+ 3. **Tool schemas:** only Hermes stores them; reconstructed from invoked names otherwise.
183
+ 4. **Base/system instructions:** only Codex stores them.
184
+ 5. **Queued user input:** only Claude Code records it, so it surfaces in the handshake.
185
+ 6. **Permission/sandbox posture:** richest in Codex, absent in Hermes.
186
+ 7. **Per-turn model switches:** Hermes stores a single session model.
187
+
188
+ ## Getting a converted session recognized by the target
189
+
190
+ How a converted transcript becomes resumable differs per harness. Both cases are
191
+ verified on real installs (a session was round-tripped Claude Code → Hermes →
192
+ Claude Code and successfully resumed in a live `claude` process, recalling a fact
193
+ that existed only in the converted transcript).
194
+
195
+ | Harness | How to place it | Resumes from file alone? |
196
+ |---|---|---|
197
+ | Claude Code (2.1.x) | Write to `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`, then `claude --resume <uuid>` **launched from the matching cwd** | **Yes** |
198
+ | Hermes | Valid filename in `~/.hermes/sessions/` is not enough | **No** (needs a SQLite session-store row) |
199
+ | 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) |
200
+
201
+ **Claude Code** resolves `--resume <uuid>` directly from the transcript file. The
202
+ one catch: the encoded-cwd directory name must match the directory you launch
203
+ `claude` from (note macOS symlinks like `/tmp` → `/private/tmp`; use the real
204
+ resolved path). No separate index write is needed. Pass `--place-claude-cwd` to
205
+ `convert` and session-bridge writes the transcript to the right place and prints
206
+ the exact resume command:
207
+
208
+ ```bash
209
+ session-bridge convert --from hermes --to claude-code SESSION.jsonl \
210
+ --place-claude-cwd ~/Developer/myproject
211
+ # placed resumable session -> ~/.claude/projects/-Users-you-Developer-myproject/<uuid>.jsonl
212
+ # resume with: (cd ~/Developer/myproject && claude --resume <uuid>)
213
+ ```
214
+
215
+ If a transcript already exists at the chosen `--session-id`, placement fails
216
+ rather than silently overwriting a recovered session; pass `--force` to replace
217
+ it deliberately.
218
+
219
+ **Hermes** stores sessions in a SQLite database (`~/.hermes/state.db`), across a
220
+ `sessions` row plus one `messages` row per turn; the `.jsonl` files are exports,
221
+ not the source of truth. Use `session-bridge register` to write those rows (it
222
+ backs up the DB first):
223
+
224
+ ```bash
225
+ session-bridge register --from claude-code SESSION.jsonl \
226
+ --model moonshotai/kimi-k3 --title "resumed from claude code"
227
+ # backed up state.db -> ...
228
+ # registered session sb_... into ~/.hermes/state.db
229
+ # resume with: hermes --resume sb_...
230
+ ```
231
+
232
+ Verified end-to-end against a real store: `hermes --resume` replays the registered
233
+ history and the model recalls it. Two things matter, both handled by the command:
234
+
235
+ - a real `started_at` (set automatically) so the session isn't sorted below the
236
+ default `hermes sessions list` limit;
237
+ - `--model` must name a model Hermes has a provider for. A cross-harness source id
238
+ (e.g. an Anthropic `claude-*` id from a Claude Code session) that Hermes cannot
239
+ route makes the resumed turn fall back and lose context, so set `--model` to a
240
+ Hermes-configured model.
241
+
242
+ **Codex** reading and round-trip are validated against a real tool-using session
243
+ (driven through OpenRouter): `function_call` / `function_call_output` / `reasoning`
244
+ (both `summary` and `content[]` shapes) parse correctly and round-trip identically.
245
+ Codex also indexes sessions in SQLite (`state_5.sqlite`, a `threads` row with a
246
+ `rollout_path`). Use `register-codex` to write a converted rollout and index it;
247
+ the command validates the local schema, takes a SQLite online-backup before the
248
+ mutation, publishes the rollout without overwriting another registration, then
249
+ indexes it in SQLite. It removes its own rollout if the index transaction fails;
250
+ a process crash can still leave an unindexed rollout, which Codex ignores.
251
+
252
+ ```bash
253
+ session-bridge register-codex --from hermes SESSION.jsonl \
254
+ --cwd ~/Developer/myproject --title "resumed from Hermes"
255
+ # backed up Codex state_5.sqlite -> ...
256
+ # registered session <uuid> into ~/.codex/state_5.sqlite
257
+ # resume with: (cd ~/Developer/myproject && codex resume <uuid>)
258
+ ```
259
+
260
+ This registration path is covered against an isolated Codex-shaped SQLite store
261
+ and was live-recall verified with Codex CLI 0.145.0: after `codex exec resume`
262
+ opened an imported session, the model returned a unique sentinel that existed
263
+ only in that transcript.
264
+
265
+ ## Known limitations
266
+
267
+ - Codex registration is schema-validated and regression-tested against an isolated
268
+ `threads` store; live recall was verified with Codex CLI 0.145.0. Future Codex
269
+ schema changes still need the same authenticated acceptance check.
270
+ - Queued-input detection is conservative: it may over-report undelivered input
271
+ rather than silently drop it (the safe direction for resume). Enqueue/dequeue
272
+ matching is scoped per `sessionId`.
273
+ - Pending-state resumption produces a handshake for a human/agent to act on; it
274
+ does not itself re-execute open tool calls.
275
+ - Failed tool results: Codex and Hermes have no native error flag, so `is_error`
276
+ is preserved as a `[tool error]` text prefix (and reported) rather than a field.
277
+ - Empty-content messages: writers preserve the turn to keep message count stable,
278
+ but a fully empty turn does not round-trip back through the Codex/Hermes readers'
279
+ content guards. This is documented, and not observed in real data.
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ python3 -m pytest # IR, three readers, writers/round-trips, handshake, placement, and both SQLite registrars
285
+ ```
286
+
287
+ Real captured sessions may contain secrets; `fixtures/real/` is gitignored and
288
+ tests run only against synthetic, faithful fixtures.
@@ -0,0 +1,257 @@
1
+ # session-bridge
2
+
3
+ [![test](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml/badge.svg)](https://github.com/connectwithprakash/session-bridge/actions/workflows/test.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/agent-session-bridge)](https://pypi.org/project/agent-session-bridge/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/agent-session-bridge)](https://pypi.org/project/agent-session-bridge/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+
8
+ Local-first, cross-harness **agent-session portability**. Export a coding-agent
9
+ session from one harness and resume it in another when the original hits a usage
10
+ limit or otherwise stops.
11
+
12
+ Supports three harnesses today:
13
+
14
+ | Harness | Session store |
15
+ |---|---|
16
+ | Claude Code | `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl` |
17
+ | Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` |
18
+ | Hermes | `~/.hermes/sessions/<ts>_<id>.jsonl` |
19
+
20
+ No cloud. Everything runs against files already on your disk.
21
+
22
+ ## Supported versions
23
+
24
+ session-bridge's real dependencies are other tools' storage formats, which can
25
+ change without notice. This table states exactly what each reader/registrar was
26
+ last verified against; registration paths validate the store schema at runtime
27
+ and fail closed (no write) when it doesn't match.
28
+
29
+ | Harness | Last verified against | Verification |
30
+ |---|---|---|
31
+ | Claude Code | 2.1.x transcripts | Round-trip + live `claude --resume` recall of a converted-only fact |
32
+ | Codex | Codex CLI 0.145.0 (`state_5.sqlite`) | Live `codex resume` recall of a converted-only sentinel |
33
+ | Hermes | `state.db` schema as of 2026-07 | Live `hermes --resume` replay of a registered session |
34
+ | Python | 3.11 – 3.13 | CI test matrix |
35
+
36
+ A newer harness version usually still works (formats drift rarely), but treat
37
+ anything beyond this table as unverified: run `session-bridge inspect` first,
38
+ and expect SQLite registration to refuse cleanly if the schema moved.
39
+
40
+ ## Why
41
+
42
+ Each harness writes an incompatible session log, and nothing bridges them.
43
+ Claude Code `/export` is lossy plain text, and OpenCode import/export is buggy
44
+ across versions. session-bridge normalizes any supported session into one
45
+ intermediate representation (IR), then renders it into another harness's shape.
46
+ It also carries the *pending state* (open tool calls, queued input) forward
47
+ through a **resume handshake**, so the receiving agent picks up deliberately
48
+ instead of guessing.
49
+
50
+ ## How it works
51
+
52
+ ```
53
+ source.jsonl ─▶ reader ─▶ IR (Session: messages, tools, pending) ─▶ writer ─▶ target.jsonl
54
+
55
+ └─▶ resume handshake (prepended system message)
56
+ ```
57
+
58
+ - **IR** (`ir.py`) is the union of what the three harnesses can express: threaded
59
+ messages with typed content blocks (text / reasoning / tool_call / tool_result),
60
+ session metadata, tool schemas, and explicit pending state.
61
+ - **Readers** (`readers/`) normalize each harness into the IR.
62
+ - **Writers** (`writers/`) render the IR into a target harness and emit a
63
+ `ConversionReport` naming every asymmetry that could not transfer losslessly.
64
+ - **Handshake** (`handshake.py`) turns detected pending state + conversion notes
65
+ into a resume preamble injected as the first message of the resumed session.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ uv tool install 'agent-session-bridge[tui]' # recommended: CLI + TUI, isolated env
71
+ uvx agent-session-bridge --help # or: no-install run
72
+ pip install 'agent-session-bridge[tui]' # or: plain pip
73
+ ```
74
+
75
+ Installs the `session-bridge` command (docs use that name); an
76
+ `agent-session-bridge` alias is included, so either name runs the same CLI.
77
+ Drop `[tui]` for the CLI without the interactive TUI. From source:
78
+
79
+ ```bash
80
+ cd session-bridge && uv sync --extra dev --extra tui # or: python3 -m pip install -e .
81
+ ```
82
+
83
+ New here? [`TUTORIAL.md`](TUTORIAL.md) is a step-by-step walkthrough (find your
84
+ session file → inspect → convert → resume) with a real worked example. The
85
+ sections below are the quick reference.
86
+
87
+ ## Usage
88
+
89
+ Prefer a guided flow? The interactive TUI discovers sessions across all three
90
+ stores, walks you through target/options, and shows conversion notes **before**
91
+ anything is written:
92
+
93
+ ![session-bridge TUI walkthrough: pick a session, inspect, dry-run, write](docs/media/tui-demo.gif)
94
+
95
+ ```bash
96
+ python3 -m pip install -e '.[tui]' # the TUI needs the optional textual dependency
97
+ session-bridge tui
98
+ ```
99
+
100
+ Pick a session, then `c` to convert (with optional Claude Code placement) or
101
+ `g` to register it into Hermes's `state.db` or Codex's `state_5.sqlite`. Both
102
+ flows end in a plan screen — loss warnings, backup plan, and the equivalent
103
+ CLI command — before any file or database is touched; registration backs up
104
+ the store first by default, and the planning phase opens live stores
105
+ read-only.
106
+
107
+ Working with an agent instead? The repo ships an agent skill
108
+ ([`skills/session-handoff`](skills/session-handoff/SKILL.md)) that teaches
109
+ any harness's agent to hand off its current session to another harness.
110
+ Install it machine-wide into every harness found on the box:
111
+
112
+ ```bash
113
+ session-bridge install-skill # symlinks into ~/.claude, ~/.codex, ~/.hermes skills dirs
114
+ session-bridge install-skill --copy # copies instead (survives uninstall, goes stale on upgrade)
115
+ ```
116
+
117
+ Inspect a session's structure:
118
+
119
+ ```bash
120
+ session-bridge inspect --from claude-code ~/.claude/projects/<dir>/<uuid>.jsonl
121
+ ```
122
+
123
+ Convert between harnesses:
124
+
125
+ ```bash
126
+ session-bridge convert --from hermes --to claude-code SESSION.jsonl \
127
+ -o resumed.jsonl --handshake-out resume.md
128
+ ```
129
+
130
+ Conversion notes (lossy asymmetries) are printed to stderr; the handshake is
131
+ prepended to the output by default (use `--no-handshake` to disable).
132
+
133
+ If the source stopped mid-turn with a tool call that never returned, that call
134
+ has no result, and a provider rejects a tool call with no matching result on the
135
+ next turn (OpenAI Responses returns a 400; Anthropic requires a `tool_result`).
136
+ Pass `--stub-open-calls` to append a synthetic interrupted result
137
+ (`[session interrupted...]`, marked as an error) for each genuinely-open call, so
138
+ the converted transcript is valid to resume; the report still discloses that the
139
+ call was interrupted.
140
+
141
+ ## What transfers, and what doesn't
142
+
143
+ The conversation core (user/assistant text, reasoning summaries, tool calls,
144
+ tool results, and call↔result linkage) transfers between all three harnesses.
145
+ The following are **inherently lossy** and are reported per conversion (see
146
+ `docs/schema-reference.md` for the full analysis):
147
+
148
+ 1. **Thread topology:** only Claude Code has `parentUuid` branches. Converting
149
+ away flattens forks; converting in synthesizes a linear chain.
150
+ 2. **Reasoning signatures:** provider-bound, so reasoning survives as summary text.
151
+ 3. **Tool schemas:** only Hermes stores them; reconstructed from invoked names otherwise.
152
+ 4. **Base/system instructions:** only Codex stores them.
153
+ 5. **Queued user input:** only Claude Code records it, so it surfaces in the handshake.
154
+ 6. **Permission/sandbox posture:** richest in Codex, absent in Hermes.
155
+ 7. **Per-turn model switches:** Hermes stores a single session model.
156
+
157
+ ## Getting a converted session recognized by the target
158
+
159
+ How a converted transcript becomes resumable differs per harness. Both cases are
160
+ verified on real installs (a session was round-tripped Claude Code → Hermes →
161
+ Claude Code and successfully resumed in a live `claude` process, recalling a fact
162
+ that existed only in the converted transcript).
163
+
164
+ | Harness | How to place it | Resumes from file alone? |
165
+ |---|---|---|
166
+ | Claude Code (2.1.x) | Write to `~/.claude/projects/<encoded-cwd>/<uuid>.jsonl`, then `claude --resume <uuid>` **launched from the matching cwd** | **Yes** |
167
+ | Hermes | Valid filename in `~/.hermes/sessions/` is not enough | **No** (needs a SQLite session-store row) |
168
+ | 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) |
169
+
170
+ **Claude Code** resolves `--resume <uuid>` directly from the transcript file. The
171
+ one catch: the encoded-cwd directory name must match the directory you launch
172
+ `claude` from (note macOS symlinks like `/tmp` → `/private/tmp`; use the real
173
+ resolved path). No separate index write is needed. Pass `--place-claude-cwd` to
174
+ `convert` and session-bridge writes the transcript to the right place and prints
175
+ the exact resume command:
176
+
177
+ ```bash
178
+ session-bridge convert --from hermes --to claude-code SESSION.jsonl \
179
+ --place-claude-cwd ~/Developer/myproject
180
+ # placed resumable session -> ~/.claude/projects/-Users-you-Developer-myproject/<uuid>.jsonl
181
+ # resume with: (cd ~/Developer/myproject && claude --resume <uuid>)
182
+ ```
183
+
184
+ If a transcript already exists at the chosen `--session-id`, placement fails
185
+ rather than silently overwriting a recovered session; pass `--force` to replace
186
+ it deliberately.
187
+
188
+ **Hermes** stores sessions in a SQLite database (`~/.hermes/state.db`), across a
189
+ `sessions` row plus one `messages` row per turn; the `.jsonl` files are exports,
190
+ not the source of truth. Use `session-bridge register` to write those rows (it
191
+ backs up the DB first):
192
+
193
+ ```bash
194
+ session-bridge register --from claude-code SESSION.jsonl \
195
+ --model moonshotai/kimi-k3 --title "resumed from claude code"
196
+ # backed up state.db -> ...
197
+ # registered session sb_... into ~/.hermes/state.db
198
+ # resume with: hermes --resume sb_...
199
+ ```
200
+
201
+ Verified end-to-end against a real store: `hermes --resume` replays the registered
202
+ history and the model recalls it. Two things matter, both handled by the command:
203
+
204
+ - a real `started_at` (set automatically) so the session isn't sorted below the
205
+ default `hermes sessions list` limit;
206
+ - `--model` must name a model Hermes has a provider for. A cross-harness source id
207
+ (e.g. an Anthropic `claude-*` id from a Claude Code session) that Hermes cannot
208
+ route makes the resumed turn fall back and lose context, so set `--model` to a
209
+ Hermes-configured model.
210
+
211
+ **Codex** reading and round-trip are validated against a real tool-using session
212
+ (driven through OpenRouter): `function_call` / `function_call_output` / `reasoning`
213
+ (both `summary` and `content[]` shapes) parse correctly and round-trip identically.
214
+ Codex also indexes sessions in SQLite (`state_5.sqlite`, a `threads` row with a
215
+ `rollout_path`). Use `register-codex` to write a converted rollout and index it;
216
+ the command validates the local schema, takes a SQLite online-backup before the
217
+ mutation, publishes the rollout without overwriting another registration, then
218
+ indexes it in SQLite. It removes its own rollout if the index transaction fails;
219
+ a process crash can still leave an unindexed rollout, which Codex ignores.
220
+
221
+ ```bash
222
+ session-bridge register-codex --from hermes SESSION.jsonl \
223
+ --cwd ~/Developer/myproject --title "resumed from Hermes"
224
+ # backed up Codex state_5.sqlite -> ...
225
+ # registered session <uuid> into ~/.codex/state_5.sqlite
226
+ # resume with: (cd ~/Developer/myproject && codex resume <uuid>)
227
+ ```
228
+
229
+ This registration path is covered against an isolated Codex-shaped SQLite store
230
+ and was live-recall verified with Codex CLI 0.145.0: after `codex exec resume`
231
+ opened an imported session, the model returned a unique sentinel that existed
232
+ only in that transcript.
233
+
234
+ ## Known limitations
235
+
236
+ - Codex registration is schema-validated and regression-tested against an isolated
237
+ `threads` store; live recall was verified with Codex CLI 0.145.0. Future Codex
238
+ schema changes still need the same authenticated acceptance check.
239
+ - Queued-input detection is conservative: it may over-report undelivered input
240
+ rather than silently drop it (the safe direction for resume). Enqueue/dequeue
241
+ matching is scoped per `sessionId`.
242
+ - Pending-state resumption produces a handshake for a human/agent to act on; it
243
+ does not itself re-execute open tool calls.
244
+ - Failed tool results: Codex and Hermes have no native error flag, so `is_error`
245
+ is preserved as a `[tool error]` text prefix (and reported) rather than a field.
246
+ - Empty-content messages: writers preserve the turn to keep message count stable,
247
+ but a fully empty turn does not round-trip back through the Codex/Hermes readers'
248
+ content guards. This is documented, and not observed in real data.
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ python3 -m pytest # IR, three readers, writers/round-trips, handshake, placement, and both SQLite registrars
254
+ ```
255
+
256
+ Real captured sessions may contain secrets; `fixtures/real/` is gitignored and
257
+ tests run only against synthetic, faithful fixtures.
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agent-session-bridge"
7
+ version = "0.2.0"
8
+ description = "Local-first cross-harness agent-session portability: export a live coding-agent session from one harness and resume it in another."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Prakash Chaudhary" }]
13
+ keywords = ["ai-agents", "claude-code", "codex", "session", "cli", "tui"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: MacOS",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Utilities",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/connectwithprakash/session-bridge"
31
+ Repository = "https://github.com/connectwithprakash/session-bridge"
32
+ Changelog = "https://github.com/connectwithprakash/session-bridge/blob/main/CHANGELOG.md"
33
+ "Project page" = "https://www.connectwithprakash.com/project/session-bridge"
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["pytest>=7"]
37
+ tui = ["textual>=1.0"]
38
+
39
+ [project.scripts]
40
+ session-bridge = "session_bridge.cli:main"
41
+ # Alias matching the PyPI distribution name, so `uvx agent-session-bridge`
42
+ # and muscle-memory from `pip install agent-session-bridge` both work.
43
+ agent-session-bridge = "session_bridge.cli:main"
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["src"]
47
+
48
+ [tool.setuptools.package-data]
49
+ session_bridge = ["skills/*/SKILL.md"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+