One import command can connect several tools, but their histories need different interpretation rules. Codex supplies events, Trae stores records in workspace databases, and Copilot requires distinguishing JSON objects from JSONL snapshots. Detecting a source establishes that the adapter found familiar data, not that it preserved every part of the history.
This article checks three adapters in ChatCrystal at commit 7642768. These are application rules for known storage shapes, not public export protocols that the tools promise to keep stable.
Configure the server’s source directories
| Source | Default location | Override points to |
|---|---|---|
| Codex | ~/.codex/sessions/ |
CODEX_SESSIONS_DIR: sessions directory |
| Trae | Platform-specific Trae/User/workspaceStorage/ |
TRAE_DATA_DIR: User directory |
| Copilot | Session directories under platform-specific Code/User/ |
COPILOT_DATA_DIR: User directory |
The platform roots for Trae and Code use %APPDATA% on Windows, ~/Library/Application Support on macOS, and ~/.config on Linux. The current Copilot adapter looks for Code by default; this does not establish automatic discovery for every VS Code derivative.
Configuration code loads these variables when the server process starts. For an override, set the variable for that startup process. Setting it only before a client import command does not change paths already loaded by a running server. Leave defaults in place when they match your data.
Codex: event extraction and limited deduplication
The Codex adapter recursively finds rollout-*.jsonl, skipping files smaller than 50 bytes. It reads session IDs, working directories, and Git branches from session_meta, and extracts user and assistant text from event_msg records.
This constructed example illustrates fields; it is not a real imported record:
{
"type": "event_msg",
"timestamp": "2026-10-01T10:00:00Z",
"payload": { "type": "user_message", "message": "Why is this request running twice?" }
}
When user text contains the exact marker ## My request for Codex:\n, the code keeps the request after it. Other text is not automatically recognized as IDE context through a general rule.
Assistant output_text from response_item can also become a message. If its combined text exactly matches the most recent assistant message, that duplicate is skipped. This is not semantic deduplication across the conversation. User and developer messages in this path are skipped. Tool calls mainly mark the most recent assistant message as using tools; this does not establish complete archival of tool results.
The title index is actually ~/.codex/session_index.jsonl, alongside sessions/. The current code caches this index, and CODEX_SESSIONS_DIR does not relocate its path. If content imports but a title is absent, investigate the index location separately.
Trae: prefer direct content, then task content
The Trae adapter requires both workspace.json and state.vscdb. It reads sessions in list from the ItemTable key memento/icube-ai-agent-storage.
Nonempty assistant content takes priority and returns no thinking. Only the fallback through agentTaskContent combines proposal text with thoughts from finish steps and collects the corresponding reasoning fields. If there are no body fragments, guideline thought can supply text. “All reasoning is retained” is therefore inaccurate.
Messages are processed by turnIndex. When time fields are unreliable, an increasing synthetic time preserves ordering. Useful order does not make those times original event timestamps. Compare content, roles, and order during acceptance; do not interpret millisecond differences as measured duration. Trae and Copilot workspace paths also use simple file:/// stripping and decoding, which can lose the POSIX root slash. Do not assume imported paths are usable absolute paths.
Copilot: the first-line snapshot is not patch replay
The Copilot adapter scans workspace chatSessions/ and global globalStorage/emptyWindowChatSessions/. It accepts .json and .jsonl, skipping files smaller than 100 bytes.
A JSON file is read as one object. For JSONL, the adapter reads only the first line and handles a kind: 0 snapshot wrapper. It does not search for the first nonempty record or replay later patches. Imported content therefore cannot automatically be treated as the complete conversation state at the file’s end. Check the original when the first line is empty, invalid, or not a supported snapshot.
User text comes from message.text; this path does not extract message.parts. Assistant responses supply text and thinking by kind; tool items set a flag. A response with only tool items and no body or thinking does not produce an assistant message. customTitle can become the imported title, and assistant time is request time plus one millisecond for ordering.
Accept each source against a known conversation
With the default local connection:
crystal import
crystal --json conversations --source codex --limit 20
crystal --json conversations --source trae --limit 20
crystal --json conversations --source copilot --limit 20
List records are under items in the JSON response. Select an actual conversation ID per source and use application details or GET /api/conversations/<id> to compare the original user request, assistant conclusion, project, and message order. Growing totals do not substitute for content inspection. Local import does not reliably restrict scanning through the CLI --source option; see the Claude Code import boundaries.
Also include a missing directory, a malformed first JSONL line, or a response containing only tool items to check failure and retention rules. Verify automatic watching separately; successful manual import does not establish it. Then continue with note generation and retrieval. For database source locations, see the Cursor import guide.