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 |
testing
Use when the user asks to create test sets, enumerate scenarios, generate edge cases, or draft a coverage matrix before implementation.
testing
Use when the user asks to review, audit, score, or validate test sets for missed cases before execution or merge.
tools
Test harness plugins in isolation using tmux panes. Runs MCP servers, unit tests, typecheck, and Claude plugin loading. Use when user says "test plugin", "check plugin", "run plugin tests", "validate plugin", or names a specific plugin to test.
development
Guide for designing integration and e2e tests using BDD (Behavior-Driven Development) methodology with Cucumber-style Given/When/Then scenarios. Use when writing or reviewing tests for any service, API, or component. Language-agnostic — covers scenario structure, step notation, assertion principles, async patterns, and common anti-patterns.