Portable AI conversation layer: a canonical schema for agent conversations and a migration CLI that moves transcripts between tools.
| Package | What it is |
|---|---|
@kontourai/thread |
Canonical Zod schema for AI conversations — messages, tool calls and results, reasoning, attachments, token usage. |
@kontourai/ferry |
Importers/exporters plus the ferry CLI: convert transcripts from Claude Code, Codex, OpenCode, and ChatGPT exports into the canonical format, or out to API/message formats. |
Every agent tool writes conversations in its own shape, and none of them can
read each other's. @kontourai/thread defines one validated, serializable
representation; @kontourai/ferry does the translation — so switching tools,
archiving sessions, or replaying a conversation against a provider API stops
being a bespoke parsing project.
# Inspect any supported transcript (format auto-detected)
ferry inspect ~/.claude/projects/<project>/<session>.jsonl
# Convert a Codex rollout to canonical thread JSON
ferry convert ~/.codex/sessions/2026/08/01/rollout-*.jsonl -o session.thread.json
# One row per tool call, across a whole corpus, straight into a query engine
ferry rows ~/.claude/projects/**/*.jsonl ~/.codex/sessions/**/*.jsonl > calls.jsonl
duckdb -c "SELECT source, tool, count(*) FROM 'calls.jsonl' GROUP BY 1,2 ORDER BY 3 DESC"
# Token usage grouped by harness
ferry usage ~/.codex/sessions/**/*.jsonl --by source --json
# Re-export a thread as an Anthropic Messages API body
ferry convert session.thread.json --to anthropic-messages
# Everything supported
ferry formatsProgrammatic use:
import { importFromClaudeCode, exportToAnthropicMessages } from "@kontourai/ferry";
import { threadToJson } from "@kontourai/thread";
const thread = importFromClaudeCode(jsonl);
const body = exportToAnthropicMessages(thread);Input: claude-code (session JSONL), codex (rollout JSONL), opencode
(opencode export JSON), kiro (CLI session JSONL), pi (session JSONL),
muse (Muse Code muse export JSON), chatgpt-export (conversations.json),
thread.
Output: thread (canonical JSON), openai-chat, anthropic-messages,
gemini, markdown.
Each adapter documents its fidelity limits (what a format cannot represent) in its source header, and importers report skipped/unparseable records through a warning callback (surfaced on stderr by the CLI) instead of losing them silently.
When a source transcript records enough anchors, Ferry assigns each imported tool result a deterministic namespaced SHA-256 identity and retains only source-declared terminal status. It never turns call IDs, timestamps, output text, or JSON-looking prose into result identity or standing. API exports declare when those fields cannot cross the target boundary.
pnpm install
npm run verify # build + typecheck + test in both packagesThe pnpm version is pinned in package.json (packageManager), and the
workspace members are listed in pnpm-workspace.yaml. Dependency install
scripts are blocked by default; a package that needs one is named there under
allowBuilds, pinned by version.
Importers are tested against fixtures that mirror the exact shapes the source tools write (field casing, split assistant events, sidechains, noise records), and conversions of multi-hundred-MB real transcripts are supported by streaming JSONL input.
Apache-2.0