# second-brain

A universal memory layer for AI coding agents. Ingests conversation history from Claude Code, Cursor, and other tools into a graph-backed knowledge store with vector search, then serves it via MCP (Model Context Protocol) so any AI agent can recall what you've discussed, decided, and built across projects and machines.

## Why

Every AI coding tool keeps its own conversation history locked in its own format. Claude Code stores JSONL in `~/.claude/projects/`, Cursor keeps JSONL in `~/.cursor/projects/`. None of them talk to each other, and none of them let you search across sessions. If you discussed an architecture decision with Claude last week and ask Cursor about it today, you start from zero.

second-brain fixes this by creating a single knowledge graph that ingests from all your AI tools, embeds everything for semantic search, tracks relationships between memories (reinforces, contradicts, supersedes), and exposes it all through MCP so any connected agent can recall context from any previous conversation.

## Architecture

```
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│ Claude Session 1 │  │ Claude Session 2 │  │ Claude Session 3 │
│  (MCP server)    │  │  (MCP server)    │  │  (MCP server)    │
│  stdin/stdout    │  │  stdin/stdout    │  │  stdin/stdout    │
└────────┬────────┘  └────────┬────────┘  └────────┬────────┘
         │ HTTP               │ HTTP               │ HTTP
         └────────────────────┼────────────────────┘
                              │
                    ┌─────────▼─────────┐
                    │ second-brain-api   │  ← daemon (systemd)
                    │ port 7200          │
                    │ owns: KuzuDB       │
                    │ owns: Embedder     │
                    │ owns: File watcher │
                    └─────────┬─────────┘
                              │
               ┌──────────────┼──────────────┐
               ▼              ▼              ▼
          ┌────────┐   ┌──────────┐   ┌──────────┐
          │ KuzuDB │   │BGE-small │   │  Sync    │
          │ (graph)│   │(384-dim) │   │(SSH+JSONL)│
          └────────┘   └──────────┘   └──────────┘
```

KuzuDB takes an exclusive file lock, so only one process can open the database. The daemon owns it and all MCP servers, CLI commands, and sync operations go through HTTP.

## Memory Pipeline

1. **File watcher** detects new `.jsonl` in `~/.claude/projects/` (10s debounce)
2. **Hash check** skips unchanged files
3. **Adapter** parses tool-specific format into conversation chunks
4. **Heuristic filter** (`is_memorable`) rejects noise: tool output, short lines, code-only blocks
5. **Dedup** checks first 128 chars against existing memories
6. **Embed** generates 384-dim BGE vector (local)
7. **Store** writes to KuzuDB with conversation and entity relations
8. **Consolidate** (manual) distills raw memories via LLM into concise knowledge facts, then cleans up raw

## Crates

| Crate | Description |
|-------|-------------|
| `second-brain-core` | KuzuDB storage, BGE-small-en-v1.5 embeddings, QueryEngine, schema types |
| `second-brain-adapters` | Parsers for Claude Code JSONL and Cursor JSONL formats |
| `second-brain-sync` | SSH transport, JSONL change log export/import, conflict resolution |
| `second-brain-mcp` | Thin MCP stdio server, proxies to daemon over HTTP, auto-starts daemon if needed |
| `second-brain-cli` | CLI binary for setup, ingest, recall, sync, consolidation |
| `second-brain-api` | HTTP daemon: owns DB, embedder, file watcher, serves all endpoints on port 7200 |

## Install

```bash
cargo install second-brain-cli second-brain-api second-brain-mcp
second-brain-cli setup
```

`setup` handles everything: creates `~/.second-brain/`, installs a systemd user service, configures Claude Code MCP in `~/.claude/settings.json`, starts the daemon, and runs the first ingest.

Or build from source:

```bash
git clone https://github.com/Anyesh/second-brain.git
cd second-brain
cargo build --release
./target/release/second-brain-cli setup
```

## Usage

### Ingest conversations

```bash
# Ingest Claude Code history
sb ingest claude-code

# Ingest Cursor history
sb ingest cursor

# Ingest everything
sb ingest all

# Re-ingest from scratch
sb reingest --clean
```

Ingest reads from standard paths (`~/.claude/projects/` for Claude Code, `~/.cursor/` for Cursor), parses conversations, extracts memories with types (episodic, semantic, procedural), generates 384-dim embeddings, and stores everything in KuzuDB with entity extraction and relationship tracking.

### Recall

```bash
sb recall "how does authentication work in the API"
sb recall "what database did we choose and why"
```

Recall uses a weighted scoring function: 50% vector cosine similarity, 30% graph relevance (relation types and counts), 20% recency (exponential decay + access frequency). Results below a 0.59 similarity threshold are filtered out.

### MCP server

`sb setup` configures this automatically. Manual config if needed:

```json
{
  "mcpServers": {
    "second-brain": {
      "command": "/path/to/second-brain-mcp",
      "env": {
        "SECOND_BRAIN_API": "http://127.0.0.1:7200",
        "SECOND_BRAIN_DAEMON_BIN": "/path/to/second-brain-api"
      }
    }
  }
}
```

The MCP server auto-starts the daemon if it isn't running.

MCP tools available:

