basic-memory import claude conversations reads the claude.ai account data export (conversations.json). Claude Code — the CLI — writes something different and unrelated: one JSONL transcript per session, at
~/.claude/projects/<cwd-slug>/<session-id>.jsonl
There is no importer for that format, and two things make it worth one:
- Claude Code deletes those transcripts. Retention is
cleanupPeriodDays in ~/.claude/settings.json, 30 days by default. So the record of how a decision was reached expires on a rolling basis while the decision stays.
- The conversations are substantial. One machine here: 131 transcripts, 158 MB. A single session's transcript can reach 15 MB.
Shape of the format
One JSON object per line. type distinguishes the record; across 131 transcripts these appeared:
| type |
what it is |
user |
a turn. message.content is either a string (what the user typed) or a list of tool_result blocks |
assistant |
a turn. message.content is a list of text, thinking and tool_use blocks |
system |
harness notices |
attachment |
injected file/context payloads |
ai-title |
the session's generated title (aiTitle), written repeatedly |
continued-in |
names the session id this one was resumed into (continuedInSessionId) |
file-history-snapshot, file-history-delta |
editor state |
queue-operation, cost-state, last-prompt, mode, permission-mode, bridge-session, atis-latch, agent-setting, agent-name |
housekeeping |
Useful fields on the turn records: sessionId, timestamp, cwd, gitBranch, isMeta, isSidechain, parentUuid.
Four things an importer has to get right
These are the traps a naive reader falls into. All four were hit while writing a local converter, and each one is invisible rather than loud.
-
Conversation text is a small minority of the bytes. tool_use and tool_result blocks dominate. Importing them wholesale imports tool output — which is where credentials surface — and buys little as memory. A messages-only default seems right, with tool content opt-in. On the history above, 158 MB of transcript reduced to 1.2 MB of notes once tool traffic was dropped.
-
A resumed session replays its parent. The transcript of a resumed session contains the whole earlier conversation under the new session id, so importing every file stores the same conversation once per resume. On one chain here: 15, then 17, then 27 user turns, the last file holding all of them. continued-in gives the forward link, so the tail of a chain is the one to keep.
-
ai-title is inherited by a resumed session, so it is not a unique title. On one history it collided on 13 notes of 30 — same title, same start time, overlapping content.
-
Subagent transcripts are separate files named agent-*.jsonl, and subagent turns inside a normal transcript carry isSidechain: true. Here, 91 of 131 files were subagent transcripts — the bulk of the bytes, and not conversation.
There is also a fifth, which is about output rather than input: a whole session can be too large for an MCP client to read back. read_note on a 136,000-character note is refused by the client for exceeding its tool-result token budget, which makes the note unsearchable in practice — search returns a snippet and the note itself cannot be opened. Some form of splitting, or a paged read, matters more for this source than for a chat export. (Measured against one client: returned in full at 45,110 characters, refused at 62,645.)
What already exists
I have a converter along these lines running as a SessionEnd hook in a Basic Memory wrapper project — one note per session into its own project, with the filters above, emitting the same type: conversation frontmatter and ### Human (ts) / ### Assistant (ts) body that ClaudeConversationsImporter produces, so the two streams sit side by side. Happy to contribute it upstream in whatever shape suits the project — as import claude-code, or as a separate importer module.
basic-memory import claude conversationsreads the claude.ai account data export (conversations.json). Claude Code — the CLI — writes something different and unrelated: one JSONL transcript per session, atThere is no importer for that format, and two things make it worth one:
cleanupPeriodDaysin~/.claude/settings.json, 30 days by default. So the record of how a decision was reached expires on a rolling basis while the decision stays.Shape of the format
One JSON object per line.
typedistinguishes the record; across 131 transcripts these appeared:usermessage.contentis either a string (what the user typed) or a list oftool_resultblocksassistantmessage.contentis a list oftext,thinkingandtool_useblockssystemattachmentai-titleaiTitle), written repeatedlycontinued-incontinuedInSessionId)file-history-snapshot,file-history-deltaqueue-operation,cost-state,last-prompt,mode,permission-mode,bridge-session,atis-latch,agent-setting,agent-nameUseful fields on the turn records:
sessionId,timestamp,cwd,gitBranch,isMeta,isSidechain,parentUuid.Four things an importer has to get right
These are the traps a naive reader falls into. All four were hit while writing a local converter, and each one is invisible rather than loud.
Conversation text is a small minority of the bytes.
tool_useandtool_resultblocks dominate. Importing them wholesale imports tool output — which is where credentials surface — and buys little as memory. A messages-only default seems right, with tool content opt-in. On the history above, 158 MB of transcript reduced to 1.2 MB of notes once tool traffic was dropped.A resumed session replays its parent. The transcript of a resumed session contains the whole earlier conversation under the new session id, so importing every file stores the same conversation once per resume. On one chain here: 15, then 17, then 27 user turns, the last file holding all of them.
continued-ingives the forward link, so the tail of a chain is the one to keep.ai-titleis inherited by a resumed session, so it is not a unique title. On one history it collided on 13 notes of 30 — same title, same start time, overlapping content.Subagent transcripts are separate files named
agent-*.jsonl, and subagent turns inside a normal transcript carryisSidechain: true. Here, 91 of 131 files were subagent transcripts — the bulk of the bytes, and not conversation.There is also a fifth, which is about output rather than input: a whole session can be too large for an MCP client to read back.
read_noteon a 136,000-character note is refused by the client for exceeding its tool-result token budget, which makes the note unsearchable in practice — search returns a snippet and the note itself cannot be opened. Some form of splitting, or a paged read, matters more for this source than for a chat export. (Measured against one client: returned in full at 45,110 characters, refused at 62,645.)What already exists
I have a converter along these lines running as a
SessionEndhook in a Basic Memory wrapper project — one note per session into its own project, with the filters above, emitting the sametype: conversationfrontmatter and### Human (ts)/### Assistant (ts)body thatClaudeConversationsImporterproduces, so the two streams sit side by side. Happy to contribute it upstream in whatever shape suits the project — asimport claude-code, or as a separate importer module.