harness/plugins/memory-keeper/skills/plugin-architecture/SKILL.md
Architecture reference for the memory-keeper plugin. Covers the daemon-based architecture, two-adapter pattern (Claude Code SSE + Pi HTTP), queue-based async processing, and unified core library. Use when onboarding, debugging capture issues, or planning changes.
npx skillsauth add popoffvg/dotfiles plugin-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
┌──────────────────────────────────────────────────┐
│ memory-keeper daemon │
│ (single long-lived process on localhost:7420) │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ MCP server │ │ Queue │ │ Drain loop │ │
│ │ (SSE/HTTP) │ │ (SQLite) │ │ (30s cycle) │ │
│ └──────┬───────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────┴───────────────┴───────────────┴──────┐ │
│ │ Core library │ │
│ │ classify, dedup, save, stats, logger │ │
│ └──────────────────────────────────────────────┘ │
└──────────┬─────────────────────────┬──────────────┘
│ SSE (MCP) │ HTTP REST
┌──────┴──────┐ ┌──────┴──────┐
│ Claude Code │ │ Pi agent │
│ (MCP client)│ │ (HTTP calls)│
└─────────────┘ └─────────────┘
Key principle: single daemon process owns all state (SQLite DB, queue, drain loop, logger, stats). Both Claude Code and Pi are thin clients.
memory-keeper/
├── common/ # Shared core — pure TS, zero framework deps
│ ├── memory.ts # Business logic: config, classify, dedup, save, stats
│ ├── logger.ts # pino logger (JSON, file dest, rotation)
│ ├── queue.ts # SQLite queue (better-sqlite3, WAL mode)
│ ├── processor.ts # processQueue() drain function
│ ├── index.ts # Re-exports
│ └── server/
│ ├── daemon.ts # SSE daemon: HTTP server + MCP + drain loop + PID lifecycle
│ ├── index.ts # Legacy stdio MCP server (kept for fallback)
│ └── package.json
├── claude/ # Claude Code plugin package
│ ├── .claude-plugin/plugin.json
│ ├── agents/context-keeper.md
│ ├── bin/memory-extract.sh # Stop hook → tells Claude to call memory_extract
│ ├── bin/ensure-daemon.sh # SessionStart hook → auto-start daemon + health banner
│ ├── hooks/hooks.json # Stop + SessionStart hooks
│ ├── mcp.json # SSE connection to daemon (type: sse, url: localhost:7420/sse)
│ └── skills/
├── pi/ # Pi agent adapter (thin HTTP client)
│ └── index.ts # Extension: cron, cursor, TUI, QMD tools, daemon HTTP calls
├── skills/ # Shared skill definitions
├── __tests__/ # Test suite
│ ├── stats.test.ts # 13 tests: formatStatsTable, health banner, QMD tracking
│ ├── queue.test.ts # 18 tests: enqueue/dequeue/markDone/markFailed/gcSessions
│ └── processor.test.ts # 12 tests: processQueue drain function
└── package.json
Single long-lived process started via ensure-daemon.sh or manually with npx tsx daemon.ts.
| Endpoint | Method | Purpose | Used by |
|---|---|---|---|
| /sse | GET | SSE transport for MCP | Claude Code |
| /messages | POST | MCP message relay | Claude Code |
| /health | GET | JSON: status, uptime, queue stats, banner | Both |
| /api/enqueue | POST | Enqueue conversation for processing | Pi |
| /api/stats | GET | Formatted stats table (text) | Pi |
| /api/health-banner | GET | One-line health banner (text) | Pi |
| /api/context | GET | Project summary + topics (text) | Pi |
| /api/queue-stats | GET | Queue counts (JSON) | Pi |
| /api/track-qmd | POST | Track QMD tool usage | Pi |
| Tool | Purpose |
|---|---|
| memory_context | Project summary + topics + health banner |
| memory_save | Save single entry with dedup |
| memory_extract | Enqueue conversation for async processing |
| memory_topics | List topics per project |
| memory_stats | Token usage stats by day (with detail drill-down) |
| memory_queue_stats | Queue status: pending/processing/done/failed |
ensure-daemon.sh (SessionStart hook) checks PID filenpx tsx daemon.ts in background~/.claude/debug/memory-keeper.pidopenQueue(), gcSessions(30), start drain loopEvery 30 seconds, processQueue() dequeues pending items and runs:
collectExistingTopics() → buildClassifyPrompt()llmCallFn() → Anthropic API (claude-haiku-4-5)parseClassification() → processInsights() (QMD + file dedup)trackTokenUsage() → markDone()| | Claude Code | Pi |
|---|---|---|
| Transport | SSE (MCP protocol) | HTTP REST |
| Connection | mcp.json → http://127.0.0.1:7420/sse | fetch() to daemon endpoints |
| Extraction trigger | Stop hook → memory_extract tool | Cron (3 min) + shutdown |
| Processing | Daemon enqueues, drain loop processes | Same (via /api/enqueue) |
| LLM | Daemon owns (Anthropic SDK) | Daemon owns |
| Context injection | memory_context tool | before_agent_start → /api/context |
| Health | memory_context includes banner | /api/health-banner |
Database: ~/.claude/debug/memory-keeper.db (WAL mode)
gcSessions(30) on daemon startup — keeps 30 most recent sessions by last_seen, deletes older sessions + their queue rows.
Unified pino logger → ~/.claude/debug/memory-keeper.log
| Component | Used by |
|---|---|
| core | common/memory.ts |
| queue | common/queue.ts |
| drain | common/processor.ts |
| daemon | common/server/daemon.ts |
JSON format, manual rotation (512KB, 3 files). Level: MK_LOG_LEVEL env (default: info).
Compact one-liner shown on session start:
memory-keeper: 3 insights today · 1.2k tokens · queue: 0 pending · 42 sessions tracked
Degraded states:
⚠ 5 failed in queue — failures highlighted first✗ no stats yet — first session? — no data| Layer | Description |
|---|---|
| Prompt-level | Existing topic headings injected into LLM prompt |
| QMD semantic | qmd search CLI, score >= 0.7 same-project = skip |
| File-level | Heading substring + word overlap >= 0.7 |
memory_extract tool; Pi: /api/enqueue)buildClassifyPrompt() with existing topics{classification, category, repo, topic, body}parseClassification() — handles truncated JSON, filters out noneprocessInsights() — per entry: QMD dedup → file dedup → saveInsight()saveInsight() routes by classification:
insight → <insights_root>/<repo>/<category>.mdagent_edit → <insights_root>/claude-config/behavior.mdtrackTokenUsage() → ~/.claude/debug/token-stats.jsonl~/.claude/memory-keeper.local.md YAML frontmatter:
| Key | Purpose | Default |
|---|---|---|
| insights_root | Root directory for all insights | required |
| exclude_paths | Comma-separated glob patterns to skip | none |
Daemon-level env vars:
| Var | Purpose | Default |
|---|---|---|
| MK_PORT | Daemon port | 7420 |
| MK_LOG_LEVEL | pino log level | info |
| MK_DAEMON_URL | Pi: daemon base URL | http://127.0.0.1:7420 |
| ANTHROPIC_API_KEY | Required for LLM classification | from env |
tools
Improve a whole CLAUDE.local.md — the private, per-project rules captured from user corrections. Wraps each conditional rule in a <task-relevant> block so it only surfaces for matching work, merges duplicates, generalizes one-off facts, drops stale entries, and routes raw project facts to engram. Use when the user says "improve claude.local", "clean up the local rules", "claude.local is bloated", or after the Stop hook has appended many rules.
testing
WM pipeline and conventions shared across all phases. Agents must read this before spec, impl, or verify work.
development
One entry point for spec writing, implementation, and bug fixing. Default is new (write spec → grill loop → produce notes → author TODO bodies). Other subcommands: verify (audit), revise (sync to shipped), prototype (settle a decision), code-map (diagram), impl (execute one TODO), fix (analyze cause, correct thoughts, fix behavior), help (this page). Invoke as /code <subcommand>.
development
Red-Green-Refactor cycle for bug fixes. Before fixing a bug, first write a failing test that reproduces it (Red), then make the minimal change to pass (Green), then clean up the code (Refactor). Use on any bug fix, error correction, failing test repair, or when user says "fix this bug".