UniSessions is an SDK-first AI CLI session converter for moving sessions between Codex, Claude Code, Pi, and OpenCode, with an MCP chat recall server built on top.
▄▄▄ ▄▄ ▄▄▄▄▄
█▀██ ██ ██▀▀▀▀█▄
██ ██ ▄ ▀▀ ▀██▄ ▄▀ ▀▀ ▄
██ ██ ████▄ ██ ▀██▄▄ ▄█▀█▄ ▄██▀█ ▄██▀█ ██ ▄███▄ ████▄ ▄██▀█
██ ██ ██ ██ ██ ▄ ▀██▄ ██▄█▀ ▀███▄ ▀███▄ ██ ██ ██ ██ ██ ▀███▄
▀█████▄▄██ ▀█▄██ ▀██████▀▄▀█▄▄▄█▄▄██▀█▄▄██▀▄██▄▀███▀▄██ ▀██▄▄██▀
Convert one AI CLI session format into another across Codex, Pi, OpenCode, and Claude Code.
I use a lot of AI coding CLIs Codex Claude Code Pi OpenCode and wanted to move a session from one tool into another without losing the useful conversation history
- Why I built this
- Quick Start
- Supported AI coding agents
- All 12 conversion directions
- How to list sessions
- How to convert sessions
- How to handle conflicts
- How to bulk export
- How to use custom paths
- MCP server for agent chat recall
- SDK usage
- Architecture
- Default session paths
- Data fidelity
- Performance
- FAQ
- Contributing
- Development
- License
I looked for a tool that could convert one AI CLI session into another AI CLI session format and found nothing so I built one
I also wanted my agent to remember things from my other sessions like if I solved a bug in one project I wanted to tell it hey in that other session I fixed this by doing X and it would go check and learn from it instead of me explaining the same thing again
So this does two things
- Moves your sessions between Codex Pi OpenCode and Claude Code in any direction all 12 combinations
- Lets your agent search through all your old chat history across all projects and providers so it can recall what you did before and learn from it
Then I wanted the project to not be locked into a CLI or MCP its an SDK first so you can use it for other projects to do cool stuff like build a GUI for it or whatever you want the CLI and MCP server are just built on top of the SDK
Clone the repo and run from it directly
git clone https://github.com/vibheksoni/session-export.git
cd session-export
python -m unisessions list codex
python -m unisessions list pi
python -m unisessions list opencode
python -m unisessions list claudeConvert a single session (dry-run first, then write):
python -m unisessions codex-to-pi <session-id>
python -m unisessions codex-to-pi <session-id> --write
python -m unisessions claude-to-pi <session-id> --writeBulk export all Codex sessions to Pi:
python -m unisessions codex-to-pi-all --write --workers 8Search across all sessions via MCP:
python -m unisessions.mcp_server| Agent | Store | Session Format | Session IDs |
|---|---|---|---|
| OpenAI Codex | codex |
JSONL rollout files under date tree | UUID v7 |
| Pi | pi |
JSONL append-only tree entries in cwd-encoded dirs | UUID v7 |
| OpenCode | opencode |
Official export/import JSON | ses_ prefixed |
| Claude Code | claude |
JSONL transcript files in cwd-sanitized dirs | UUID v4 |
| From \ To | Pi | Codex | OpenCode | Claude |
|---|---|---|---|---|
| Codex | codex-to-pi |
-- | codex-to-opencode |
codex-to-claude |
| Pi | -- | pi-to-codex |
pi-to-opencode |
pi-to-claude |
| OpenCode | opencode-to-pi |
opencode-to-codex |
-- | opencode-to-claude |
| Claude | claude-to-pi |
claude-to-codex |
claude-to-opencode |
-- |
python -m unisessions list codex
python -m unisessions list pi
python -m unisessions list opencode
python -m unisessions list claudeAll commands default to dry-run. Add --write to produce output.
python -m unisessions codex-to-pi <session-id> --write
python -m unisessions pi-to-codex <session-id> --write
python -m unisessions codex-to-opencode <session-id> --write
python -m unisessions pi-to-opencode <session-id> --write
python -m unisessions opencode-to-codex <session-id> --write
python -m unisessions opencode-to-pi <session-id> --write
python -m unisessions claude-to-pi <session-id> --write
python -m unisessions pi-to-claude <session-id> --write
python -m unisessions claude-to-codex <session-id> --write
python -m unisessions codex-to-claude <session-id> --write
python -m unisessions claude-to-opencode <session-id> --write
python -m unisessions opencode-to-claude <session-id> --writeWhen a destination file already exists use --on-conflict to control behavior:
| Mode | Behavior |
|---|---|
skip (default) |
Skip if destination exists |
overwrite |
Replace existing destination with new content |
fork |
Generate a new UUID session ID, preserve old file untouched |
update |
Skip if unchanged, overwrite if source changed (fast head-meta + line count check) |
python -m unisessions codex-to-pi <id> --write --on-conflict fork
python -m unisessions codex-to-pi <id> --write --on-conflict updateExport all Codex sessions to one or more targets in parallel:
python -m unisessions codex-to-pi-all --write --workers 8
python -m unisessions export-all --write --targets pi opencode claude --workers 8Use system defaults by omitting path flags. For backups or staging:
python -m unisessions --codex-session-dir C:\path\to\sessions list codex
python -m unisessions --pi-session-dir C:\path\to\pi\sessions list pi
python -m unisessions --opencode-session-dir C:\path\to\opencode\exports list opencode
python -m unisessions --claude-session-dir C:\path\to\claude\projects list claudeOpenCode output files use the official import/export JSON shape. Load them with
opencode import <path-to-json>.
UniSessions ships a FastMCP server that exposes a SQLite FTS5 full-text search index over parsed session chat history. AI agents can use it to recall past conversations, find what was discussed, and search across all providers.
python -m unisessions.mcp_server{
"mcpServers": {
"unisessions": {
"command": "unisessions-mcp",
"args": [],
"env": {
"UNISESSIONS_SEARCH_INDEX": "C:\\Users\\you\\AppData\\Local\\unisessions\\search.sqlite"
}
}
}
}unisessions-mcp --transport streamable-http --host 127.0.0.1 --port 8765 --path /mcp
unisessions-mcp --transport http --host 127.0.0.1 --port 8765
unisessions-mcp --transport sse --host 127.0.0.1 --port 8765Environment knobs: UNISESSIONS_MCP_TRANSPORT, UNISESSIONS_MCP_HOST,
UNISESSIONS_MCP_PORT, UNISESSIONS_MCP_PATH, UNISESSIONS_MCP_LOG_LEVEL,
UNISESSIONS_MCP_SHOW_BANNER, UNISESSIONS_SEARCH_INDEX.
| Tool | Description |
|---|---|
list_chats |
List sessions globally or filtered by provider and project path |
index_status |
Report indexed, missing, stale, deleted, and refresh-size counts |
refresh_chats_index |
Parse sessions into SQLite FTS5 index for fast recall |
search_chats |
Full-text search with literal, regex, all-keywords, and any-keywords modes |
search_sessions |
Find which sessions match a topic, with match counts and top snippets |
search_chats returns a structured response with search_metadata (total_matches, deduplicated, sessions_searched, messages_searched, truncated) and a results array ranked by relevance score. Duplicate messages across compaction cycles are collapsed to a single hit with a duplicate_count field.
search_chats supports provider (codex, pi, opencode, claude), cwd,
session_id, role (user, assistant), message type (message, compaction,
contextual), exclude_keywords to filter out false positives, max_per_session
(default 5) to prevent one session from flooding results, date range (after,
before), and stale-index policy (refresh, skip, error).
Search runs over parsed TextMessage rows, not raw JSON files, so semantic
filters like roles=["user"] stay correct. Tool calls and tool outputs are
excluded to keep recall focused on chat text. An internal raw-match cap of 200
prevents timeouts on massive sessions.
Performance: warm indexed search ~35-40ms on a 20-session corpus. Cold
index refresh ~39-49s (I/O-bound). Call index_status first, then
refresh_chats_index, then use search_chats with stale_policy="skip" for
fast interactive recall.
Build your own application on top of session_sdk without the CLI:
from session_sdk import (
CodexStore, PiStore, PiDcpStore,
CodexToPiConverter, SessionIdFactory, WindowsDefaults,
)
defaults = WindowsDefaults()
codex = CodexStore(defaults.codex_home)
pi = PiStore(defaults.pi_agent_home)
dcp = PiDcpStore(defaults.pi_dcp_home)
converter = CodexToPiConverter(codex, pi, dcp, SessionIdFactory())
plan = converter.plan("your-session-id-here")
print(f"Source: {plan.source.path}")
print(f"Destination: {plan.destination}")
print(f"Records: {len(plan.records)}")
converter.write(plan, overwrite=False)from session_sdk import (
CodexStore, PiStore, OpenCodeStore, ClaudeStore,
SessionSearchEngine, WindowsDefaults,
)
defaults = WindowsDefaults()
engine = SessionSearchEngine(
CodexStore(defaults.codex_home),
PiStore(defaults.pi_agent_home),
OpenCodeStore(defaults.opencode_data_home),
claude=ClaudeStore(defaults.claude_home),
)
engine.refresh_index(provider="claude")
response = engine.search(
query="authentication",
provider="claude",
roles=["assistant"],
stale_policy="skip",
)
for hit in response["results"]:
print(f"{hit['session_id']} [{hit['role']}] {hit['snippet'][:80]}")
# SDK escape hatch for apps that want custom ranking or filtering.
raw_rows = engine.raw_search_rows(query="authentication", provider="claude")
engine.close()session-export/
pyproject.toml # both packages + optional [mcp] extra
session_sdk/ # the SDK core (no CLI dependencies)
__init__.py # public API surface
json_types.py # JSON type guards and coercion
jsonl.py # JSONL read/write helpers (orjson when available)
models.py # SessionSummary, TextMessage, NativeSession, ConversionPlan
paths.py # WindowsDefaults, path encoding, SessionIdFactory, timestamps
stores.py # CodexStore, PiStore, PiDcpStore, OpenCodeStore, ClaudeStore
converters.py # Extractors, builders, 12 converters
search.py # parsed SQLite FTS5 chat recall index/search
unisessions/ # CLI and MCP app (depends on session_sdk)
__init__.py
__main__.py # python -m unisessions
cli.py # argparse, command routing, dry-run/write, conflict resolution
mcp_server.py # FastMCP tools for chat recall/search
tests/
test_conversion.py # 22 tests
docs/ # full API documentation
examples/ # 7 tested Python example scripts
requirements.txt # tiktoken (required), orjson + google-re2 (optional)
requirements-mcp.txt # fastmcp for MCP server
json_types (leaf)
jsonl -> json_types
models -> json_types
paths (leaf)
stores -> models, jsonl, json_types, paths
converters -> stores, models, paths, json_types
search -> stores, converters, models
The SDK never imports from the CLI. unisessions depends on session_sdk,
never the reverse.
| Agent | Default Location |
|---|---|
| Codex sessions | ~/.codex/sessions |
| Codex archived | ~/.codex/archived_sessions |
| Pi sessions | ~/.pi/agent/sessions |
| Pi DCP sidecars | ~/.pi-dcp/sessions |
| OpenCode data | %APPDATA%\opencode or OPENCODE_GLOBAL_DATA_DIR |
| Claude Code | ~/.claude or CLAUDE_CONFIG_DIR |
UniSessions performs text-history conversions, not full behavioral state replay. It preserves user/assistant/system text and enough metadata for the target tool to open the session. It does not fully preserve every tool call, provider-specific event, approval state, sandbox state, MCP runtime, or UI-only event.
Compaction markers are preserved across all four formats so the target tool can reconstruct context correctly:
- Pi:
type="compaction"entry withsummary,firstKeptEntryId,tokensBefore,details, andfromHook=True - Codex:
type="compacted"record withpayload.messagesummary text - OpenCode: user message with
CompactionPart+ assistantsummary=True - Claude Code:
systementry withsubtype="compact_boundary"+logicalParentUuid, followed by user message withisCompactSummary=true
Codex injects contextual messages (permissions, AGENTS.md instructions,
environment context, skills, plugins) as developer or user role messages.
These are marked is_contextual=True by the SDK extractor and skipped by all
builders during export to prevent payload overflow in target tools. SDK
consumers can still inspect these messages.
orjsonfor JSON parsing when available (2x faster than stdlib)google-re2for regex search when available (4x faster than stdlibre)- O(1) session lookup via cached path index and ID index
has_changes()reads head meta + line count (no full JSON parse)- Bulk export reuses converter instances across sessions
- Default bulk workers: 8 (higher is opt-in; 32 workers measured ~2x slower)
- Warm indexed search: ~35-40ms on a 20-session corpus
- Cold index refresh: ~39-49s (I/O-bound, threading helps modestly)
Can I convert Claude Code sessions to Pi?
Yes. Use python -m unisessions claude-to-pi <session-id> --write. All 12
conversion directions are supported between Codex, Pi, OpenCode, and Claude
Code.
Can I export all my Codex sessions at once?
Yes. Use python -m unisessions codex-to-pi-all --write --workers 8 to bulk
export all Codex sessions to Pi in parallel. You can also export to multiple
targets at once with export-all --write --targets pi opencode claude.
Can my AI agent search my old chat history?
Yes. The MCP server exposes search_chats and search_sessions tools that do full-text search
over parsed chat messages from all providers. Your agent can recall what you
discussed in any session across any project. Search runs on a SQLite FTS5
index so warm queries are ~35ms. Results are ranked by relevance, deduplicated
across compaction cycles, and capped per session to prevent timeouts.
What happens if the destination session already exists?
Use --on-conflict to control behavior: skip (default), overwrite,
fork (new UUID, preserves old file), or update (skip if unchanged,
overwrite if source changed).
Does this preserve tool calls and tool outputs?
No. This tool performs text-history conversions preserving user and assistant chat messages, compaction summaries, and session metadata. Tool calls and tool outputs are not preserved in the current version.
Can I use the SDK without the CLI?
Yes. The SDK (session_sdk) is a standalone library with no CLI dependencies.
Import stores, converters, and the search engine directly in your own Python
projects. The CLI and MCP server are thin wrappers built on top.
What session formats are supported?
Codex JSONL rollout files, Pi JSONL append-only tree entries, OpenCode export/import JSON, and Claude Code JSONL transcript files. All four formats are supported in all 12 conversion directions.
How fast is bulk export?
A full export of 276 Codex sessions to Pi completes in about 65 seconds with 8 workers. The largest sessions (700MB+) take a few seconds each. Default workers is 8 because higher counts measured slower on I/O-bound workloads.
- Fork the repo
- Create a branch:
git checkout -b feature/your-feature - Run tests:
python -m unittest discover -s tests -v - Submit a PR with a description of your change
python -m compileall session_sdk unisessions tests
python -m unittest discover -s tests -v22 tests covering conversion shape, compaction extraction/emission, dry-run safety, assistant usage estimation, OpenCode JSON shape, custom session directories, path encoding, Claude extraction and conversion, search behavior, search index persistence, regex full-index search, keyword contraction handling, and dedup edge cases.
- X/Twitter: @ImVibhek
- Website: vibheksoni.com
- Security Blog: opendoors.wtf
MIT