Architecture
This document explains how Lore captures, stores, and links AI coding sessions.
Overview
Section titled “Overview”┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│ AI Tools │ │ Lore │ │ Git Repo ││ │ │ │ │ ││ Claude Code │────▶│ Watchers │ │ .git/ ││ Aider │ │ (parsers) │ │ ││ Gemini CLI │ │ │ │ │ ││ Continue.dev │ │ ▼ │ │ ││ Cline │ │ SQLite DB │◀───▶│ Commits ││ ... │ │ (sessions, │ │ │└─────────────────┘ │ messages, │ └─────────────────┘ │ links) │ └─────────────────┘Components
Section titled “Components”Watchers
Section titled “Watchers”Each supported AI tool has a dedicated watcher that knows how to:
- Find session files on disk
- Parse the tool’s specific format (JSONL, JSON, Markdown)
- Extract messages with roles, timestamps, and content
- Convert to Lore’s internal data model
Watchers implement a common trait:
pub trait Watcher { fn info(&self) -> WatcherInfo; fn is_available(&self) -> bool; fn find_sources(&self) -> Result<Vec<PathBuf>>; fn parse_source(&self, path: &Path) -> Result<Vec<(Session, Vec<Message>)>>; fn watch_paths(&self) -> Vec<PathBuf>;}New tools can be supported by implementing this trait. For VS Code extensions using Cline-style storage, use the generic VsCodeExtensionWatcher which requires only a configuration struct. See the Contributing guide for details.
Storage
Section titled “Storage”Lore uses SQLite for all persistent storage:
~/.lore/├── lore.db # SQLite database├── config.yaml # User configuration└── logs/ # Daemon logsDatabase Schema
Section titled “Database Schema”sessions - Core session data
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| tool | TEXT | Tool name (e.g., “claude-code”) |
| tool_version | TEXT | Tool version if available |
| model | TEXT | AI model used |
| started_at | DATETIME | Session start time |
| ended_at | DATETIME | Session end time |
| working_directory | TEXT | Project directory |
| git_branch | TEXT | Active branch |
| message_count | INT | Number of messages |
| source_path | TEXT | Original file path (for dedup) |
| machine_id | UUID | Machine identifier |
messages - Conversation content
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| session_id | UUID | Foreign key to sessions |
| parent_id | UUID | For threaded conversations |
| role | TEXT | user, assistant, system |
| content | JSON | Message content (text, tool use, etc.) |
| timestamp | DATETIME | Message time |
| message_index | INT | Order in conversation |
session_links - Commit associations
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| session_id | UUID | Foreign key to sessions |
| commit_sha | TEXT | Git commit SHA |
| link_type | TEXT | commit, branch, remote |
| confidence | FLOAT | Auto-link confidence (0-1) |
| created_by | TEXT | user, auto, hook |
Additional tables: tags, annotations, summaries, machines, messages_fts (full-text search)
Full-Text Search
Section titled “Full-Text Search”Message content is indexed using SQLite FTS5:
CREATE VIRTUAL TABLE messages_fts USING fts5( content, content='messages', content_rowid='rowid');This enables fast full-text queries across all sessions:
lore search "authentication middleware"Daemon
Section titled “Daemon”The background daemon uses:
- notify crate for file system events
- tokio async runtime for concurrent watching
- Unix socket for IPC with CLI commands
┌─────────────────────────────────────────────────┐│ Daemon ││ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ Watcher │ │ Watcher │ │ Watcher │ .. ││ │ claude │ │ aider │ │ gemini │ ││ └────┬─────┘ └────┬─────┘ └────┬─────┘ ││ │ │ │ ││ └─────────────┼─────────────┘ ││ │ ││ ▼ ││ ┌────────────┐ ││ │ Importer │ ││ └─────┬──────┘ ││ │ ││ ▼ ││ ┌────────────┐ ││ │ Database │ ││ └────────────┘ │└─────────────────────────────────────────────────┘The daemon:
- Watches directories for all enabled tools
- Debounces rapid file changes
- Incrementally parses only new content
- Updates session git_branch when it changes
MCP Server
Section titled “MCP Server”The MCP (Model Context Protocol) server exposes Lore data to AI tools:
┌─────────────────┐ stdio ┌─────────────────┐│ Claude Code │◀──────────────▶│ Lore MCP ││ (or other │ JSON-RPC │ Server ││ MCP client) │ │ │└─────────────────┘ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Database │ └─────────────────┘Tools exposed via MCP:
lore_search- Full-text searchlore_get_session- Get session by IDlore_list_sessions- List with filterslore_get_context- Recent sessions for repolore_get_linked_sessions- Sessions for a commit
Lore syncs reasoning history over git, not through a hosted service. There is no server. Encrypted sessions travel through the git remotes you already use.
There are two stores:
┌──────────────────────────┐ ┌──────────────────────────┐│ Per-repo store │ │ Global personal store ││ │ │ ││ project/.git │ │ ~/.lore/sync (git repo) ││ refs/lore/sessions │ │ synced to a private ││ (this repo's sessions)│ │ remote you configure ││ over the repo's remote│ │ (all your sessions) │└───────────┬──────────────┘ └────────────┬─────────────┘ │ │ ▼ ▼ encrypt (AES-256-GCM) encrypt (AES-256-GCM)- Per-repo store: Sessions for a project live inside that project’s own git repository under
refs/lore/sessions, a ref outsiderefs/headsthat is never checked out. It carries only that repo’s sessions and rides on the repo’s existing remote, so reasoning travels with the code. - Global personal store: A standalone git repo at
~/.lore/sync, synced to a private remote. It aggregates all of a user’s sessions across every tool and repo for backup and cross-project search.
The sync operation is fetch, merge, encrypt, push. Merges are newer-wins per session. Each session is gzipped then encrypted with AES-256-GCM using a key derived from a passphrase via Argon2id. The git host only ever sees ciphertext plus a minimal plaintext metadata file per session (id, tool, timestamps, message count, machine id, branch, and deliberately no file paths).
Sync runs from the pre-push git hook (best-effort, never blocks the push) or when the user runs lore sync. The daemon is not involved in sync.
Data Flow
Section titled “Data Flow”Import Flow
Section titled “Import Flow”1. User runs: lore import2. Registry returns enabled watchers3. Each watcher scans for session files4. Parser converts tool format → internal model5. Deduplication check (by source_path)6. Insert session + messages into database7. FTS index updated automaticallyLink Flow
Section titled “Link Flow”1. User runs: lore link abc123 --commit HEAD2. Resolve session ID prefix → full UUID3. Resolve git ref → commit SHA4. Create session_link record5. Link appears in: lore show, lore blame, MCPSearch Flow
Section titled “Search Flow”1. User runs: lore search "auth"2. Query FTS5 index with filters3. Rank results by relevance4. Group by session5. Format output (text/json)Sync Flow
Section titled “Sync Flow”1. User runs: lore sync (or git push fires the pre-push hook)2. Fetch the store ref/repo from the remote3. Decrypt and merge sessions not already in the local DB (newer wins)4. Encrypt unsynced local sessions (gzip -> AES-256-GCM)5. Push the updated store back to the remoteDesign Principles
Section titled “Design Principles”- Local-first: All data stays on your machine; sync is opt-in and encrypted end to end
- Tool-agnostic: Same data model regardless of AI tool
- Git-integrated: Sessions are meaningful in the context of commits
- Incremental: Daemon only processes new content
- Queryable: Everything is searchable and filterable