diff --git a/docs/memory-system/00-README.md b/docs/memory-system/00-README.md new file mode 100644 index 0000000..4cee99e --- /dev/null +++ b/docs/memory-system/00-README.md @@ -0,0 +1,39 @@ +# Memory System — Documentation Set + +A personal, cross-project memory system for Claude Code: lightweight, fast, tag-organized, +self-evolving, local-fast with lazy remote sync. Built for a multi-client freelancer who +wants the AI to learn things once and reuse them everywhere, without bloating project repos. + +## Read in this order + +1. **[01-video-synthesis.md](01-video-synthesis.md)** — Synthesis of the "6 Levels of Claude + Code Memory" video. What each level/tool is, where memory lives, how it's retrieved, and + the author's recommendations. Background reading; the source of many ideas below. +2. **[02-system-design.md](02-system-design.md)** — The architecture we settled on. Three + layers (episodic / knowledge / deferred-semantic), the knowledge-layer internals, data + model, hooks, sync, and guardrails. **Start here if you only read one.** +3. **[03-architecture-decisions.md](03-architecture-decisions.md)** — ADR log. Every decision + and *why*, including what we rejected and why (MemPalace, OpenBrain/Mem0, Postgres, + Recall/LightRAG) and what we deferred (semantic search over the vault). +4. **[04-build-plan.md](04-build-plan.md)** — How a human builds this, step by step. The + scripts, the hooks, the CRUD lifecycle, the AI's write/query conventions, and the + Claude Code plugin + skills that package it for global install. +5. **[05-handoff.md](05-handoff.md)** — Where we are, what's decided, what's open, and the + first concrete actions for the next session. + +## One-paragraph summary + +Two complementary memory types kept as **separate systems**: **episodic** ("what happened, +when") handled by **memsearch** (Milvus Lite, embedded, auto-captured session/journal notes), +and **semantic/knowledge** ("how do we…") handled by a **flat Obsidian markdown vault** that +is the single source of truth, organized by **namespaced nested tags** (`tool/`, `client/`, +`domain/`, `convention/`, `scope/`) rather than folders, and indexed by a small **Ruby + +Sequel + SQLite** tag index exposed through a CLI. Retrieval is **hook-injected and +on-demand** so project repos stay thin. Freshness is **lazy** (a write-time hook plus a +session-start reconcile — no daemon, no cron). Everything is markdown-as-truth and syncs to a +VPS via **git/Syncthing**; the indexes are disposable and rebuilt per machine. A meaning-based +search layer over the vault (**QMD**) is designed-in but **deferred** until structured tagging +proves insufficient. The whole thing ships as a **global Claude Code plugin with skills** so +every project knows how to use it. + +_Last updated: 2026-06-03_ diff --git a/docs/memory-system/01-video-synthesis.md b/docs/memory-system/01-video-synthesis.md new file mode 100644 index 0000000..8fc99ea --- /dev/null +++ b/docs/memory-system/01-video-synthesis.md @@ -0,0 +1,122 @@ +# Video Synthesis — The 6 Levels of Claude Code Memory + +Synthesis of the YouTube video "The 6 Levels of Claude Code Memory" plus the referenced +articles (John Connelly / Pawel Huryn). This is background: the source of several ideas in our +design. Our system borrows from Levels 1–3 and deliberately ignores 4–6. + +## The framing + +Every memory system answers the same question: **when you give Claude Code a task, how does it +pull the right context at the right time?** Each level differs on just two axes: + +- **Where memory lives** — the storage mechanism and file structure (markdown vs vectors, + local vs cloud). +- **How Claude gets it** — the retrieval stage (auto-injected into context, searched on + demand in a DB, etc.). + +The recurring enemy is **context rot**: as you load more context, the model recalls less of it +reliably. The cure throughout is **progressive disclosure** — load a small *index*, pull the +detail only when needed. + +## The levels + +### Level 1 — What ships natively (CLAUDE.md + memory.md / automemory) + +- **CLAUDE.md**: plain markdown in the project, always loaded every session like a system + prompt. Hierarchical (project-folder → root → individual project); lower levels inherit the + parent but local rules win on conflict. +- **Common mistake**: stuffing it full (brand guide, tone doc, client list) → burns context → + context rot. **Rule of thumb: keep CLAUDE.md under 200 lines**; push larger context into + separate files *referenced* from CLAUDE.md so they load only when needed. +- **memory.md / automemory**: `/memory` shows imported/project/user memory. Auto-memory keeps a + per-project `memory.md` that acts as an **index of pointers** to many small memory files — + Claude quietly takes notes in the background and builds the index. +- Anthropic is clearly working on this natively (leaked references to an unreleased always-on + consolidation daemon, "Chyros"). Native memory will only get better. + +### Level 2 — Forcing reliable recall (Connelly hook / Huryn structure) + +- Paste a memory-management prompt + rules into CLAUDE.md. Structured memory rooted at + `~/.claude/memory/`: `memory.md` (index), `general.md` (cross-project facts/prefs/env), + `domain/.md` (one file per topic), `tools/.md` (one file per tool, e.g. + `slack.md` with config/workarounds/edge cases). +- A **session-start hook** auto-injects the **index** (not full content) into every session and + sub-agent — not relying on Claude choosing to read it. +- A **"reorganize memory"** command periodically reads all memory files, removes + duplicates/outdated entries, merges related ones, splits overloaded files, sorts by date, + and rebuilds the index. +- Huryn's updated post (productcompass.pm) adds **active hypothesis tracking**, a **catalog of + "false beliefs,"** and an **AI-proposes-reorganization / human-keeps-editorial-control** + loop. The transferable mechanic (not the content-writing domain) is the hypothesis/ + false-belief log + propose-and-approve loop — useful for per-client "what we tried / decided + / what didn't work." +- **Author's take: most people should stop here.** + +### Level 3 — Search by meaning, not keywords (memsearch; OpenClaw template) + +- Add only if you've used Claude Code > 1 month, have many memory files, and have asked + something you *know* is in your notes that Claude couldn't find. +- **OpenClaw memory design (3 layers)**: (1) `memory.md` = long-term durable facts, loaded at + session start; (2) **daily note files** = one per date, a running log, today+yesterday + auto-loaded, older left on disk; (3) optional **"dreaming"** = background process that scores + daily notes and promotes recurring ones into long-term memory, forgetting stale stuff. +- **memsearch** (by Zilliz): ports this into Claude Code as a two-line plugin. Markdown-first, + same chunking/structure. Chunks into semantic vectors; a **user-prompt-submit hook + auto-injects the top-3 semantic matches** into every prompt. Plain-readable markdown. +- Alternative **claude-mem**: MCP-based, 3-tier storage (summaries/timeline/observations), + dashboard/team/cost features. Author's view: overkill, and MCP-based means Claude must + actively call the search tool; stores opaque blobs vs memsearch's readable markdown. + +### Level 4 — Verbatim conversation recall (MemPalace) + +- Local RAG, free, claims highest published benchmark. Stores words **verbatim** (nothing + summarized) indexed in a dense symbolic dialect ("memory palace": wings → rooms → closets → + drawers). Two DBs: SQL (entities/relationships) + Chroma (vector chunks). Background hooks on + session-end/pre-compaction. ~42ms retrieval. +- **Downsides**: storage is **not readable markdown**; drawers are **isolated** (knowledge not + interconnected); local-only. + +### Level 5 — Self-organizing knowledge base (Karpathy LLM wiki; Recall; LightRAG) + +- Karpathy's **LLM wiki**: `raw/` (you drop sources, Claude reads, never writes) + `wiki/` + (Claude owns entirely). Plain markdown, Obsidian graph. **Recall** is a hosted done-for-you + version (you don't own the data). **LightRAG** is enterprise knowledge-graph overkill. +- **Author's take**: these are for **content knowledge bases / deep research**, NOT operational + memory ("what did we decide about client X's landing page in March"). Skip for our use case. + +### Level 6 — One brain for all AI tools (OpenBrain; Mem0) + +- **OpenBrain**: memory in a **Postgres DB you own** (Supabase), one `thoughts` table (text + + embedding + tags + timestamp), MCP server fronting it so any AI tool shares the same memory. + Most portable/future-proof. **Downsides**: longer/harder setup; **always remote → latency on + every query**; small monthly cost. **Mem0**: cross-tool layer, fast setup, but data lives on + their servers permanently. +- **Author's take**: only if you live across many AI tools and want real-time shared memory. + +## Author's recommendation + +- Just starting → Level 1 done right. +- A bit in → Level 2 (Connelly hook). **Most should stop here.** +- Lots of context, losing old decisions → Level 3 (memsearch) or Level 4 (MemPalace). +- Levels 5–6 are a different realm for specific use cases. +- **Levels 1+2+3 stack** (similar folder structures). The author personally runs up to Level 3: + OpenClaw conventions + semantic search + injection hooks. + +## What we took / left (see ADRs for why) + +- **Took**: progressive-disclosure index (L1/L2), per-tool/per-domain granularity + reorganize + command + session hooks (L2), Huryn's propose-and-approve reorg loop, OpenClaw daily-notes + + dreaming and memsearch (L3). +- **Replaced**: L2's **folders** with **namespaced tags** in a flat vault. +- **Left**: MemPalace (opaque, isolated), Recall/LightRAG (content KB, not operational), + OpenBrain/Mem0 (always-remote, fights local-fast). + +## Reference links + +- John & Pawel's system — youngleaders.tech/p/how-i-finally-sorted-my-claude-code-memory +- Pawel Huryn (updated) — productcompass.pm/p/self-improving-claude-system +- memsearch — github.com/zilliztech/memsearch +- MemPalace — github.com/MemPalace/mempalace +- Karpathy LLM wiki — gist.github.com/karpathy +- Recall — recall.it · Mem0 — mem0.ai · OpenBrain — github.com/NateBJones-Project +- QMD (our deferred semantic layer) — github.com/tobi/qmd diff --git a/docs/memory-system/02-system-design.md b/docs/memory-system/02-system-design.md new file mode 100644 index 0000000..02c705f --- /dev/null +++ b/docs/memory-system/02-system-design.md @@ -0,0 +1,173 @@ +# System Design + +_Status: approved 2026-06-03. Implementation not yet started._ + +## Goals (what this system must do) + +1. **Thin projects** — keep as little AI context inside each project repo as possible. Projects + focus on project files; knowledge is pulled in on demand or injected by hooks. +2. **Cross-project / cross-client knowledge** — the AI learns something once (e.g. the SEMrush + API) and references it from anywhere. Two scopes: **global** (broadly useful) and + **project/client-specific** (how a given client uses a tool) — both globally reachable. + Ask anything client- or project-related from any project. +3. **Timeline awareness** — from any project, lightweight awareness of recent activity ("what + was I doing an hour ago / yesterday"), with the ability to drill deeper. +4. **Remote, local-fast** — accessible anywhere (VPS / personal OS) but runs local-fast; lazy + sync (minutes/hourly) is fine; real-time is overkill. + +Desired properties: **lightweight** (low tokens), **fast** (out of the way), **flexible** +(cross project/client), **self-evolving** (AI maintains it under clear rules), **easy to +manage** (AI-managed), **semi-structured** (organization that can evolve). + +## Core principle: two memory types, kept separate + +| Type | Question | Lifecycle | Write path | Our tool | +|------|----------|-----------|-----------|----------| +| **Episodic** | "What happened, when?" | accretes & decays | auto-captured | **memsearch** | +| **Semantic / knowledge** | "How do we…?" | deliberately maintained | curated by you/AI | **Obsidian vault + tag index** | + +This is the classic **episodic vs. semantic** memory split. Keeping them separate is the key +architectural decision — they have different lifecycles, write paths, and query patterns, and +forcing one tool to do both is what made every earlier design feel forced. + +## The three layers + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ EPISODIC ── memsearch (Milvus Lite, embedded, no Docker) │ +│ auto-captured session/journal notes · NL semantic recall │ +│ answers "when did we…", "what was I doing yesterday" │ +├─────────────────────────────────────────────────────────────────┤ +│ KNOWLEDGE ── flat Obsidian vault (single source of truth) │ +│ + Ruby/Sequel/SQLite tag index (CLI) │ +│ structured tag filtering · answers "how do we…", "what do we │ +│ know about X for client Y" │ +├─────────────────────────────────────────────────────────────────┤ +│ SEMANTIC-OVER-KNOWLEDGE ── QMD (SQLite+vector, MCP) [DEFERRED] │ +│ meaning-based recall over the vault when tags miss │ +│ add ONLY when structured tagging proves insufficient │ +└─────────────────────────────────────────────────────────────────┘ +``` + +All three are **local-first, markdown-as-truth, no Docker, no server, no API keys.** + +## Layer 1 — Episodic (memsearch) + +- **What it is**: a Claude Code plugin (by Zilliz) that auto-captures session notes as daily + markdown, chunks them, and stores a **shadow index** in **Milvus Lite** (a single embedded + file — no server, no Docker). Hybrid search = BM25 + dense vectors + RRF, local ONNX + embeddings (`bge-m3`, no API key/cost). A FileWatcher (1500ms debounce) handles updates and + deletions; markdown stays the source of truth. +- **Why off-the-shelf**: it already implements the OpenClaw daily-notes + "dreaming" pattern + and the markdown-as-truth / disposable-shadow-index philosophy we'd otherwise hand-build. +- **Role in our system**: satisfies Goal 3 (timeline). The AI queries it in natural language + ("what was decided about X last week"). We do **not** make it filter by our tags — it owns + the episodic corpus only. + +## Layer 2 — Knowledge (vault + tag index) + +The heart of the system, and the part we build. + +### Vault +- **Flat markdown directory**, single source of truth, **configurable location** (NOT forced + into `~/.claude/`; symlink if a tool insists). Browsable in Obsidian as a viewer. +- **Replaces project-local documentation**: instead of docs scattered per repo, knowledge + lives once in the vault and is pulled into any project on demand. + +### Frontmatter contract (every note) +```yaml +--- +summary: One line, written at creation. The router shows this so the AI can pick a + file without opening it. +tags: + - tool/semrush # namespaced, nested (slash = Obsidian nested tag) + - client/sesame3g + - domain/seo + - scope/project # or scope/global +--- +``` +- **Namespaces** are the "virtual indexes": `tool/`, `client/`, `domain/`, `convention/`, + `scope/`. `#tool` matches all children — native prefix filtering, no folders needed. +- **Two knowledge scopes** via `scope/global` vs `scope/project` (+ a `client/` tag): global = + broadly useful tool/domain knowledge; project = how a specific client uses it. Both are + globally queryable; the scope tag is the shortcut that avoids scanning every client's usage. + +### Index (the tag cache) +A small **Ruby program, Sequel ORM, SQLite** — the disposable structured cache over the vault. + +- **Schema**: + - `files(id, path, mtime, summary, scope)` — one row per note. + - `tags(id, name)` — one row per distinct tag (enables enumerating the vocabulary: + "what clients/tools do I have notes on?"). + - `files_tags(file_id, tag_id)` — `many_to_many` join. +- **CLI** (the only interface; the AI never touches SQLite directly): + - `index update --since ` — incremental: pulls `.md` files with `mtime >` last cache + time, re-reads their frontmatter, upserts; **also reconciles** (prunes rows whose path no + longer exists). + - `index update --rebuild` — full rebuild from scratch (default `false`). + - `index query --client X --tool Y [--scope global] [--domain Z]` — returns **path + + summary + matched tags** for each hit (decided: option C). Tags shown so the AI sees *why* + a file matched. + - `index tags --namespace tool/` — enumerate a virtual index. +- **Source of truth rule**: markdown is authoritative; the SQLite file is a rebuildable cache + that is **never synced** and can be deleted/rebuilt anytime. + +### Freshness (lazy — chosen Option A) +- **AI writes** → a `PostToolUse` hook on `Write`/`Edit` targeting vault `.md` files calls + `index update --file ` (updates exactly that file, prunes if deleted). Event-driven, + no polling, no staleness for AI edits. +- **Manual edits** (rare) → caught by a **session-start reconcile** (`index update --since` + + prune). **No daemon, no cron.** + +### Retrieval (hook-injected + on-demand) +- **Session-start hook** injects: (a) a compact index/overview, (b) the current project's + declared `convention/*` tags **resolved to files** (so coding conventions auto-pull and a + convention edit propagates to every project using that tag), (c) a pointer to recent + episodic journal. +- **On demand**: the AI runs `index query` to pull specific knowledge into context only when + the task needs it. Projects stay thin — their CLAUDE.md holds **tags/pointers**, not content. + +## Layer 3 — Semantic over knowledge (QMD) — DEFERRED + +- **What**: QMD (github.com/tobi/qmd) — local markdown search, **SQLite + FTS5/BM25 + local + vector embeddings (EmbeddingGemma-300M GGUF) + LLM rerank**. CLI + optional **MCP server**. + No Docker, no API keys. Proves SQLite + a local vector model is enough — no Milvus/Postgres + for knowledge. +- **Why deferred**: start structured-only. Add QMD as a **set-and-forget** semantic layer over + the vault **only when** we catch ourselves failing to retrieve notes we know exist (the + video's "only level up when it bites"). It complements, not replaces, the tag index (QMD + filters by path/collection context, not first-class frontmatter tags). + +## Timeline (Goal 3) details +- A **session-end hook** appends a daily journal note (one file per date) with pointers to the + project/knowledge files touched. memsearch indexes these; today+yesterday are cheap to load, + older entries are reachable by query for drill-down. + +## Self-evolution guardrails +- The AI **writes only to the vault**, never silently into project repos. +- **Required frontmatter schema** (summary + namespaced tags) is enforced so the index stays + queryable. +- **Daily notes are append-only**; consolidation/reorg is a **separate, reviewable step run in + plan mode** (Connelly's reorganize + Huryn's propose-and-approve loop). +- **Promotion to `scope/global`** requires a rule (e.g. a fact recurring N times) — not every + stray note gets promoted. + +## Sync (Goal 4) +- The **vault** syncs to the VPS via **git** (versioned history, hourly) or **Syncthing** + (continuous, zero-thought). Decision deferred to build time. +- **Indexes are never synced** — Milvus Lite and (later) QMD shadow indexes are rebuilt per + machine. Sync only the markdown. + +## Packaging +- The whole thing ships as a **global Claude Code plugin with skills** (hooks + scripts + + CRUD know-how) so every project, on every machine, knows how to use the vault effectively. + See [04-build-plan.md](04-build-plan.md). + +## How each goal is met + +| Goal | Met by | +|------|--------| +| 1. Thin projects | Knowledge in the vault, not repos; CLAUDE.md holds tags/pointers; on-demand `index query` | +| 2. Cross-project/client knowledge, global vs project scopes | Flat vault + namespaced tags + `scope/` + `client/`; enumerable virtual indexes | +| 3. Timeline | memsearch episodic layer + session-end journal hook | +| 4. Remote, local-fast | Markdown vault synced via git/Syncthing; disposable per-machine indexes | diff --git a/docs/memory-system/03-architecture-decisions.md b/docs/memory-system/03-architecture-decisions.md new file mode 100644 index 0000000..ff810d3 --- /dev/null +++ b/docs/memory-system/03-architecture-decisions.md @@ -0,0 +1,149 @@ +# Architecture Decision Records + +A running log of decisions and *why*. Format per entry: Context · Decision · Rationale · +Alternatives rejected · Status. Newest decisions extend the log; supersede rather than delete. + +--- + +## ADR-001 — Two memory types, kept as separate systems + +- **Context**: Earlier attempts to make one tool serve both "what happened" and "how do we do + X" felt forced (e.g. trying to make memsearch filter knowledge by tags). +- **Decision**: Model **episodic** memory and **semantic/knowledge** memory as two separate + systems with different tools. +- **Rationale**: They have different lifecycles (episodic accretes and decays; knowledge is + deliberately maintained), different write paths (auto-captured vs curated with guardrails), + and different query patterns ("when did we…" vs "how do we…"). Separation dissolves the + earlier integration tension entirely. +- **Alternatives rejected**: One unified store (memsearch-for-everything, or OpenBrain's single + `thoughts` table) — conflates the two and forces awkward filtering. +- **Status**: Accepted. + +## ADR-002 — memsearch for the episodic layer + +- **Context**: Need timeline/"what happened" memory (Goal 3) that's NL-queryable and lazy. +- **Decision**: Adopt **memsearch** (Zilliz) off-the-shelf for episodic memory. +- **Rationale**: It already implements the OpenClaw daily-notes + "dreaming" pattern and the + markdown-as-truth / disposable-shadow-index philosophy we'd otherwise build. Embedded + **Milvus Lite** (single file), hybrid BM25+vector+RRF search, local ONNX embeddings (no API + key/cost), a FileWatcher that handles deletions — **no Docker, no server**. Two-line install. +- **Alternatives rejected**: claude-mem (MCP-based — Claude must actively call search; opaque + blobs vs readable markdown; overkill features). Hand-building daily notes + dreaming + ourselves (reinventing a solved tool). +- **Status**: Accepted. + +## ADR-003 — Flat vault with namespaced tags, not folders + +- **Context**: Connelly/Huryn organize by folders (`tools/`, `domain/`). User wants a flat + Obsidian vault with tags as virtual indexes, and cross-cutting filters (client × tool × + convention). +- **Decision**: One **flat markdown vault**; organize via **namespaced, nested tags** + (`tool/`, `client/`, `domain/`, `convention/`, `scope/`). Slashes are valid Obsidian nested + tags, so `#tool` matches all children. +- **Rationale**: A note can carry several namespaces at once (`tool/semrush` + + `client/sesame3g` + `convention/react-ts`) — folders can't express that. Enables "filter by + client+tool to narrow the index." Enumerable virtual indexes ("what clients/tools exist"). +- **Alternatives rejected**: Folder hierarchy (single-axis; can't do cross-cutting filters). + Pure-prefix path filtering via memsearch `source_prefix` (would force directories back in). +- **Trade-off accepted**: Tags give the *human/Obsidian* free filtering, but the *AI* gets + nothing for free from tags — we must materialize them into a queryable index (see ADR-004). +- **Status**: Accepted. + +## ADR-004 — SQLite + Sequel (Ruby) tag index as the knowledge-layer cache + +- **Context**: The AI can't use Obsidian tags directly; tag filtering needs a machine-queryable + index. A previous `~/Documents/SecondBrain/` tag database was lost track of. +- **Decision**: A small **Ruby program using the Sequel ORM over SQLite**, exposed as a **CLI**. + Schema: `files(path, mtime, summary, scope)`, `tags(name)`, `files_tags` join + (`many_to_many`). The summary is a **column on `files`** (an attribute), not a join. +- **Rationale**: Normalized `tags` table makes enumerating the vocabulary a first-class cheap + query (the "virtual index" goal). The `summary` column is what turns the index from a + *finder* into a *router* — the AI sees enough to pick a file without opening it (progressive + disclosure, low tokens). Ruby + Sequel + CLI keeps the contract clean and the DB swappable; + the AI never touches SQLite directly. +- **Failure-mode guard (the lost-SecondBrain lesson)**: **markdown is always authoritative; the + SQLite file is a disposable cache** that is never synced and can be rebuilt from frontmatter + anytime (`index update --rebuild`). +- **Alternatives rejected**: Plain-markdown generated `INDEX.md` (must regenerate; grep-at-scale + is token-heavy). Frontmatter grep on demand (scales badly). Milvus/Postgres for knowledge + (overkill; QMD/memsearch prove SQLite is enough — see ADR-006/008). +- **Query output**: returns **path + summary + matched tags** (option C) — tags are cheap and + show *why* a result matched, useful for cross-client queries. +- **Status**: Accepted. + +## ADR-005 — Structured-first; semantic search over the vault deferred + +- **Context**: Tag filtering ("client/sesame3g + tool/semrush") may miss notes whose wording + doesn't match the query ("how do we use semrush" vs a note titled "search analytics + integration"). +- **Decision**: Ship the knowledge layer **structured-only** (tags + summaries). **Defer** + meaning-based search over the vault until it demonstrably bites. +- **Rationale**: Structured tagging is the lightweight/fast thing the user wants, and the + summary+tag design is built to make it work. Follow the video's "only level up when it bites." +- **Status**: Accepted (semantic deferred). + +## ADR-006 — QMD as the (deferred) semantic-over-knowledge layer + +- **Context**: When ADR-005's structured-only proves insufficient, we want a set-and-forget + semantic layer over the vault, local and Docker-free. +- **Decision**: Earmark **QMD** (github.com/tobi/qmd) for that role; do **not** install yet. +- **Rationale**: Local markdown search using **SQLite + FTS5/BM25 + local vector embeddings + (EmbeddingGemma-300M GGUF) + LLM rerank**; CLI + optional **MCP server**; no Docker, no API + keys. Validates that SQLite + a local vector model suffices (no Milvus/Postgres for + knowledge). Complements the tag index (QMD filters by path/collection context, not + first-class frontmatter tags), so it adds semantic recall without replacing structured + filtering. +- **Alternatives rejected**: Pointing memsearch at the vault (mixes episodic and knowledge + corpora; its filtering is path-prefix not tags). A bespoke embedding index (reinvents QMD). +- **Status**: Deferred / earmarked. + +## ADR-007 — Lazy freshness: write-hook + session-start reconcile, no daemon/cron + +- **Context**: The cache must reflect new/edited/deleted/renamed notes without becoming a + resource hog or going stale on renames. +- **Decision**: **Option A (lazy).** A `PostToolUse` hook updates the index on **AI** writes + (single-file, prunes on delete). **Manual** edits are caught by a **session-start reconcile** + (`index update --since` + prune of vanished paths). **No daemon, no cron.** +- **Rationale**: The AI is the primary writer, so write-time hooks give event-driven freshness + with no polling. The user rarely edits the vault by hand, so a session-start reconcile is + enough; a continuous `inotify` daemon (the `listen` gem) would add an always-on process to + manage/sync for negligible benefit. Matches the user's "lazy sync is fine" stance. +- **Alternatives rejected**: `inotify`/`listen` daemon (live freshness, but always-on process + to manage — unnecessary). Cron reconcile ("seems silly" per user; session-start covers it). +- **Status**: Accepted. + +## ADR-008 — Markdown-as-truth; sync the vault, not the indexes + +- **Context**: Must be accessible on a VPS / multiple machines but run local-fast (Goal 4). +- **Decision**: Sync the **markdown vault** to the VPS via **git or Syncthing** (choice deferred + to build time). **Indexes (Milvus Lite, future QMD) are rebuilt per machine and never + synced.** +- **Rationale**: Markdown is plain text — git/Syncthing sync it trivially; lazy (hourly or + continuous-async) is enough. Indexes are disposable caches; syncing binary DBs invites + conflicts for no gain. Local reads stay fast; ownership and portability stay with the user. +- **Alternatives rejected**: **OpenBrain / Mem0** hosted DBs — always-remote, adds per-query + latency and monthly cost, conflicts with local-fast; ownership weaker (Mem0 especially). + Only worth it for real-time cross-tool memory, which the user called overkill. +- **Status**: Accepted. + +## ADR-009 — Package as a global Claude Code plugin with skills + +- **Context**: Every project, on every machine, should know how to use the vault — write + conventions, query patterns, the hooks, and the CLI — without per-project setup. +- **Decision**: Ship hooks + scripts + CRUD know-how as a **global Claude Code plugin with + skills**, installed at the user level. +- **Rationale**: Skills carry the "when to write / what conventions / how & when to query" + guidance to the model; the plugin registers the session-start / session-end / PostToolUse + hooks and bundles the Ruby CLI. Global install = consistent behavior everywhere; single + source of truth for the conventions themselves. +- **Status**: Accepted (to be built — see 04-build-plan.md). + +## Rejected tools (summary) + +| Tool | Why rejected for our use | +|------|--------------------------| +| MemPalace (L4) | Storage not readable markdown; isolated drawers (knowledge not interconnected); fights self-managing + cross-linking goals | +| Recall / LightRAG (L5) | Content knowledge bases / deep research, not operational memory; Recall = hosted, you don't own data; LightRAG = enterprise overkill | +| OpenBrain / Mem0 (L6) | Always-remote DB → latency + cost; conflicts with local-fast lazy-sync; only pays off for real-time cross-tool memory (user: overkill) | +| Postgres / Milvus server | Unnecessary — SQLite (tag index) + Milvus Lite (memsearch) + QMD's SQLite cover everything locally with no Docker | +| claude-mem | MCP-based (Claude must call search); opaque blobs vs readable markdown; feature overkill | diff --git a/docs/memory-system/04-build-plan.md b/docs/memory-system/04-build-plan.md new file mode 100644 index 0000000..df620fd --- /dev/null +++ b/docs/memory-system/04-build-plan.md @@ -0,0 +1,155 @@ +# Build Plan + +How a human builds this system, step by step, and answers to the operational questions: +which scripts and hooks, how the AI knows when to write and what conventions to follow, how and +when it queries, the CRUD hooks, and how it's packaged as a global Claude Code plugin with +skills. + +> This is a build outline, not the implementation plan. The next session should turn this into +> a proper implementation plan (writing-plans skill) and execute it. + +--- + +## Part A — Build order (human builder's path) + +Build bottom-up: the vault and CLI first (usable standalone), then the hooks, then the plugin +that packages it. + +### Step 1 — Vault skeleton & conventions +- Decide the vault location (default: a synced home dir, e.g. `~/brain`; symlink into + `~/.claude/memory` only if a tool insists). **Vault is the single source of truth.** +- Write `CONVENTIONS.md` in the vault: the frontmatter contract and the tag namespaces + (`tool/`, `client/`, `domain/`, `convention/`, `scope/`). This doubles as the spec the + skills teach the AI. +- Seed a few real notes (e.g. `tool/semrush` global + a `client/` project note) to test + against. + +### Step 2 — The Ruby tag-index CLI (the core build) +- Ruby project, `sequel` + `sqlite3` gems. SQLite file in a cache dir (e.g. + `~/.cache/memory-index/index.sqlite`) — disposable, never synced. +- **Schema / migrations**: `files(id, path, mtime, summary, scope)`, `tags(id, name)`, + `files_tags(file_id, tag_id)`; `many_to_many` between files and tags. +- **Frontmatter parser**: read YAML frontmatter from a `.md` file → `{summary, scope, tags[]}`. + Fail loudly (or quarantine) notes missing the required `summary`/tags so the contract is + enforced. +- **Commands** (a thin CLI dispatch — `thor`/`optparse` or plain): + - `index update --since ` — find `.md` with `mtime >` last-cache-time, upsert + them; **reconcile**: delete rows for paths no longer on disk. + - `index update --file ` — upsert a single file (used by the write hook); prune if the + path is gone. + - `index update --rebuild` (default off) — drop & rebuild from a full vault scan. + - `index query --client X --tool Y [--domain Z] [--scope global]` — AND the filters; output + JSON lines of `{path, summary, tags[]}` (option C). + - `index tags [--namespace tool/]` — enumerate the vocabulary (virtual index). +- Store `last_cache_time` (a tiny meta table or a stamp file) so `--since auto` works. +- **Tests**: create/edit/delete/rename a note, assert the index reflects it; rebuild equals + incremental. + +### Step 3 — Hooks (maintenance + retrieval) +See Part C for exact mapping. Implement as small shell wrappers that call the Ruby CLI: +- `pre-tool-memory` style **PostToolUse** updater (write path → `index update --file`). +- **SessionStart** reconcile + inject (index overview + resolved `convention/*` files + journal + pointer). +- **SessionEnd** journal appender (daily note with pointers). + +### Step 4 — Episodic layer (memsearch) +- Install memsearch (`/plugin marketplace add zilliztech/memsearch`, then `plugin install + memsearch`), local to start. Verify daily memory files appear after a few conversations. +- Decide whether memsearch indexes our session-end journal notes or its own capture (likely its + own; our journal can point into the knowledge vault). + +### Step 5 — Sync +- Pick **git** (versioned, hourly push/pull) or **Syncthing** (continuous, zero-thought) for the + vault → VPS. Configure on each machine. **Do not sync the SQLite/Milvus caches.** + +### Step 6 — Package as a global plugin (Part D) +- Wrap Steps 2–3 into a Claude Code plugin with skills; install at user level. + +### Step 7 (deferred) — QMD semantic layer +- Only when structured-only retrieval misses notes you know exist: `qmd` over the vault, + optionally as an MCP server the AI can query. + +--- + +## Part B — The AI's write & query conventions (skills teach these) + +### When the AI WRITES to the vault +Trigger writes when the AI learns something **evergreen and reusable across projects** — not +project-ephemeral state (that's the episodic layer / project files). Concretely: +- It worked out how a tool/API behaves (e.g. SEMrush auth, rate limits, an endpoint quirk). +- It established a convention, decision, or preference that should apply beyond this task. +- It discovered a client-specific fact worth reusing (how *this* client uses a tool). + +**What conventions to follow when writing** (enforced by the frontmatter contract): +- One concept per note; keep notes small (the L1 "under ~200 lines / one topic" discipline). +- **Required frontmatter**: a one-line `summary` (written now, not deferred), `scope/global` + or `scope/project`, and namespaced tags (`tool/…`, `client/…`, `domain/…`, optionally + `convention/…`). +- **Scope rule**: default new tool/domain knowledge to `scope/global`; mark `scope/project` + + a `client/` tag when it's specific to how a client uses something. +- **Write only to the vault**, never silently into a project repo. +- **Promotion to global** (project → global) and **consolidation** are *not* done inline — + they happen in the reorganize step (Part C), in plan mode, for human review. + +### When & how the AI QUERIES +- **At session start**: the injected index overview + resolved `convention/*` files tell it + what's available without reading everything. +- **On demand, during a task**: when the task touches a tool/client/domain, run + `index query --tool semrush --client sesame3g` → get `{path, summary, tags}` → open only the + files whose summary matches. This is the progressive-disclosure loop that keeps tokens low. +- **Cross-client lookups**: query by `--tool` alone (omit client) to surface what was learned + with *any* client; tags in the output show provenance. +- **"What happened" questions** go to the **episodic layer (memsearch)** in natural language, + not the tag index. + +--- + +## Part C — Hooks & CRUD mapping + +CRUD over the knowledge vault, and which hook/command services each operation: + +| Operation | Trigger | Mechanism | +|-----------|---------|-----------| +| **Create** | AI writes a new note | AI writes `.md` (Write tool) → **PostToolUse** hook → `index update --file` | +| **Read / query** | AI needs knowledge | AI calls `index query …` (no hook; on-demand CLI) | +| **Update** | AI/user edits a note | AI edit → **PostToolUse** hook → `index update --file`; user edit → **SessionStart** reconcile | +| **Delete / rename** | note removed/renamed | covered by `--file` (prune) on AI ops; by **SessionStart** reconcile (prune vanished paths) on manual ops | +| **Inject** | new session/sub-agent starts | **SessionStart** hook injects index overview + resolved `convention/*` + journal pointer | +| **Journal** | session ends | **SessionEnd** hook appends a dated journal note with pointers | +| **Reorganize** | periodic, user-invoked | `reorganize memory` run in **plan mode**: dedupe, merge, split, re-tag, promote `scope/global`, rebuild index — human approves | + +Hooks are thin shell wrappers over the Ruby CLI so the logic lives in one place. + +**Hooks summary:** +- **SessionStart** — reconcile (`index update --since auto`) + inject context. +- **PostToolUse** (on `Write`/`Edit` of vault `.md`) — `index update --file`. +- **SessionEnd** — append daily journal note. +- (memsearch brings its own `UserPromptSubmit` + capture hooks for the episodic layer.) + +--- + +## Part D — Claude Code plugin with skills (global install) + +Goal: one global install so every project/machine knows the vault, conventions, hooks, and CLI. + +**Plugin contents:** +- **Hooks** registered in settings: SessionStart, PostToolUse, SessionEnd (the shell wrappers + from Part C), pointed at a configurable vault path + cache dir. +- **The Ruby CLI** (the tag index) bundled or installed as a dependency, on `PATH`. +- **Skills** (these carry the *know-how* to the model): + - `memory-write` — when to record evergreen knowledge, the frontmatter contract, scope rules, + "vault not repo." (Part B write section.) + - `memory-query` — how/when to query the tag index vs the episodic layer; the progressive- + disclosure loop; cross-client lookups. (Part B query section.) + - `memory-reorganize` — the plan-mode consolidation/promotion procedure + guardrails. +- **Config**: vault path, cache path, sync method — set once at user level. + +**Why a plugin + skills (not just CLAUDE.md):** hooks must be registered by the harness (the +plugin does that), and the conventions are taught as skills so they load on demand without +bloating every project's context. A single global install keeps the conventions themselves a +single source of truth — edit the skill once, every project follows. + +**Open question for build time**: do the coding-`convention/*` notes live *in the vault* (data, +edited freely, resolved by the SessionStart hook) while the *memory-system skills* live *in the +plugin* (behavior, versioned)? Recommended: yes — conventions are data in the vault; the skills +are the mechanism. diff --git a/docs/memory-system/05-handoff.md b/docs/memory-system/05-handoff.md new file mode 100644 index 0000000..95723c1 --- /dev/null +++ b/docs/memory-system/05-handoff.md @@ -0,0 +1,82 @@ +# Session Handoff + +_Written 2026-06-03 at the end of the brainstorming session. For the next session to pick up._ + +## What this is + +Designing a personal, cross-project **memory system for Claude Code** for a multi-client +freelancer. We finished **brainstorming/architecture** and wrote the design + ADRs. **No +implementation has started.** The source material is the transcript at +`/home/jared/Documents/cc-os/memory-systems-compared060326` (the "6 Levels of Claude Code +Memory" video). + +## Where we are + +- ✅ Synthesized the video (`01-video-synthesis.md`). +- ✅ Settled the architecture and got user sign-off (`02-system-design.md`). +- ✅ Recorded every decision + rejected/deferred options (`03-architecture-decisions.md`). +- ✅ Wrote the build outline + operational answers (`04-build-plan.md`). +- ⏳ **Next**: turn `04-build-plan.md` into a real implementation plan and start building. + +## The design in 30 seconds + +Three local, markdown-as-truth, no-Docker layers: +1. **Episodic** ("what happened, when") = **memsearch** (Milvus Lite, off-the-shelf). +2. **Knowledge** ("how do we…") = **flat Obsidian vault** (single source of truth) + a + **Ruby/Sequel/SQLite tag index** with a CLI. Organized by **namespaced nested tags** + (`tool/ client/ domain/ convention/ scope/`), not folders. +3. **Semantic over the vault** = **QMD**, **deferred** until structured tagging proves + insufficient. + +Retrieval is hook-injected + on-demand (projects stay thin). Freshness is lazy (write-time +hook + session-start reconcile; no daemon/cron). Vault syncs to a VPS via git/Syncthing; +indexes are disposable and rebuilt per machine. Ships as a **global Claude Code plugin with +skills**. + +## Decisions locked (don't relitigate without reason) + +- Two separate systems for episodic vs knowledge (ADR-001). +- memsearch for episodic (ADR-002). Flat vault + tags, not folders (ADR-003). +- SQLite + Sequel (Ruby) CLI tag index; markdown authoritative, cache disposable (ADR-004). +- Structured-first; semantic (QMD) deferred (ADR-005/006). +- Lazy freshness: PostToolUse write hook + SessionStart reconcile, no daemon/cron (ADR-007). +- Sync the vault not the indexes; reject OpenBrain/Mem0/Postgres (ADR-008). +- Package as a global plugin with skills (ADR-009). +- Query output = path + summary + matched tags (option C). + +## Open questions for build time + +1. **Vault location** — default `~/brain` (or similar synced home dir)? Symlink into + `~/.claude/memory` only if a tool requires it. +2. **Sync mechanism** — git (versioned history, hourly) vs Syncthing (continuous, zero-thought). +3. **Convention notes placement** — confirmed direction: coding `convention/*` live as **data + in the vault** (resolved by the SessionStart hook); the memory-system **skills live in the + plugin** (behavior, versioned). Validate when building. +4. **memsearch journal** — does memsearch index our SessionEnd journal notes, or only its own + auto-capture? Decide how the journal points into the knowledge vault. +5. **Promotion rule** — the concrete threshold for project→`scope/global` promotion (e.g. + recurrence count) used by the reorganize step. +6. **CLI ergonomics** — command framework (thor vs optparse), output format details, where + `last_cache_time` is stored. + +## Recommended first actions next session + +1. Re-read `02-system-design.md` and `04-build-plan.md`. +2. Invoke the **writing-plans** skill to convert `04-build-plan.md` Part A into a staged + implementation plan (Step 2, the Ruby tag-index CLI, is the critical path — build and test + it standalone first). +3. Resolve open questions 1–2 (vault location + sync) before writing code, since they affect + paths in the hooks and CLI. +4. Build Step 2 (CLI) with tests, then Step 3 (hooks), then validate end-to-end on the seeded + notes before touching the plugin packaging (Step 6). + +## Context notes + +- Working dir `/home/jared/Documents/cc-os` is **not a git repo**; consider `git init` if we + want history for these docs and the build. +- User prefers **Docker when reasonable** — but this design deliberately needs **none** (all + layers are embedded/local). Only full Milvus or QMD-server modes would involve Docker, and + we're not using those. +- User is Ruby-comfortable (chose Ruby + Sequel deliberately). +- A past `~/Documents/SecondBrain/` attempt (tag DB) was lost track of — the "markdown + authoritative, cache disposable & rebuildable" rule (ADR-004) is the explicit fix for that.