docs/history-import.md

History import

Picasso can bring in past sessions from Claude Code, Codex and OpenCode as read-only threads. The code is packages/sessions/src/history-import* (@picasso/sessions/history-import), shared by the CLI, the TUI and the Mac helper.

Where imported threads are stored, and why

Imported threads are written as engine session logs, not as Picasso's own local session journal (~/.picasso/sessions/*.jsonl):

threads the Mac started.

The engine log is the only format both surfaces already read, so an imported thread shows in both with no new reader.

Each import is <DSH_HOME>/sessions/<project-key(cwd)>/<id>/session.v4.jsonl.zstd (<DSH_HOME> is the engine home, ~/.picasso/dsh-home by default), written the way the engine writes logs: a header frame followed by a body frame, each one a checksummed zstd frame. The header holds only the engine's header keys. The id is a UUID-shaped hash of source:sourceId, so it is the same on every run. The log uses stock event types only:

Tool calls are summarised as a list at the end of the turn's assistant text (Tool calls (n): - \Bash\ pnpm test). No tool/call records are written, so continuing a thread on another model never replays another tool's calls. Reasoning and tool output are not imported. The record shapes were checked against the pinned engine's adoptSessionEvent, validateStoredEvents and format-catalog header reader. A test runs adoptSessionEvent when the package is installed.

Sources (read-only)

SourceLocationNotes
Claude Code$CLAUDE_CONFIG_DIR/projects/*/*.jsonl (default ~/.claude/projects)Subagent transcripts, sidechain and meta records are skipped. The title is custom-title, else ai-title, else summary, else the first prompt.
Codex$CODEX_HOME/sessions/**/rollout-*.jsonl (default ~/.codex/sessions)Prompts come from event_msg:user_message, or from user response_items minus injected instructions in older rollouts. Thread names come from session_index.jsonl. Subagent threads (parent_thread_id) are skipped.
OpenCode$XDG_DATA_HOME/opencode, ~/.local/share/opencode, or ~/Library/Application Support/opencodeReads opencode.db (session_v2 + session_message, then the older session + message + part) opened read-only, plus the legacy JSON `storage/sessionmessagepart`. Only top-level sessions are imported.

Idempotency

<picassoHome>/imports/history.json records one entry per imported session: source, path, fingerprint (size+mtime, or the row's updated time), content hash, and the mtime of the log that was written. On a later run:

in which case it is left alone.

A session with no typed prompt is counted as "no prompts" and is not imported.

Read-only until continued

The relay adds imported: {source, label, importedAt} to GET /threads rows, and the TUI resume list shows an "Imported" badge. Both read the manifest.

gets the same turns without the import mark. The prompt runs there, and the imported thread stays as it was.

Nothing is imported without one click or keypress of consent. The answer is stored in the shared settings as settings.historyImport = {decision, sources, decidedAt} (imported, skipped, or later = offered again on a launch a day or more after). The last picker selection is kept in <picassoHome>/imports/selection.json.

expands a source: projects (most recent first, select all/none), a date filter (all, 30 days, 90 days, last year) and "skip very short sessions (≤ 1 prompt)". The footer shows live totals. Counts come from scanHistory (directory listings, mtimes, Codex's first line, OpenCode rows); scanHistoryDetails then reads the first 256 KB of each file for prompt counts and Claude Code's real project paths, cached per fingerprint in <picassoHome>/imports/scan-cache.json.

failed with the reason), the session being converted, an ETA, then "Imported N · skipped N · failed N — open with /resume". Failures can be retried (only those source paths are read again). The importer emits start, session, converted and done events; runImportJob folds them into the status (perSource, current, failures).

/import sessions) opens it any time; /import skills and /import <path> keep the skill import. The run is in-process; esc hides it into a header chip. No TTY, --json or -p: never prompts.

background picasso import --selection <json>); after picasso auth login it opens when due. picasso import [claude|codex|opencode|all...] [--dry-run] [--json] imports now; picasso import --status shows the last or running import (<picassoHome>/imports/status.json).

{sources, filters?}, POST /desktop/import/retry, POST /desktop/import/skip, POST /desktop/import/later, GET /desktop/import/status (live while the relay runs the import), and the older GET /desktop/import/sources`.