This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Go binary and local MCP server that gives Claude Code persistent, queryable memory across sessions. Memories are stored in Qdrant as hybrid dense+sparse vectors (configured local provider for dense, BM25 for sparse). Claude retrieves them before proposing designs so it doesn't re-suggest previously rejected approaches.
make build # builds ./lore binary
make install # go install to $GOPATH/bin
make test # go test ./... (unit tests only, no services needed)
make test-integration # integration tests using testcontainers (requires Docker)
make vet # go vet ./...
make tidy # go mod tidy
# Run a single test
go test ./tests/ -run TestScrub -v
go test ./tests/ -run TestIntegration_UpsertSearchSupersede -tags integration -v
# Local services
make run-qdrant # docker compose up -d (qdrant on :6333/:6334)
make stop-qdrant
make doctor # checks Qdrant + embedding-provider connectivity
# Quick end-to-end smoke test (requires run-qdrant + ollama pull nomic-embed-text)
make smoke- Write path:
lore add/memory_recordMCP tool → scrub secrets → dense embed (configured provider) + sparse embed (BM25) → QdrantUpsertWithSparse - Read path:
lore search/memory_searchMCP tool → embed query → Qdrant hybrid search (RRF fusion) → recency boost → return top-k - Ingest path:
lore ingest-session <transcript>→ parse JSONL → scrub → chunk → Anthropic extracts memories as JSON → cosine dedup gate → upsert
| Package | Role |
|---|---|
internal/model/memory.go |
Memory struct; Kind (decision/rejection/pattern/session/note); Source (manual/claude-session/commit/pr-review); SparseVector |
internal/config/config.go |
Viper config from ~/.config/lore/lore.yaml and LORE_* env vars |
internal/store/qdrant.go |
Qdrant REST client: UpsertWithSparse, Search (hybrid/dense), Scroll, GetByID, UpdatePayloadFields |
internal/embed/ollama.go |
OllamaEmbedder — dense vectors, auto-probes dimension |
internal/embed/openai.go |
OpenAI-compatible embeddings for LM Studio, oMLX, llama.cpp, and compatible servers |
internal/embed/bm25.go |
Stateless tokenizer and hash-based sparse vectorizer |
internal/embed/corpus.go |
In-memory corpus-aware scorer used only by benchmarks |
internal/ingest/session.go |
JSONL transcript parser + Anthropic-based memory extractor + cosine dedup |
internal/ingest/commit.go |
Git log parser extracting Why:/Decision:/Reject: marker lines |
internal/scrub/scrub.go |
Regex scrubber for 7 secret patterns (applied before sending to Anthropic and on all MCP inputs) |
internal/llm/anthropic.go |
Raw-HTTP Anthropic client used by the ingestor |
internal/server/mcp.go |
MCP server over stdio — 5 tools: memory_search, memory_record, memory_list_recent, memory_forget, memory_supersede |
internal/cli/root.go |
Cobra root; loadRuntime (config+embedder+Store) vs loadStoreOnly (config+Store, no embedder) |
internal/git/root.go |
RootOrCwd, RemoteOriginURL, RepoID (stable repo identifier: SHA256 of remote URL or abs path) |
setup, serve, init [--reset], doctor, add, search, list, forget, supersede, reindex, prune, snapshot, ui, ingest-session, ingest-commits [--since <ref>], ingest-pr, export, import, migrate-repo <old-id> <new-id>, bench, recall-bench, embed-pending [--all]
Commands that only read/delete (forget, list, export, migrate-repo) use loadStoreOnly — they never fail when the embedding provider is unavailable. Commands that write new memories use loadRuntime, which probes the configured provider first.
Named-vector format (Phase 2+): dense (model dimension, cosine) + sparse (BM25). Phase 1 used an unnamed single vector — detected and rejected with a suggestion to run lore init --reset.
SupersededBy == "" is the active-memory invariant. All search filters include this check; memory_supersede sets it on the old record and creates a new one with a fresh UUID.
Write path: if dense embed fails, the memory is still written with pending_embed=true and a sparse-only vector. lore embed-pending (and embed-pending --all) backfills dense vectors when the provider is back.
Read path: memory_search falls through with an empty dense vector and the store's sparse-only branch in Search returns results without RRF fusion. Only errors when both dense embed and sparse vectorization fail.
git.RepoID returns "remote:<16-hex-chars-of-SHA256(origin-url)>" when a remote origin exists, otherwise the abs path. Use this (not cwd) when storing memories for stable cross-rename identity. Existing path-based memories can be migrated with lore migrate-repo.
Config file: ~/.config/lore/lore.yaml (see examples/lore.yaml). Notable env var overrides: LORE_QDRANT_HOST, LORE_ANTHROPIC_API_KEY.
Add to Claude Code's MCP config (see examples/mcp.json):
{"mcpServers": {"lore": {"command": "lore", "args": ["serve"]}}}hooks/session-end.sh is a SessionEnd hook for Claude Code. Install via ~/.claude/settings.json. It receives the transcript via $TRANSCRIPT_PATH and runs lore ingest-session; failures are non-fatal (exit 0).
- Unit tests (
tests/store_test.go,embed_test.go,ingest_test.go,commit_test.go) — no live services, run withgo test ./... - Integration tests (
tests/integration_test.go) — require Docker, tagged//go:build integration, spin up Qdrant via testcontainers. Run withmake test-integration. - Integration tests use 4-dim stub embeddings (all-zeros) — no embedding server needed.
You have access to memory_search, memory_record, memory_list_recent,
memory_forget, and memory_supersede tools backed by the Lore MCP server.
- Before proposing any non-trivial design, architecture choice, library
selection, or refactor, call
memory_searchwith the most relevant query for what you are about to suggest. - If a result has
kind="rejection"and matches your approach, do NOT suggest it. Surface the rejection reason and propose an alternative. - If a result has
kind="decision"and is relevant, treat it as binding context — align your suggestion with it. - When the user makes a new design decision, rejects an approach, or says
"remember this", call
memory_recordwith the appropriatekind.
- Search with concrete nouns + a verb (e.g. "auth middleware token validation").
- Use
decisionfor chosen approaches,rejectionfor discarded ones,patternfor conventions,notefor free-form context. repodefaults to the current git root; set it explicitly only for cross-project queries.a