| Tool | Description |
|------|-------------|
| `recall` | Semantic search with graph-boosted scoring |
| `remember` | Store a new memory with embedding |
| `ingest` | Trigger conversation ingestion |
| `associate` | Create explicit relations between memories |
| `forget` | Remove a memory by ID |
| `context` | Get a memory with its graph neighborhood |
| `status` | DB stats: memory count, index health, sync state |

### Sync

Bidirectional sync between machines over SSH using a sequence-numbered JSONL change log.

```bash
# Full bidirectional sync with a peer
sb sync user@remote-host

# Manual export/import
sb sync-export --after-seq 0 > changes.jsonl
cat changes.jsonl | sb sync-import

# Check sync state
sb sync-status

# Backfill sync log for data that predates the log
sb sync-backfill
```

Sync resolves conflicts with last-write-wins on memory updates (newer `last_accessed` wins). Conversations, entities, and relations use create-or-skip semantics.

### Consolidation

Distill raw conversation memories into higher-level summaries using a local LLM (Ollama).

```bash
sb consolidate                                        # default: 50 memories, concurrency 3
sb consolidate --limit 200                            # process more
sb consolidate --model gemma-4-E4B-it-Q4_K_M          # default model
sb consolidate --llm-url http://gpu-server:8081        # OpenAI-compatible endpoint
sb consolidate --concurrency 3                         # parallel LLM requests
sb consolidate --dry-run                               # preview without writing
```

Uses any OpenAI-compatible chat completions endpoint (llama.cpp, Ollama, vLLM). After consolidation, raw memories are automatically cleaned up. Distilled memories are the primary knowledge store, linked to sources via `DistilledFrom` relations.

### Daily Pipeline

End-of-day automation: consolidate memories via local LLM.

```bash
sb daily-pipeline                            # consolidate memories
sb daily-pipeline --llm-url http://localhost:8081 --model my-model
```

**Systemd timer** (auto end-of-day at 23:00):

```bash
cp config/daily-pipeline.{timer,service} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now daily-pipeline.timer
```

**Requirements:**
- Local LLM at `localhost:8081` (llama.cpp, Ollama, vLLM) or pass `--llm-url`
- Default model: `gemma-4-E4B-it-Q4_K_M` (override with `--model`)

### Memory Types

Memories are classified at ingest time (heuristic, no LLM):

| Type | Trigger patterns |
|------|-----------------|
| Task | TODO, FIXME, action item, remaining, need to |
| Decision | decided, chose, went with, agreed, selected |
| Architecture | architecture, data model, schema, the approach |
| Debugging | root cause, the bug, the issue is, error: |
| Question | contains "?" + interrogative word |
| Procedural | steps:, to do this, run, install, how to |
| Episodic | deployed, shipped, migrated, found that |
| Semantic | catch-all factual knowledge |

Priority order: first match wins (Task → Decision → ... → Semantic).

## Configuration

| Env var | Default | Description |
|---------|---------|-------------|
| `SECOND_BRAIN_DB` | `~/.second-brain/graph.kuzu` | Path to KuzuDB database |
| `SECOND_BRAIN_API` | `http://127.0.0.1:7200` | Daemon URL (used by MCP server and CLI) |
| `SECOND_BRAIN_BIND` | `127.0.0.1:7200` | Daemon listen address |
| `SECOND_BRAIN_DAEMON_BIN` | auto-detected | Path to daemon binary (used by MCP auto-start) |
| `RUST_LOG` | - | Tracing filter (e.g. `info`, `second_brain_mcp=debug`) |

## Query scoring

The QueryEngine combines three signals:

- **Vector similarity (weight: 0.5)**: cosine distance between query embedding and memory embedding using BGE-small-en-v1.5 (384 dimensions)
- **Graph relevance (weight: 0.3)**: based on relation types connected to the memory. Reinforces (+0.3), RelatesTo (+0.2 * strength), DistilledFrom (+0.15), Mentions (+0.1), DerivedFrom (+0.05), Contradicts (-0.1), Supersedes (-0.2)
- **Recency (weight: 0.2)**: exponential decay over 30 days plus a logarithmic access frequency boost

Weights are configurable via `QueryEngine::with_weights(vector, graph, recency)`.

## Graph schema

Node types: `Memory`, `Entity`, `Conversation`, `IngestLog`, `ConsolidationLog`, `SyncLog`, `SyncState`, `WikiExportLog`

Relation types:
- `RELATES_TO` (Memory -> Memory): semantic relationship with strength
- `REINFORCES` (Memory -> Memory): one memory strengthens another
- `CONTRADICTS` (Memory -> Memory): conflicting information with resolution
- `SUPERSEDES` (Memory -> Memory): newer information replaces older
- `DISTILLED_FROM` (Memory -> Memory): consolidation link
- `DERIVED_FROM` (Memory -> Conversation): provenance tracking
- `MENTIONS` (Memory -> Entity): entity extraction links

## Dependencies

- [KuzuDB](https://kuzudb.com/) 0.11 for the graph database (embedded, single-file)
- [fastembed](https://github.com/Anush008/fastembed-rs) 5 for BGE-small-en-v1.5 embeddings (384-dim, runs locally, no API calls)
- Rust 1.85+

## License

MIT
