Relocate a coding-agent session across harnesses (Codex, Claude Code, OpenCode, Grok, Cursor) and resume it natively.
Project description
harness-convert (hc)
Relocate a coding-agent session across harnesses and resume it natively.
The escape hatch: you're 80% through a fix, your harness hits a rate limit /
outage mid-task, and you can't even ask it for a handoff. hc reads the session
transcript off disk (the dead harness doesn't need to be running or your quota
intact), rewrites it into the target harness's format, and you keep going there.
hc # interactive wizard (TTY): pick from/session/to
hc --from claude --to codex # dry-run latest; TTY asks before write
hc --from claude --to codex -y # write without prompting
hc --from codex --to claude <session-id> # a specific session
hc --from claude --to codex --dest-cwd DIR # land it in a different folder
hc list --from claude -n 5 # newest 5; TTY: pick one and convert
hc list --from claude --no-interactive # plain table (also when piped)
By default it's a dry run. Pass --write or -y to create the file (or confirm
when prompted on a TTY). It then prints the exact resume command. Flags always
win; missing pieces prompt only on an interactive TTY. Stdlib only; set
HC_NO_INTERACTIVE=1 to force non-interactive mode.
How it works
A session is (a) a model-context stream, (b) a UI-render stream, and (c) identity metadata. Conversion maps all three.
- Common interface (
hconv/common.py): every harness maps to four records:UserMessage,AssistantMessage,ToolCall,ToolResult. This universal floor guarantees any pair converts and resumes. Private reasoning is dropped (each harness encrypts/owns its own; unrecoverable). - N² enrichment (
hconv/enrich.py): surplus the floor can't hold (session titles, ...) rides a sparse(source, dest)map, layered on top. A pair with no entry is simply common-only. The map never re-encodes the common records. - Adapters (
hconv/adapters/): one per harness,locate / read / dest_path / write. Codex's writer emits BOTH streams (response_itemfor the model,event_msgfor scrollback incl.exec_command_end/patch_apply_endtool cards); Claude's single row set serves both. OpenCode is SQLite, not JSONL: it reads thesession/message/parttables read-only, and writes the canonical{info, messages}file thatopencode importvalidates and ingests (safer than poking a live WAL DB), soopencode -s <id>resumes it. Grok Build stores a session directory (summary.json+chat_history.jsonl+updates.jsonlunder~/.grok/sessions/); write emits all three sogrok --resume <id>loads history. Cursor is a content-addressed protobuf blob tree inside SQLite; the adapter walks it from the current root, and is read-only (writable = False, enforced at the CLI). - Ragged-tail close (
synthesize_missing_results): the source usually died mid-tool-call, so every orphanToolCallgets a synthetic result, else the resumed API call rejects the history.
Install
pipx install harness-convert # PyPI
npm i -g @theharshitsingh/hc # npm (needs python3 on PATH)
brew install harshitsinghbhandari/tap/harness-convert # Homebrew
Stdlib only, no dependencies. From a checkout, pipx install . or plain
python3 hc.py ... also work.
Supported
Codex (~/.codex), Claude Code (~/.claude), OpenCode
(~/.local/share/opencode), and Grok Build (~/.grok, or $GROK_HOME): any
direction between the writable ones. Converting into OpenCode writes an import
file; resume with opencode import <file> && opencode -s <id> (the command hc
prints). Converting into Grok writes a session directory; resume with
grok --resume <id>. Within a harness, sessions are also freely relocatable
across working directories (pure metadata rewrite, lossless).
Cursor (~/.cursor/chats) is a source only: --from cursor works, --to cursor is rejected. cursor-agent has no import command, so writing a session
would mean authoring its undocumented protobuf blob tree with nothing to
validate the result against. Reading is full fidelity, tool outputs included.
See docs/cursor-format.md. Grok on-disk shapes: docs/grok-format.md.
Test
python3 tests/test_hconv.py
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file harness_convert-0.5.0.tar.gz.
File metadata
- Download URL: harness_convert-0.5.0.tar.gz
- Upload date:
- Size: 67.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
454c7a055de46d5775eff61e194f9d91f394eed6c7849839136306f8179e08f1
|
|
| MD5 |
97e055408fa55504ebef546d8dc3d080
|
|
| BLAKE2b-256 |
2e2ab58e19966334596a5efc25436f98b223acfad77eb8f29c6f35653e7a2c2b
|
Provenance
The following attestation bundles were made for harness_convert-0.5.0.tar.gz:
Publisher:
release.yml on harshitsinghbhandari/harness-convert
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harness_convert-0.5.0.tar.gz -
Subject digest:
454c7a055de46d5775eff61e194f9d91f394eed6c7849839136306f8179e08f1 - Sigstore transparency entry: 2336157120
- Sigstore integration time:
-
Permalink:
harshitsinghbhandari/harness-convert@c6472e53fee24e8143e71ce0d29d9fe479c6d7a7 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/harshitsinghbhandari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c6472e53fee24e8143e71ce0d29d9fe479c6d7a7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file harness_convert-0.5.0-py3-none-any.whl.
File metadata
- Download URL: harness_convert-0.5.0-py3-none-any.whl
- Upload date:
- Size: 36.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6174f8194d510a62529caebb0b37cd9dcd7b36abe6fb65d886b5e8d9e53d38be
|
|
| MD5 |
e60720790296465b3e1294b7835cc7d2
|
|
| BLAKE2b-256 |
967b5401d10c0ce87176397537ab406090792e984d0a01926b8e365797c5107c
|
Provenance
The following attestation bundles were made for harness_convert-0.5.0-py3-none-any.whl:
Publisher:
release.yml on harshitsinghbhandari/harness-convert
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
harness_convert-0.5.0-py3-none-any.whl -
Subject digest:
6174f8194d510a62529caebb0b37cd9dcd7b36abe6fb65d886b5e8d9e53d38be - Sigstore transparency entry: 2336157129
- Sigstore integration time:
-
Permalink:
harshitsinghbhandari/harness-convert@c6472e53fee24e8143e71ce0d29d9fe479c6d7a7 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/harshitsinghbhandari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c6472e53fee24e8143e71ce0d29d9fe479c6d7a7 -
Trigger Event:
push
-
Statement type: