# Build Plan _Last updated: 2026-06-09_ 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. **Architecture decision (2026-06-03):** Graphify replaces the Ruby/SQLite tag index. The Ruby CLI (old Step 2) is skipped. Graphify handles both knowledge-graph queries over the vault and free AST-based code graphs per project. The deferred QMD semantic layer (old Step 7) is also skipped — Graphify covers it without vectors. See `06-graphify-evaluation.md` for the full rationale. **Architecture decision (2026-06-04):** Build order inverted (ADR-013). Build and validate the full system against a small 5–10 note fixture set first; defer bulk vault migration to last. Validate end-to-end on one small pilot project containing both code and documents before onboarding any others. Onboard remaining projects one at a time, with observe-and-adjust between each. Steps 2d and 2e below are updated accordingly. --- ## Part A — Build order (human builder's path) Build bottom-up: the vault and Graphify first (usable standalone), then the hooks, then the plugin that packages it. ### Step 1 — Vault skeleton & conventions - Vault location is settled by ADR-012: `~/Documents/SecondBrain` (the existing Obsidian vault). No new vault is created; 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. The required fields are `summary` (one-line, author-written — this is the human-readable router hint Graphify does not generate) and `scope` (`scope/global` or `scope/project`). Tags are now supplementary, not the primary query mechanism. **Naming resolved (2026-06-09):** `vault-conventions.md` is the canonical name (not `CONVENTIONS.md`). The file exists at `~/Documents/SecondBrain/vault-conventions.md`. - Seed a few real notes (e.g. `tool/semrush`, `client/`) to use as extraction test cases in Step 2. **Status: COMPLETE (2026-06-09)** - `vault-conventions.md` (renamed 2026-06-09 from `CONVENTIONS.md`) exists in the vault at `~/Documents/SecondBrain/vault-conventions.md` — frontmatter contract + tag taxonomy. - All 6 fixture notes exist in the vault (seeded for extraction testing in Step 2c). - 14 Graphify handbook + memory-system design notes migrated to the vault (2026-06-09) as part of migration scaffolding — first-class deliverable per the empirical finding locked 2026-06-05. ### Step 2 — Graphify + Ollama setup (the knowledge graph layer) This replaces the Ruby tag-index CLI. Graphify builds a knowledge graph over vault docs via a local SLM, and a free code graph over project files via AST. #### 2a — Install Graphify **DONE (2026-06-08)** ``` pip install graphify # or follow current install docs ``` Verify: `graphify --version` **Status:** `graphify` v0.8.31 installed at `/home/jared/.local/bin/graphify`. #### 2b — Configure Ollama for extraction **DONE (2026-06-08)** The 2024 approach (Modelfile with `PARAMETER num_ctx`) still works but the preferred programmatic method is now per-request via the API (Graphify supports this via `GRAPHIFY_OLLAMA_NUM_CTX`): ```bash # Set in your shell profile or service env: OLLAMA_FLASH_ATTENTION=1 # 30-50% VRAM savings on KV cache — always set this GRAPHIFY_OLLAMA_NUM_CTX=8192 # 8K is sufficient for vault notes (200-2000 words) # and leaves comfortable headroom for prompt overhead ``` To verify context is being used: `ollama ps` shows allocated context after first call. **Status:** All env vars set in `~/.claude/plugins/memory/config.yaml`: `OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`. Model locked to `qwen25-coder-7b-16k` (per ADR and memory note — this is Graphify's default and the confirmed working extraction model). Available models (as of 2026-06-03, in order of interest): - `gemma4:e2b` — 7.2 GB, ~2B effective params, 128K native window, fast; **start here** - `qwen3.5:2b` — 2.7 GB, smallest, good fallback if VRAM is constrained - `gemma4:e4b` — 9.6 GB, more capable, slower #### 2c — Claude reference-set benchmark (THE GATE, before committing to a local model) This step produces the **gold-standard reference set** — one structured extraction output per fixture note per Claude tier. It does **not** choose the final extraction model, and it does **not** measure speed. **Input:** the 5–10 fixture notes selected in Step 1 (from the runbook). **What to run:** dispatch one Claude Code subagent per tier (Claude-tiers only — Ollama models are not reachable in this environment): | Tier | Model ID | Role | |---|---|---| | Haiku | `claude-haiku-4-5` | Lightweight reference | | Sonnet | `claude-sonnet-4-6` | Mid-tier reference | | Opus | `claude-opus-4-8` | **Gold standard** (scoring rubric) | Each subagent receives only the fixture note text plus the shared extraction spec — no design docs, no project context (fairness contract). See ADR-013. **Metrics — quality only (wall-clock speed is explicitly out of scope here):** 1. Entity correctness — right concepts extracted, no hallucinated entities 2. Relationship plausibility and typing — edges plausible, correctly typed, no missing key edges 3. Confidence-tag accuracy — `INFERRED` vs `AMBIGUOUS` applied appropriately **Deliverables** (produced by this step): - Dispatch prompt (copy/paste-able): `docs/memory-system/benchmark/dispatch-prompt.md` - Shared extraction schema: `docs/memory-system/benchmark/extraction-spec.md` - Per-tier outputs: `docs/memory-system/benchmark/reference-outputs/..md` Opus output is the rubric. **Deferred later step:** local Ollama models (gemma4:e2b, qwen3.5:2b, gemma4:e4b) are timed AND scored against these references — that scoring run is where speed re-enters and the final model is chosen. Do not hardcode a model before that run completes. **Status (2026-06-04): EXECUTED.** 6 cross-domain fixtures × 3 Claude tiers = 18 reference fragments generated in `benchmark/reference-outputs/`. Run as-is (no vault frontmatter modification); verified clean. Fixtures listed in `benchmark/dispatch-prompt.md`. Authoritative detail lives in `docs/memory-system/05-implementation-process.md` §2c. Local-model gut-check done (2026-06-04): `gemma4:e4b` is the candidate; GPU fix pending reboot; Graphify owns the ollama call — see `docs/memory-system/benchmark/local-llm-findings-2026-06-04.md`. #### 2d — Initial fixture graph build (ADR-013: small-first) Run the initial build against the small fixture set (5–10 notes from Step 1/2c), not the full vault. The bulk vault build is deferred to after the system is validated end-to-end. ```bash graphify extract --path ~/Documents/SecondBrain --backend ollama --model gemma4:e2b \ --token-budget 512 --max-concurrency 2 ``` Tune `--token-budget` (semantic chunk size) and `--max-concurrency` based on VRAM headroom. Review `GRAPH_REPORT.md` — check god nodes make sense (they should be your most-connected tools, clients, and domain concepts). **Note (2026-06-08):** The `~/Documents/SecondBrain` vault already has a live `graphify-out/` graph (32 files, built 2026-06-05). The fixture-first approach from ADR-013 may therefore be superseded — the vault graph exists before Step 1 (conventions/fixtures) is formally complete. Determine whether to treat the existing vault graph as the fixture baseline or run a clean fixture-only build once Step 1 is done. This is an open question before 2d can be marked done. **Full vault migration** (the `~/Documents/SecondBrain` build above run over all notes) is the final step — deferred to after end-to-end validation on the pilot project. Do not bulk-migrate the vault until the system is verified working on the fixture set and pilot project. #### 2e — Per-project code graphs (free, no model needed) **ADR-013 order:** start with ONE pilot project that contains both code and documents; validate end-to-end before onboarding others. Onboard remaining projects one at a time, with observe-and-adjust between each. Do not batch all projects at once. For the pilot project (and each subsequent project, one at a time): ```bash graphify extract --path ~/projects// --no-docs ``` `--no-docs` skips LLM extraction; only AST is run. Zero token cost. Add `--update` on subsequent runs; use `--force` when you've deleted files (to clear stale nodes). Store each project's `graphify-out/` alongside the project, or in a configurable cache dir. Keep per-project graphs separate — do not merge client projects into one graph. **Note (2026-06-08):** A `graphify-out/` for the `cc-os` project exists (built 2026-06-05), but `cc-os` is a docs-only repo, not a pilot project in the ADR-013 sense. A pilot project with real code has not yet been onboarded. **Status: NOT STARTED** (pilot project onboard pending) ### Step 3 — Hooks (maintenance + retrieval) **DONE (2026-06-08)** Hooks now call Graphify instead of the Ruby CLI. - **PostToolUse** (on Write/Edit of vault `.md`): `graphify update --file ` to merge the changed note into the vault graph. Note: Graphify's `--update` does not prune deleted nodes — a periodic `--force` rebuild (e.g. weekly, or triggered by SessionStart stale check) is needed to clear ghost nodes. - **SessionStart**: reconcile (check vault graph mtime vs last rebuild; trigger `--force` if stale threshold exceeded) + inject context (emits project graph path if a `graphify-out/graph.json` exists at the project root; vault context injection was removed). - **SessionEnd**: append daily journal note with pointers to vault notes touched. **Stale-node mitigation strategy:** track last `--force` rebuild time in a stamp file (e.g. `~/.cache/graphify/vault-rebuild.stamp`). SessionStart hook triggers `--force` if older than N days (7 is a reasonable starting point). ### Step 4 — Episodic layer (memsearch) — DONE - 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). - Graphify does **not** replace memsearch here. Time-anchored semantic queries ("what was I working on last Tuesday?") are better served by vector similarity over session logs. ### Step 5 — Sync — PARTIAL Step 5 covers two distinct sync lines; they are at different statuses: #### 5a — memsearch episodic memory git sync — **DONE (2026-06-09)** `~/.memsearch` is now a git repo on branch `main` with remote `origin` pointing to a private self-hosted Forgejo repo (`ssh://git@forgejo.swansoncloud.com:2222/jared/memsearch.git`; web: `https://forgejo.swansoncloud.com/jared/memsearch`). Initial commit `106cebc`. A whitelist `.gitignore` tracks **only** `memory/*.md` (the daily session files) and `.gitignore`. Excluded as derived/disposable: `milvus.db` (Milvus Lite index — rebuildable via `memsearch index`), `config.toml`, and the bge-m3 ONNX model (lives in `~/.cache/huggingface`). Auto-commit and push are wired into the cc-os memory plugin's **own `session-end.sh`** hook (not the marketplace plugin, which can be clobbered on update). The block guards on `~/.memsearch/.git` existing, commits only when staged content exists (message: `memsearch: session memory `), and pushes with `timeout 30 ... || true`. Wrapped in a subshell with trailing `|| true` — cannot fail session shutdown. See **ADR-015** for rationale and the cross-client commingling, which is an accepted design choice (single global store across all clients by intent — see ADR-015, resolved 2026-06-09). #### 5b — Obsidian vault → VPS sync — **NOT STARTED** - Pick **git** (versioned, hourly push/pull) or **Syncthing** (continuous, zero-thought) for the vault → VPS. Configure on each machine. - **Do not sync** the Graphify `graphify-out/` directories, Milvus caches, or the Ollama models. Graphs are rebuilt per machine from the vault (the single source of truth). ### Step 6 — Package as a global plugin (Part D) **DONE (2026-06-08)** - Wrap Steps 2–3 into a Claude Code plugin with skills; install at user level. **Status:** Global plugin installed at `~/.claude/plugins/memory/` with all hook scripts and 4 skills (`memory-vault`, `memory-write`, `memory-reorganize`, `memory-project`). Plugin enabled in `~/.claude/settings.json`. ### Step 7 — (SKIPPED) QMD semantic layer Covered by Graphify. The knowledge-graph approach provides structured semantic retrieval without vectors. Only revisit if Graphify's graph queries prove insufficient for a use case where embedding similarity would outperform graph traversal. --- ## 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 — this is the human-authored router hint; Graphify extracts entities but does not write summaries), and `scope/global` or `scope/project`. - **Scope rule**: default new tool/domain knowledge to `scope/global`; mark `scope/project` when it's specific to how a client uses something. - **Write only to the vault**, never silently into a project repo. - **Promotion to global** and **consolidation** happen in the reorganize step (Part C), in plan mode, for human review. ### When & how the AI QUERIES **Knowledge queries (vault graph)** via Graphify CLI (Bash tool): - `graphify query "how do we authenticate with SEMrush?"` — semantic graph search - `graphify path "client/sesame3g" "tool/semrush"` — how these two concepts connect - `graphify explain "convention/seo-workflow"` — node deep-dive with neighbors - Add `--budget N` to cap answer size (default ~2000 tokens); `--dfs --budget N` for bounded traversal - Add `--graph ` to query a specific project's `graph.json` instead of the vault graph **At session start**: the session-context.sh hook emits the project graph path (pointing at `/graphify-out/graph.json`) if one exists — vault context injection (god-node summary, convention summaries, journal pointer) was removed. The AI queries the vault or project graph on demand rather than having it injected. **On demand, during a task**: run `graphify query` when the task touches a tool/client/domain. The graph returns relevant node clusters; the AI opens only the vault notes whose `summary` matches the task. The `summary` frontmatter field remains the human-written progressive-disclosure hint. **Cross-client lookups**: `graphify query "tool semrush"` returns all nodes connected to the SEMrush concept, regardless of which client project they came from. Graph edges show provenance. **"What happened" questions** go to **memsearch** in natural language — not the graph. --- ## Part C — Hooks & CRUD mapping | Operation | Trigger | Mechanism | |-----------|---------|-----------| | **Create** | AI writes a new vault note | AI writes `.md` (Write tool) → **PostToolUse** hook → `graphify update --file` | | **Read / query** | AI needs knowledge | AI runs `graphify query` / `graphify explain` via Bash (no hook; on-demand) | | **Update** | AI/user edits a note | AI edit → **PostToolUse** → `graphify update --file`; user edit → **SessionStart** reconcile | | **Delete / rename** | note removed/renamed | `graphify update --file` prunes AI-deleted paths; **SessionStart** triggers `--force` rebuild if stale (catches manual deletes) | | **Inject** | new session starts | **SessionStart** hook: emits project graph path (vault context injection removed) | | **Journal** | session ends | **SessionEnd** hook appends a dated journal note with pointers | | **Reorganize** | periodic, user-invoked | `reorganize memory` in **plan mode**: dedupe, merge, split, re-scope, then `graphify --force` to rebuild clean graph — human approves | | **Stale graph rebuild** | SessionStart stale check | If rebuild stamp > N days old: `graphify --force` on vault; resets stamp | Hooks are thin shell wrappers; the logic lives in Graphify. **Hooks summary:** - **SessionStart** — stale check + `--force` if needed, then inject project graph path (vault context injection was removed; session-context.sh now only emits the project graph path). - **PostToolUse** (on `Write`/`Edit` of vault `.md`) — `graphify update --file`. - **SessionEnd** — append daily journal note. - (memsearch brings its own hooks for the episodic layer.) **Status:** All 4 hooks wired in `~/.claude/settings.json`: SessionStart, UserPromptSubmit (context injection), PostToolUse (vault writes), SessionEnd (journal). --- ## Part D — Claude Code plugin with skills (global install) Goal: one global install so every project/machine knows the vault, conventions, hooks, and Graphify config. **Plugin contents:** - **Hooks** registered in settings: SessionStart, PostToolUse, SessionEnd (shell wrappers from Part C), pointed at a configurable vault path + Graphify output dir. - **Graphify CLI** on `PATH` — the AI uses the Bash tool to run `graphify query`, `graphify path`, `graphify explain` directly. No server process. Project-specific graphs are queried with `--graph ` pointing at the project's `graphify-out/graph.json`. - **Skills** (these carry the *know-how* to the model): - `memory-write` — when to record evergreen knowledge, the frontmatter contract (summary required, scope rule), "vault not repo." - `memory-vault` — how/when to use `graphify query` vs memsearch; god-node discipline; `--budget`/`--dfs` usage; cross-client lookups via `--graph`; progressive-disclosure via summary field. - `memory-reorganize` — the plan-mode consolidation/promotion procedure + guardrails; when to trigger `graphify --force` rebuild. - `memory-project` **DONE (2026-06-08)** — full lifecycle skill for per-project Graphify graphs: - **Onboard**: `graphify extract . --backend ollama --model qwen2.5-coder:7b`, then `graphify cluster-only .`, add `graphify-out/` to `.gitignore` - **Query**: `graphify query ... --graph ` for project-context queries (wraps `memory-vault`) - **Update**: `graphify update .` for incremental updates (no API cost) or `--force` rebuild after structural changes - **Remove**: delete `graphify-out/` when no longer needed - **Config**: vault path, Graphify output dir, Ollama model name, `num_ctx`, rebuild-stale threshold in days — set once at user level. - **Env vars** set by plugin: `OLLAMA_FLASH_ATTENTION=1`, `GRAPHIFY_OLLAMA_NUM_CTX=8192`, `GRAPHIFY_OLLAMA_KEEP_ALIVE=5`. **Why a plugin + skills (not just CLAUDE.md):** hooks must be registered by the harness; conventions taught as skills load on demand without bloating every project's context. A single global install keeps conventions a single source of truth. **Convention notes placement:** coding `convention/*` notes live as **data in the vault** (edited freely, resolved by SessionStart hook); memory-system **skills live in the plugin** (behavior, versioned). --- ## Open questions 1. **Vault location** — Settled by ADR-012: `~/Documents/SecondBrain`. Symlink into `~/.claude/memory` only if a tool requires it. 2. **Sync mechanism** — git (versioned, hourly) vs Syncthing (continuous)? 3. **Stale rebuild threshold** — how many days before SessionStart triggers `--force`? 7 days is the starting guess; tune after observing drift in practice. 4. **Per-project graph path** — how does the AI know which `graph.json` to pass to `--graph` for the current project? Convention: `/graphify-out/graph.json`, injected by SessionStart hook if a project graph exists at that path. 5. **Summary field ownership** — Graphify extracts entities but does NOT write summaries. The human (or AI, on vault note creation) must write the `summary` frontmatter. Confirm this discipline holds in practice. 6. **Model benchmark results** — defer all model decisions until Step 2c benchmark is done. Don't hardcode `gemma4:e2b` until testing confirms it's the right pick. 7. **memsearch + journal** — does memsearch index our SessionEnd journal notes, or only its own auto-capture, and how does the journal point into the knowledge vault?