@ersss/dsh-migrate
Import chat history and memory from other AI agents (Claude Code, Codex CLI, Gemini CLI, Aider, Cline, OpenCode, Hermes Agent, OpenClaw) into DeepSeek Harness
AI Analysis
核心用途是跨平台迁移对话上下文,支持导入 Claude Code、Aider、Cline 等多种主流工具的会话记录。适合需要更换客户端但不想丢失已有项目调试上下文的开发者。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:kkkkkkkkki/dsh-migrateREADME
Read the full README ↗dsh-migrate
English | 中文
Import chat history and memory from other AI agents into DeepSeek Harness.
Switching harnesses should not mean losing your context. dsh-migrate reads the transcripts and memory stores other agents leave on disk and writes them as native DeepSeek Harness session logs — searchable, inspectable in the Trajectory view, and resumable (--fork-style continue) as if they had been recorded by DSH itself.
It works two ways: a /migrate slash command inside DSH, and a standalone CLI (npx @ersss/dsh-migrate) that scans the whole machine and previews with a dry run before writing anything.
Supported sources
| Agent | Sessions | Memory |
|---|---|---|
| Claude Code | ~/.claude/projects/**.jsonl (text, thinking, tool_use/tool_result) | ~/.claude/CLAUDE.md, project memory/*.md |
| Codex CLI | ~/.codex/sessions/**/rollout-*.jsonl (messages, reasoning, function calls) | ~/.codex/AGENTS.md, instructions.md |
| Gemini CLI | ~/.gemini/tmp/**/chats/session-*.json (messages, thoughts, toolCalls) | ~/.gemini/GEMINI.md |
| Cursor | ~/.cursor/projects/*/agent-transcripts/*/*.jsonl (text, tool_use; results live in Cursor's bubble store and are not recorded) | — |
| Kimi CLI | ~/.kimi/sessions/*/*/wire.jsonl (text, thinking, tool calls; cwd recovered via kimi.json) | — |
| ChatGPT (web export) | conversations.json from the data export (DAG-mapped threads; tool messages folded to text) | — |
| Cline / Roo Code | VS Code globalStorage/*/tasks/*/api_conversation_history.json | — |
| OpenCode | ~/.local/share/opencode/** message/part JSON files | — |
| Aider | **/.aider.chat.history.md (bounded scan) | CONVENTIONS.md files |
| Hermes Agent | ~/.hermes/state.db (SQLite sessions+messages, read-only) | ~/.hermes/memories/MEMORY.md, USER.md |
| OpenClaw | ~/.openclaw/agents/*/agent/openclaw-agent.sqlite (transcript_events, read-only) | workspace MEMORY.md, memory/*.md |
Every adapter is tolerant: malformed lines and unknown row types are skipped, never fatal. Missing fields degrade gracefully (unknown model → imported, unknown cwd → the _no-cwd project).
Install
# 1. Install the plugin into your DSH web profile
dsh plugin --profile web add @ersss/dsh-migrate
# 2. Restart DSH
dsh web
The package declares a dsh.bundle patch layer, so installing it does two things at once:
- registers the
/migrateslash command, and - points DSH's session store at
$DSH_HOME/sessions-importedin raw-JSONL mode — a dedicated root for imported logs that never mixes with the default store's encoding.
Heads-up: the bundled patch relocates the default session root, which means sessions you recorded before installing the plugin stay in the old
sessions/root and stop being listed. If you have existing DSH sessions you still want listed, either merge the patch's two rows into$DSH_HOME/profiles/web/cordis.patch.ymland keep both roots by alternating--patch, or simply run the CLI with--outpointed at your live root (raw JSONL reads fine under a zstd root only if the root was created withcompression: 'none'). Fresh installs are unaffected.
Use
From inside DSH (Web UI, TUI, headless):
/migrate list # show what each agent has on disk
/migrate run # import everything
/migrate run --source claude-code --limit 10
From a terminal, without DSH running (also the right choice for big archives):
npx @ersss/dsh-migrate list
npx @ersss/dsh-migrate run # → $DSH_HOME/sessions-imported
npx @ersss/dsh-migrate run --out /path/to/root # anywhere you like
npx @ersss/dsh-migrate run --write-instructions . # also fold memory into ./AGENTS.md
Imported sessions appear in the sidebar grouped by their original project directory, and in the Trajectory view with their full event stream — prompts, reasoning, tool calls, and results.
What gets written
DSH's session log is an append-only JSONL event stream (SESSION_FORMAT_VERSION 0). For every source session, dsh-migrate writes one log:
$DSH_HOME/sessions-imported/--
--//session.jsonl
{"type":"session","version":0,"id":"…","createdAt":…,"delegationDepth":0,"cwd":"…"}
{"type":"turn/start","seq":0,"time":…,"data":{"turn":1}}
{"type":"user/message","seq":1,…,"data":{"role":"user","source":{"kind":"user","via":"dsh-migrate:claude-code"},"content":[{"type":"text","text":"…"}]},"surfaceOp":"append"}
{"type":"step/start",…} {"type":"assistant/message",…} {"type":"tool/call",…} {"type":"tool/result",…} {"type":"step/end",…}
{"type":"turn/end",…,"data":{"turn":1,"reason":{"kind":"completed"}}}
…
{"type":"session/end-seed",…} ← marks the log as imported seed history
Mapping decisions, briefly:
- Turns. One source user prompt (plus the assistant work it triggered) becomes one
turn/start…turn/endbracket. Tool-result-only user rows (Claude Code, Cline) attach to the pendingtool/callinstead of opening a turn. - Messages. User and system text become
user/messageevents; assistant text/reasoning/tool-calls become oneassistant/messageper step; tool results becometool/resultpaired by call id. - Provenance, not impersonation. Imported assistant messages keep the original provider and model (
provider: "dsh-migrate:anthropic", model: "claude-fable-5"). Imported user messages arekind: "user"— required for the trajectory view and history derivation — with the importer declared on the merge-extensibleviachannel (via: "dsh-migrate:claude-code"). - Timestamps and ids. Original timestamps are preserved when the source records them (monotonicity enforced); missing call ids get fresh UUIDs; session ids are fresh UUIDs (never the source's) so an import can never shadow a live session. The header carries
importedSource/importedSourceId, so imported sessions are filterable without walking the event stream. - Memory. Each source's memory records become one "Imported memory" session (readable, searchable, resumable), and
--write-instructionsadditionally folds them into that project'sAGENTS.mdbetween `` markers (idempotent re-runs). - Append-only honesty. Writes use
wx(no overwrite). A colliding id is retried with a fresh one; existing logs are never modified.
How resume works
The trailing session/end-seed event makes the whole imported log seed history: when you continue an imported session, DSH replays the log as inherited context, then starts a clean live segment — the model sees the prior conversation exactly as DSH would have logged it. The agent preset and tools of the current runtime apply; tools the old agent had and DSH does not simply become history.
Notes & limitations
- Read-only on sources. The importer never writes to the source agents' directories.
- Images and attachments are not imported (DSH attachments are content-addressed host objects); their surrounding text is.
- Compaction history in source transcripts is flattened to plain messages.
- Source-specific gaps, by design of the source format: Cursor transcripts don't record tool results (calls import without results); ChatGPT exports have no structured tool calls (tool messages become text); Kimi CLI sub-agent internals mirrored as
SubagentEventare skipped (importsubagents//wire.jsonldirectly if you want them). - SQLite sources are read immutably (
mode=ro&immutable=1), so a running Hermes/OpenClaw gateway is never disturbed. OpenClaw imports everysession_window; resets/rollover windows appear as separate sessions (they were separate transcripts upstream too). - Aider scan roots are
$DSH_MIGRATE_SCAN(path-list),~/projects,~/code,~/dev,~/work, and a shallow$HOMEsweep — its history files live inside projects, so a fixed home-relative path cannot find them. - Format drift: these layouts are undocumented internals of the source agents and change over time. Adapters skip what they do not recognise; if your agent version is newer, please open an issue with a (redacted) sample row.
Development
npm install
npm run build # tsc → dist/
npm test # fixture-driven end-to-end tests (real DSH codec replay)
The test suite replays produced logs through the actual @deepseek-ai/dsh-session codec (decodeStorageRecord + foldSurface) vendored from a live DSH profile, so format regressions fail loudly.
Contributing a source adapter
One adapter = one file in src/formats/ implementing:
interface SourceAdapter {
key: string
label: string
detect(home: string): Promise
readSessions(home: string, options?: { limit?: number }): Promise
readMemories(home: string): Promise
}
Register it in src/formats/index.ts and add a fixture test. See src/formats/claude-code.ts for the reference implementation.
License
MIT