# create-adapter > Create conversation adapters for importing AI chat history from different tools (Claude Code, Cursor, Warp, Codex, etc.). Covers the adapter.Adapter interface, caching strategies, incremental parsing, watch/FD management, and performance standards. Use when creating a new adapter, modifying adapter behavior, or debugging adapter performance issues. See references/ for Cursor DB and Warp SQLite schema details. - Author: Guy MANDINA - Repository: guyghost/sidecar - Version: 20260210000132 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-09 - Source: https://github.com/guyghost/sidecar - Web: https://mule.run/skillshub/@@guyghost/sidecar~create-adapter:20260210000132 --- --- name: create-adapter description: > Create conversation adapters for importing AI chat history from different tools (Claude Code, Cursor, Warp, Codex, etc.). Covers the adapter.Adapter interface, caching strategies, incremental parsing, watch/FD management, and performance standards. Use when creating a new adapter, modifying adapter behavior, or debugging adapter performance issues. See references/ for Cursor DB and Warp SQLite schema details. --- # Create Adapter ## Why Performance Matters Adapters are the largest performance risk in Sidecar. Conversations refresh on watch events in a hot path that runs continuously during active sessions: ``` watch event -> coalescer -> session refresh -> adapter.Sessions() -> metadata parsing ``` If an adapter does full directory scans and full-file reparses on every change, CPU and FD usage spike quickly. ## Reference Adapters Study these before writing a new adapter: - `internal/adapter/claudecode` - Incremental JSONL parsing, targeted refresh - `internal/adapter/codex` - Directory cache, two-pass metadata parsing, global watch scope - `internal/adapter/cursor` - SQLite/WAL-aware cache invalidation, FD-safe DB access ## Required Interface All adapters implement `adapter.Adapter`: ```go type Adapter interface { ID() string Name() string Icon() string Detect(projectRoot string) (bool, error) Capabilities() CapabilitySet Sessions(projectRoot string) ([]Session, error) Messages(sessionID string) ([]Message, error) Usage(sessionID string) (*UsageStats, error) Watch(projectRoot string) (<-chan Event, io.Closer, error) } ``` ### Required Session Fields Every session from `Sessions()` must set: - `ID`, `Name` - `AdapterID`, `AdapterName`, `AdapterIcon` - `CreatedAt`, `UpdatedAt` - `MessageCount`, `FileSize` `FileSize` is used for dynamic debounce and huge-session auto-reload protection. ### Path and Watch Strategy Set `Session.Path` only when Sidecar should use tiered file watching for that adapter: - **File-based append-only** (JSONL/log): set `Path` to absolute file path - **DB/WAL adapters** (Cursor): prefer adapter-specific `Watch()` with WAL-aware invalidation; do not set `Path` unless tiered watching covers your write surface ## Performance Standards ### 1) Cache metadata and messages aggressively Minimum cache keys: - Metadata: `path + size + modTime` - Messages: `path + size + modTime` - SQLite/WAL: include WAL size+mtime in the key Use bounded LRU behavior. Prune stale paths. ### 2) Incremental parsing for append-only formats For JSONL/event-log adapters: - Cache last parsed byte offset - Parse only appended bytes - Fall back to full parse on shrink/rotation/corruption - Preserve immutable head metadata from prior parse ### 3) Two-pass metadata for large files When incremental metadata parse is impractical: - Head pass: ID, CWD, first user message, first timestamp - Tail pass: latest timestamp, token totals - Skip middle of large files ### 4) Avoid repeated expensive path work Resolve project path once per `Sessions()` call (`Abs`/`EvalSymlinks`), reuse for all matches. ### 5) Return defensive copies from caches Never return cache-owned slices/maps directly. Copy message/session structures to avoid mutation bugs. ### 6) Keep DB access FD-safe For SQLite adapters: - Open read-only (`mode=ro`) - `SetMaxOpenConns(1)`, `SetMaxIdleConns(0)` - Close rows and DB handles promptly - Avoid multiple DB connections per `Messages()` call ## Watching and FD Management ### 1) Prefer directory-level watches Do not watch per-session files when directory-level watch gives equivalent signals. ### 2) Implement watch scope If adapter watches a global path (same location regardless of worktree): ```go func (a *Adapter) WatchScope() adapter.WatchScope { return adapter.WatchScopeGlobal } ``` This prevents duplicate watchers across worktrees. ### 3) Always emit SessionID when known Watch events should include session ID for targeted refresh (avoids full reloads). ### 4) Debounce and non-blocking sends - Debounce bursty write events - Use buffered channels - Non-blocking sends: `select { case ch <- evt: default: }` ### 5) Ensure cleanup All watcher paths must close cleanly on plugin stop. No goroutine or FD leaks. ## Message and Content Rendering Adapters must provide rich structured content for Conversation Flow UI. ### Required message mapping Map source records to: - `Message.Role`, `Message.Content`, `Message.ContentBlocks` - `Message.ToolUses` (legacy compatibility) - `Message.ThinkingBlocks` (if available) - `Message.Model` when available ### Tool linking rule Use consistent `ToolUseID` for `tool_use` and `tool_result` blocks. If incremental parsing is used, preserve pending tool-link state across cache updates. ## Optional Interfaces ### TargetedRefresher ```go type TargetedRefresher interface { SessionByID(sessionID string) (*Session, error) } ``` Reduces refresh from O(N sessions) to O(1). Implement when adapter can resolve a session directly. ### ProjectDiscoverer Implement when source format allows discovery of sessions beyond current git worktrees. ## Error Handling - `Detect()`: return `(false, nil)` for missing data directories - `Sessions()`: skip corrupt/unreadable entries and continue; hard-fail only on systemic errors - `Messages()`: return `nil, nil` for missing session files; fail on parse errors - `Watch()`: return `(nil, nil, err)` when watch setup fails ## Benchmark Targets New adapters should meet these performance targets: - `Messages()` full parse (~1MB): under 50ms - `Messages()` incremental append: under 10ms - `Messages()` cache hit: under 1ms - `Sessions()` on 50 session files: under 50ms ## Testing Requirements Required tests for every new adapter: - Relative vs absolute project path behavior in `Detect()`/`Sessions()` - `Sessions()` sorted by `UpdatedAt desc` - Required session fields populated (`Adapter*`, `FileSize`, `Path` when applicable) - Cache hit behavior (no reparsing on unchanged files) - File growth behavior (incremental parse path) - File shrink/rotation behavior (fallback full parse) - Tool use/result linking (including incremental append cases) - Watcher event emission includes `SessionID` - Watcher cleanup (no leaked closers) Run tests: ```bash go test ./internal/adapter/ -run . go test ./internal/adapter/ -bench . -benchmem ``` ## PR Compliance Checklist ### A) Correctness - [ ] Full `adapter.Adapter` contract implemented - [ ] `Sessions()` sets required identity and timestamp fields - [ ] `FileSize` populated for every session - [ ] `Path` strategy explicit and correct for adapter type - [ ] Message role/content mapping correct - [ ] `ContentBlocks` include text/tool/thinking data - [ ] Tool result linking correct (`ToolUseID` parity) ### B) Performance - [ ] Metadata cache implemented and bounded - [ ] Message cache implemented and bounded - [ ] Incremental parse or two-pass strategy implemented - [ ] No repeated `Abs/EvalSymlinks` in per-session loops - [ ] No duplicate parsing for single-pass data - [ ] Benchmarks added with realistic fixtures ### C) FD / Watching - [ ] Directory-level watches preferred - [ ] Global adapters implement `WatchScopeProvider` - [ ] Watch events include `SessionID` - [ ] Debounce + buffered + non-blocking send pattern - [ ] DB adapters account for WAL in invalidation/watch - [ ] Watchers and goroutines close cleanly ### D) Integration - [ ] Adapter registered via `register.go` and main import - [ ] Search uses adapter `Messages()` path - [ ] Large-session behavior validated (`FileSize`-driven) ## Schema References See `references/cursor-db-format.md` for Cursor's per-session SQLite database structure (Merkle tree blobs, hex-encoded metadata, WAL considerations). See `references/warp-sqlite-schema.md` for Warp's single SQLite database structure (ai_queries, agent_conversations, blocks tables, protobuf tasks).