Lossless session switching between Claude Code, Codex, and Pi.
Each of those tools stores its session context in its own native JSONL shape. This repo converts between them without dropping information, so you can hot-swap a session from one tool to another and keep working.
git clone https://github.com/sshkeda/lossless-agent-context
cd lossless-agent-context
bun install# Pi -> Claude Code conversion
bun src/cli/index.ts convert session.jsonl --to claude-code -o claude.jsonl
# Claude Code resume-safe seed
# Also writes claude-seed.jsonl.lossless.json when recovery metadata is needed.
bun src/cli/index.ts prepare-claude-code-resume session.jsonl --from pi -o claude-seed.jsonl
# stdin piping, source auto-detected from the first JSONL line
cat session.jsonl | bun src/cli/index.ts convert - --to codex
# Explicit source
bun src/cli/index.ts convert session.jsonl --from pi --to codexProviders: pi | claude-code | codex. --from is auto-detected when omitted.
raw native JSONL ↔ canonical event model ↔ raw native JSONL
(pi/claude/codex) (pi/claude/codex)
The canonical model is append-only and provider-agnostic. Exporters take canonical events back to any of the three native shapes.
When going cross-provider (e.g. Pi → Claude Code), exporters embed foreign native line envelopes as __lac_foreign / __lac_canonical fields when the target provider can safely carry them. Claude Code resume seeds are stricter than generic conversion JSONL, so prepare-claude-code-resume writes an adjacent recovery sidecar at <file>.jsonl.lossless.json. Keep that file next to the JSONL when converting back; the CLI reads it automatically for file-based Claude Code imports.
src/cli/—lac convertCLIsrc/core/— canonical event schemasrc/adapters/— Pi / Claude Code / Codex JSONL importers + exporterstest/e2e/— fixture-driven integration tests
- Pi JSONL ↔ canonical
- Claude Code JSONL ↔ canonical
- Codex JSONL ↔ canonical
- Cross-provider export (e.g. Pi → Claude Code) with lossless
__lac_foreign/__lac_canonicalcarry-through - Deterministic recovery sidecars (
*.lossless.json) for transforms that provider JSONL cannot safely carry directly, such as demoted reasoning markers - Native Codex response items including messages, reasoning, function/custom tool calls, web search calls, and image generation calls
- Semantic Pi → Claude Code export validated by the real
@anthropic-ai/claude-agent-sdk—getSessionMessagesparses the converted output and returns the original Pi user/assistant chain
bun run check # typecheck + eslint + prettier
bun run test # portable fixture-driven test suite
bun run verify:portable # check + test in series (what CI proves)Before cutting a release from a machine that has the local CLIs and session stores available, also run the real-log gate:
bun run test:real-logstest:real-logs reads recent sessions from ~/.pi/agent/sessions, ~/.claude/projects, and ~/.codex/archived_sessions, validates target-native output at each hop, and checks byte-identical same-provider round-trips. Override picked files with LAC_REAL_PI_SESSION / LAC_REAL_CLAUDE_SESSION / LAC_REAL_CODEX_SESSION.
Strict native fidelity:
provider -> lac -> other format -> lac -> providermust round-trip back to the original native session bytes.- This applies uniformly to Claude Code, Codex, and Pi.
- If a rebuilt provider session is not byte-for-byte identical to the original native session, that is a bug in lac.
- Provider-specific workarounds that rely on replaying preserved raw files instead of reconstructing them are not the intended end state — reconstruction itself must be lossless.
Current gap: Claude resume seeds are not yet natively lossless. Real experiments showed Claude Code can reject synthetic resume seeds with API Error: 400 due to tool use concurrency issues once enough historical tool_result pairs are preserved. The required fix is exact native fidelity in reconstruction: Claude -> lac -> pi -> lac -> Claude must produce the same native Claude bytes, including historical tool execution state.
MIT © 2026 Stephen Shkeda