UniSessions
UniSessions is an SDK-first AI CLI session converter for moving sessions between Codex, Claude Code, Pi, OpenCode, Devin, Factory, Windsurf Cascade, Grok Build, Freebuff Desktop, and OMP (oh-my-pi), reading T3 Code threads too, with trace export for HuggingFace and fine-tuning (tool calls and reasoning included), plus an MCP chat recall server built on top.
▄▄▄ ▄▄ ▄▄▄▄▄
█▀██ ██ ██▀▀▀▀█▄
██ ██ ▄ ▀▀ ▀██▄ ▄▀ ▀▀ ▄
██ ██ ████▄ ██ ▀██▄▄ ▄█▀█▄ ▄██▀█ ▄██▀█ ██ ▄███▄ ████▄ ▄██▀█
██ ██ ██ ██ ██ ▄ ▀██▄ ██▄█▀ ▀███▄ ▀███▄ ██ ██ ██ ██ ██ ▀███▄
▀█████▄▄██ ▀█▄██ ▀██████▀▄▀█▄▄▄█▄▄██▀█▄▄██▀▄██▄▀███▀▄██ ▀██▄▄██▀
Convert one AI CLI session format into another across Codex, Pi, OpenCode, Claude Code, Devin, Factory, Windsurf Cascade, Grok Build, Freebuff Desktop, and OMP.
I use a lot of AI coding CLIs Codex Claude Code Pi OpenCode Devin Factory Windsurf Grok and wanted to move a session from one tool into another without losing the useful conversation history
Table of Contents
- Why I built this
- Quick Start
- Supported AI coding agents
- Conversion directions (110 in total)
- How to list sessions
- How to convert sessions
- How to export traces
- Options: tool calls and thinking
- 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
Why I built this
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 Claude Code Devin Factory Windsurf Cascade Grok Build Freebuff Desktop and OMP in any direction: 110 combinations, counting T3 Code
- Exports sessions as traces in HuggingFace STS OpenAI fine-tuning or ShareGPT format for Hub upload or model training
- 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
Quick Start
Install from PyPI:
pip install unisessions
python -m unisessions list codex
python -m unisessions list pi
python -m unisessions list opencode
python -m unisessions list claude
python -m unisessions list devin
python -m unisessions list factory
python -m unisessions list windsurf
python -m unisessions list grok
python -m unisessions list freebuff
python -m unisessions list omp
python -m unisessions list t3
Or clone the repo:
git clone https://github.com/vibheksoni/session-export.git
cd session-export
pip install -e .
python -m unisessions list codex
Convert 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> --write
python -m unisessions devin-to-pi <session-id> --write
python -m unisessions factory-to-pi <session-id> --write
python -m unisessions windsurf-to-pi <session-id> --write
python -m unisessions freebuff-to-pi <session-id> --write
python -m unisessions omp-to-pi <session-id> --write
python -m unisessions t3-to-pi <session-id> --write
python -m unisessions t3-to-codex <session-id> --write
Export a session as a HuggingFace trace:
python -m unisessions to-trace codex <session-id> --format sts --write -o trace.jsonl
Bulk export all Codex sessions to Pi:
python -m unisessions codex-to-pi-all --write --workers 8
Search across all sessions via MCP:
python -m unisessions.mcp_server
Supported AI coding agents
| 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 |
| Devin (Windsurf CLI) | devin |
ATIF transcript JSON + SQLite metadata | slug names |
| Factory (Droid) | factory |
JSONL transcript files with session headers | UUID v4 |
| Windsurf (Cascade) | windsurf |
AES-256-GCM encrypted protobuf trajectory files | UUID v4 |
| Grok (Grok Build and grok-cli) | grok |
ACP updates.jsonl + summary.json per session dir (Grok Build), SQLite grok.db (grok-cli) |
session directory name, or session id |
| Freebuff (Freebuff Desktop) | freebuff |
Per-project SQLite desktop-v2.db (threads + messages) + project.json |
thread id |
| OMP (oh-my-pi) | omp |
JSONL tree entries in cwd-encoded dirs, with a fixed-width title line | UUID v7 |
| T3 Code | t3 (source) and experimental target |
SQLite userdata/statev2.sqlite (legacy state.sqlite). Reads are read-only. |
thread id |
Conversion directions (110 in total)
T3 Code adds 20 directions, 110 in total. Reading T3 is stable: ten t3-to-<provider> commands. Writing into T3 is experimental: ten <provider>-to-t3 commands write a thread into a new, empty T3 home, which T3 imports the first time it starts there. The live T3 data directory is never written. See docs/stores.md.
| From \ To | Pi | Codex | OpenCode | Claude | Devin | Factory | Windsurf | Grok | Freebuff | OMP |
|---|---|---|---|---|---|---|---|---|---|---|
| Codex | codex-to-pi |
-- | codex-to-opencode |
codex-to-claude |
codex-to-devin |
codex-to-factory |
codex-to-windsurf |
codex-to-grok |
codex-to-freebuff |
codex-to-omp |
| Pi | -- | pi-to-codex |
pi-to-opencode |
pi-to-claude |
pi-to-devin |
pi-to-factory |
pi-to-windsurf |
pi-to-grok |
pi-to-freebuff |
pi-to-omp |
| OpenCode | opencode-to-pi |
opencode-to-codex |
-- | opencode-to-claude |
opencode-to-devin |
opencode-to-factory |
opencode-to-windsurf |
opencode-to-grok |
opencode-to-freebuff |
opencode-to-omp |
| Claude | claude-to-pi |
claude-to-codex |
claude-to-opencode |
-- | claude-to-devin |
claude-to-factory |
claude-to-windsurf |
claude-to-grok |
claude-to-freebuff |
claude-to-omp |
| Devin | devin-to-pi |
devin-to-codex |
devin-to-opencode |
devin-to-claude |
-- | devin-to-factory |
devin-to-windsurf |
devin-to-grok |
devin-to-freebuff |
devin-to-omp |
| Factory | factory-to-pi |
factory-to-codex |
factory-to-opencode |
factory-to-claude |
factory-to-devin |
-- | factory-to-windsurf |
factory-to-grok |
factory-to-freebuff |
factory-to-omp |
| Windsurf | windsurf-to-pi |
windsurf-to-codex |
windsurf-to-opencode |
windsurf-to-claude |
windsurf-to-devin |
windsurf-to-factory |
-- | windsurf-to-grok |
windsurf-to-freebuff |
windsurf-to-omp |
| Grok | grok-to-pi |
grok-to-codex |
grok-to-opencode |
grok-to-claude |
grok-to-devin |
grok-to-factory |
grok-to-windsurf |
-- | grok-to-freebuff |
grok-to-omp |
| Freebuff | freebuff-to-pi |
freebuff-to-codex |
freebuff-to-opencode |
freebuff-to-claude |
freebuff-to-devin |
freebuff-to-factory |
freebuff-to-windsurf |
freebuff-to-grok |
-- | freebuff-to-omp |
| OMP | omp-to-pi |
omp-to-codex |
omp-to-opencode |
omp-to-claude |
omp-to-devin |
omp-to-factory |
omp-to-windsurf |
omp-to-grok |
omp-to-freebuff |
-- |
How to list sessions
python -m unisessions list codex
python -m unisessions list pi
python -m unisessions list opencode
python -m unisessions list claude
python -m unisessions list devin
python -m unisessions list factory
python -m unisessions list windsurf
python -m unisessions list grok
python -m unisessions list freebuff
python -m unisessions list omp
How to convert sessions
All 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> --write
python -m unisessions devin-to-pi <session-id> --write
python -m unisessions pi-to-devin <session-id> --write
python -m unisessions devin-to-codex <session-id> --write
python -m unisessions codex-to-devin <session-id> --write
python -m unisessions devin-to-opencode <session-id> --write
python -m unisessions opencode-to-devin <session-id> --write
python -m unisessions devin-to-claude <session-id> --write
python -m unisessions claude-to-devin <session-id> --write
python -m unisessions factory-to-pi <session-id> --write
python -m unisessions pi-to-factory <session-id> --write
python -m unisessions factory-to-codex <session-id> --write
python -m unisessions codex-to-factory <session-id> --write
python -m unisessions factory-to-opencode <session-id> --write
python -m unisessions opencode-to-factory <session-id> --write
python -m unisessions factory-to-claude <session-id> --write
python -m unisessions claude-to-factory <session-id> --write
python -m unisessions factory-to-devin <session-id> --write
python -m unisessions devin-to-factory <session-id> --write
python -m unisessions windsurf-to-pi <session-id> --write
python -m unisessions pi-to-windsurf <session-id> --write
python -m unisessions windsurf-to-codex <session-id> --write
python -m unisessions codex-to-windsurf <session-id> --write
python -m unisessions windsurf-to-opencode <session-id> --write
python -m unisessions opencode-to-windsurf <session-id> --write
python -m unisessions windsurf-to-claude <session-id> --write
python -m unisessions claude-to-windsurf <session-id> --write
python -m unisessions windsurf-to-devin <session-id> --write
python -m unisessions devin-to-windsurf <session-id> --write
python -m unisessions windsurf-to-factory <session-id> --write
python -m unisessions factory-to-windsurf <session-id> --write
python -m unisessions grok-to-pi <session-id> --write
python -m unisessions pi-to-grok <session-id> --write
python -m unisessions grok-to-codex <session-id> --write
python -m unisessions codex-to-grok <session-id> --write
python -m unisessions grok-to-opencode <session-id> --write
python -m unisessions opencode-to-grok <session-id> --write
python -m unisessions grok-to-claude <session-id> --write
python -m unisessions claude-to-grok <session-id> --write
python -m unisessions grok-to-devin <session-id> --write
python -m unisessions devin-to-grok <session-id> --write
python -m unisessions grok-to-factory <session-id> --write
python -m unisessions factory-to-grok <session-id> --write
python -m unisessions grok-to-windsurf <session-id> --write
python -m unisessions windsurf-to-grok <session-id> --write
python -m unisessions freebuff-to-pi <session-id> --write
python -m unisessions pi-to-freebuff <session-id> --write
python -m unisessions freebuff-to-codex <session-id> --write
python -m unisessions codex-to-freebuff <session-id> --write
python -m unisessions freebuff-to-opencode <session-id> --write
python -m unisessions opencode-to-freebuff <session-id> --write
python -m unisessions freebuff-to-claude <session-id> --write
python -m unisessions claude-to-freebuff <session-id> --write
python -m unisessions freebuff-to-devin <session-id> --write
python -m unisessions devin-to-freebuff <session-id> --write
python -m unisessions freebuff-to-factory <session-id> --write
python -m unisessions factory-to-freebuff <session-id> --write
python -m unisessions freebuff-to-windsurf <session-id> --write
python -m unisessions windsurf-to-freebuff <session-id> --write
python -m unisessions freebuff-to-grok <session-id> --write
python -m unisessions grok-to-freebuff <session-id> --write
python -m unisessions omp-to-pi <session-id> --write
python -m unisessions pi-to-omp <session-id> --write
python -m unisessions omp-to-codex <session-id> --write
python -m unisessions codex-to-omp <session-id> --write
python -m unisessions omp-to-opencode <session-id> --write
python -m unisessions opencode-to-omp <session-id> --write
python -m unisessions omp-to-claude <session-id> --write
python -m unisessions claude-to-omp <session-id> --write
python -m unisessions omp-to-devin <session-id> --write
python -m unisessions devin-to-omp <session-id> --write
python -m unisessions omp-to-factory <session-id> --write
python -m unisessions factory-to-omp <session-id> --write
python -m unisessions omp-to-windsurf <session-id> --write
python -m unisessions windsurf-to-omp <session-id> --write
python -m unisessions omp-to-grok <session-id> --write
python -m unisessions grok-to-omp <session-id> --write
python -m unisessions omp-to-freebuff <session-id> --write
python -m unisessions freebuff-to-omp <session-id> --write
OMP resumes a session on the model its last reply names. If that model is not available, interactive omp falls back to your configured model with a warning, but omp -p and RPC mode refuse to resume unless you pass --model:
omp --resume <session-id> --model <provider/model> -p "continue"
How to export traces
Export any session as a trace file for HuggingFace Hub upload, model fine-tuning, or training data preparation. Three formats are supported:
| Format | Use case | Tool calls and reasoning |
|---|---|---|
sts |
HuggingFace Hub trace viewer (Session Trace Simple Format) | text only (the format has no tool events) |
openai |
OpenAI / Azure fine-tuning JSONL: assistant tool_calls, then role: tool results |
included |
sharegpt |
ShareGPT for LLaMA-Factory, Axolotl: gpt, function_call, and observation turns |
included |
Reasoning is written as a <think>...</think> block at the start of the assistant text. Leave either part out with --no-thinking or --no-tool-calls (see Options).
# HuggingFace STS format (default) -- print to stdout
python -m unisessions to-trace codex <session-id> --format sts
# OpenAI fine-tuning format -- write to file
python -m unisessions to-trace pi <session-id> --format openai --write -o train.jsonl
# ShareGPT format from Devin session
python -m unisessions to-trace devin <session-id> --format sharegpt --write -o traces.jsonl
Upload to HuggingFace Hub:
pip install huggingface-cli
hf upload your-username/your-dataset trace.jsonl
The Hub auto-detects the trace format and renders it in the trace viewer. See the trace export docs for SDK usage and format details.
Options: tool calls and thinking
Every conversion carries reasoning and tool calls (with their outputs and error flags) by default. Turn either part off per command, or for a whole environment:
python -m unisessions pi-to-claude <id> --write --no-tool-calls
python -m unisessions to-trace codex <id> --format openai --no-thinking --write -o train.jsonl
export UNISESSIONS_NO_TOOL_CALLS=1 # the default for every command in this shell
The same switches exist in the SDK:
from session_sdk import ConversionOptions, conversion_options
with conversion_options(ConversionOptions(include_tool_calls=False)):
plan = converter.plan(session_id) # the plan carries text and reasoning only
How to handle conflicts
When 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 |
Overwrite only when the source file was modified after the destination was written |
python -m unisessions codex-to-pi <id> --write --on-conflict fork
python -m unisessions codex-to-pi <id> --write --on-conflict update
How to bulk export
Export all Codex sessions to one or more targets in parallel. Every target listed below is supported:
python -m unisessions codex-to-pi-all --write --workers 8
python -m unisessions export-all --write --targets pi opencode claude devin factory windsurf grok freebuff omp --workers 8
How to use custom paths
Use 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 claude
python -m unisessions --devin-session-dir C:\path\to\devin\transcripts list devin
python -m unisessions --factory-session-dir C:\path\to\factory\sessions list factory
python -m unisessions --windsurf-session-dir C:\path\to\windsurf\cascade list windsurf
python -m unisessions --grok-home C:\path\to\grok --grok-session-dir C:\path\to\grok\sessions list grok
python -m unisessions --freebuff-home C:\path\to\freebuff --freebuff-session-dir C:\path\to\freebuff\projects list freebuff
python -m unisessions --omp-agent-home C:\path\to\omp\agent --omp-session-dir C:\path\to\omp\sessions list omp
OpenCode output files use the official import/export JSON shape. Load them with
opencode import <path-to-json>.
MCP server for agent chat recall
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.
Setup
python -m unisessions.mcp_server
MCP client configuration
{
"mcpServers": {
"unisessions": {
"command": "unisessions-mcp",
"args": [],
"env": {
"UNISESSIONS_SEARCH_INDEX": "%LOCALAPPDATA%\\unisessions\\search.sqlite"
}
}
}
}
HTTP transports for app-managed servers
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 8765
Environment knobs: UNISESSIONS_MCP_TRANSPORT, UNISESSIONS_MCP_HOST,
UNISESSIONS_MCP_PORT, UNISESSIONS_MCP_PATH, UNISESSIONS_MCP_LOG_LEVEL,
UNISESSIONS_MCP_SHOW_BANNER, UNISESSIONS_SEARCH_INDEX.
MCP tools
| 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, devin, factory, windsurf, grok, freebuff, omp, t3), 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.
SDK usage
Build your own application on top of session_sdk without the CLI:
from session_sdk import (
CodexStore, PiStore, PiDcpStore, FactoryStore, WindsurfStore,
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)
Trace export API
from session_sdk import (
CodexStore, WindowsDefaults,
MessageExtractor, build_trace,
)
from session_sdk.jsonl import _dumps
defaults = WindowsDefaults()
store = CodexStore(defaults.codex_home)
session = store.load("session-id")
messages = MessageExtractor().from_codex(session)
# Build HuggingFace STS-format trace
records = build_trace("sts", session, messages)
# Write to JSONL
with open("trace.jsonl", "wb") as f:
for record in records:
f.write(_dumps(record))
f.write(b"\n")
Search API
from session_sdk import (
CodexStore, PiStore, OpenCodeStore, ClaudeStore, DevinStore, FactoryStore, WindsurfStore, GrokStore, FreebuffStore, OmpStore,
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),
devin=DevinStore(defaults.devin_home),
factory=FactoryStore(defaults.factory_home),
windsurf=WindsurfStore(defaults.windsurf_home),
grok=GrokStore(defaults.grok_home),
freebuff=FreebuffStore(defaults.freebuff_home),
omp=OmpStore(defaults.omp_agent_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()
Architecture
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, DevinStore, FactoryStore, WindsurfStore, GrokStore, FreebuffStore, OmpStore, T3Store (read; experimental write)
converters.py # Extractors, builders, and a converter for every direction
options.py # ConversionOptions: carry or leave out thinking and tool calls
traces.py # STS, OpenAI, ShareGPT trace format builders
windsurf_pb.py # Windsurf Cascade protobuf layout (step types, variants)
data/ # packaged schema templates (T3 legacy state.sqlite, grok-cli grok.db)
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, trace export
mcp_server.py # FastMCP tools for chat recall/search
tests/
test_*.py # 136 tests: conversions, traces, options, update mode, Grok, T3, Windsurf, round trips
docs/ # full API documentation
examples/ # 7 tested Python example scripts
requirements.txt # tiktoken + cryptography (required), orjson + google-re2 (optional)
requirements-mcp.txt # fastmcp for MCP server
Dependency chain (no cycles)
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.
Default session paths
| 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 |
| Devin | %APPDATA%/devin or DEVIN_CONFIG_DIR |
| Factory | ~/.factory or FACTORY_CONFIG_DIR |
| Windsurf Cascade | ~/.codeium/windsurf/cascade/ or WINDSURF_CONFIG_DIR |
| Grok Build | $GROK_HOME/sessions or ~/.grok/sessions |
| grok-cli | $GROK_HOME/grok.db or ~/.grok/grok.db |
| Freebuff Desktop | $FREEBUFF_CONFIG_DIR/projects or ~/.config/freebuff-desktop/projects |
| OMP | ~/.omp/agent/sessions (or the agent dir of the active profile, PI_CODING_AGENT_DIR, or $XDG_DATA_HOME/omp) |
| T3 Code | $T3CODE_HOME/userdata or ~/.t3/userdata (statev2.sqlite, then legacy state.sqlite). Writes need an explicit --t3-home |
Data fidelity
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. Thinking, tool calls, and tool outputs are carried across where the target format has a field for them (see Format Support). Provider-specific events, approval state, sandbox state, MCP runtime, and UI-only events are not preserved.
How compaction is handled
Compaction markers are preserved across all ten 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 - Devin: compaction summaries become system steps in ATIF transcript JSON
- Windsurf Cascade: compaction checkpoints (field 30) become compaction summary messages
- Grok Build: compaction summaries import as
user_message_chunkenvelopes so the conversation stays readable in Grok's TUI - OMP:
type="compaction"entry whosefirstKeptEntryIdis its own id, so a resumed session starts from the summary
How contextual messages are handled
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.
Performance
orjsonfor JSON parsing when available (2x faster than stdlib)google-re2for regex search when available (4x faster than stdlibre)- Trace export to HuggingFace STS, OpenAI fine-tuning, and ShareGPT formats
- 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)
FAQ
Can I convert Claude Code sessions to Pi?
Yes. Use python -m unisessions claude-to-pi <session-id> --write. All 90
conversion directions are supported between Codex, Pi, OpenCode, Claude Code,
Devin, Factory, Windsurf Cascade, Grok, Freebuff Desktop, and OMP. T3 Code adds 20
more (110 in total).
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 devin factory windsurf grok freebuff omp.
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?
Yes, where the target format can hold them. Each tool call keeps its name, arguments, output, and error flag, and thinking is kept too. T3 Code writes keep text only, and some source tools (for example Windsurf's non-shell steps) are not decoded yet. See Format Support.
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, Claude Code JSONL transcript files, Devin ATIF
transcript JSON with SQLite metadata, Factory JSONL transcript files,
Windsurf Cascade AES-256-GCM encrypted protobuf trajectory files,
Grok Build ACP updates.jsonl + summary.json session directories, grok-cli
grok.db databases, Freebuff Desktop per-project SQLite desktop-v2.db
(threads + messages) databases, OMP JSONL tree entries with a fixed-width
title line, and T3 Code's SQLite databases. The ten standard formats convert in
all 90 directions, and T3 Code adds 20 more.
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.
Contributing
- 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
Development
python -m compileall session_sdk unisessions tests
python -m unittest discover -s tests -v
136 tests covering conversion shape, compaction extraction and emission, dry-run
safety, assistant usage estimation, OpenCode JSON shape, custom session
directories, path encoding, every provider's extraction and conversion, OMP reads
and writes (branch selection, title slot, /clear), thinking and tool calls across
all targets, Windsurf Cascade steps, Grok Build and grok-cli stores, Freebuff
Desktop stores, T3 Code reads and writes, trace formats (STS, OpenAI, ShareGPT),
the conversion options, update mode, export-all, search behavior and index
persistence, and dedup edge cases.
Links
- X/Twitter: @ImVibhek
- Website: vibheksoni.com
- Security Blog: opendoors.wtf
License
MIT
Metadata
Release files for unisessions 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| unisessions-0.5.0.tar.gz | 158.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unisessions-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 267.9 kB
Release files / unisessions-0.5.0.tar.gz
| Download URL | unisessions-0.5.0.tar.gz |
|---|---|
| Size | 158.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5d9c30a1b80121c6b628ab2f7f492c6a0d25e7f964a0c85c77889ac1bf01099
|
|
BLAKE2b-256 checksum How to use checksums |
4176dd23178956e60275ca9ea70e4d765bcfc8d46ae2167d6f41768592756050
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / unisessions-0.5.0-py3-none-any.whl
| Download URL | unisessions-0.5.0-py3-none-any.whl |
|---|---|
| Size | 109.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d9d5ab6f3e479b43ee09009ad5a1ac003a748e44ca6133d06e77f46560f0d15b
|
|
BLAKE2b-256 checksum How to use checksums |
a4d2a685ea893c98fd6b31f2dbc3b4d43247488e3d8f4aa6a75f64d4d85c7aad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logSecurity for unisessions 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for unisessions-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.