th-memory-mcp 1.2.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/ARCHITECTURE_v2.md +1582 -0
  2. package/README.md +214 -187
  3. package/README.th.md +28 -7
  4. package/dist/core/consolidation-engine.js +87 -0
  5. package/dist/core/context-engine.js +50 -0
  6. package/dist/core/graph-engine.js +67 -0
  7. package/dist/core/lifecycle-engine.js +76 -0
  8. package/dist/core/retrieval-engine.js +40 -0
  9. package/dist/core/temporal-engine.js +73 -0
  10. package/dist/db/index.js +111 -0
  11. package/dist/db/migrations.js +160 -0
  12. package/dist/db/repositories/memories.js +52 -0
  13. package/dist/db.js +3 -0
  14. package/dist/index.js +42 -0
  15. package/dist/lib/embed.js +8 -5
  16. package/dist/memory/conflict-resolver.js +125 -0
  17. package/dist/memory/decay.js +30 -0
  18. package/dist/memory/deduplicator.js +51 -0
  19. package/dist/memory/scorer.js +44 -0
  20. package/dist/memory/source-weights.js +13 -0
  21. package/dist/memory/types.js +41 -0
  22. package/dist/retrieval/fts.js +22 -0
  23. package/dist/retrieval/fusion.js +11 -0
  24. package/dist/retrieval/scorer.js +19 -0
  25. package/dist/retrieval/vector.js +29 -0
  26. package/dist/tools/consolidate.js +63 -0
  27. package/dist/tools/context.js +55 -0
  28. package/dist/tools/export_memory.js +1 -1
  29. package/dist/tools/extract_memories.js +90 -0
  30. package/dist/tools/forget.js +1 -1
  31. package/dist/tools/history.js +1 -1
  32. package/dist/tools/import_memory.js +95 -0
  33. package/dist/tools/lesson.js +1 -1
  34. package/dist/tools/link_memory.js +31 -0
  35. package/dist/tools/memory_stats.js +1 -1
  36. package/dist/tools/merge_memory.js +49 -0
  37. package/dist/tools/profile.js +1 -1
  38. package/dist/tools/recall.js +1 -1
  39. package/dist/tools/recent_interactions.js +1 -1
  40. package/dist/tools/remember.js +1 -1
  41. package/dist/tools/update_memory.js +98 -0
  42. package/package.json +46 -46
  43. package/design.md +0 -308
package/design.md DELETED
@@ -1,308 +0,0 @@
1
- # Design: Adaptive Memory MCP — behavior-learning memory system for OpenCode
2
-
3
- > Project: D:\Coding_Project\mcp
4
- > Date: 2026-08-26 (rev.3 — as-built updated after all phases implemented)
5
- > Status: **implementation complete** — server v1.1.0, 9 tools, tests passing 70/70 assertions
6
-
7
- ## 1. Overview
8
-
9
- A system that lets OpenCode "remember and adapt" to the user, composed of 3 parts:
10
-
11
- 1. **MCP Server (th-memory-mcp v1.1.0)** — stores/retrieves preferences, lessons, and usage history in SQLite, exposing 9 tools the AI can call
12
- 2. **OpenCode Plugin (learning-capture)** — hooks events to auto-capture prompts/tool usage and injects the profile back into context on compaction
13
- 3. **Global Instructions (memory-protocol.md)** — the Memory Protocol rules, attached to every agent/session via `"instructions"` in the global opencode.json
14
-
15
- ### Important constraints
16
- LLM APIs are **not trained on our data** — the only real "learning" possible is **context-based learning**:
17
- - capture behavior → distill into preferences/lessons
18
- - recall into context at the start of a new session (AI calls `recall` / plugin injects)
19
- This is the same mechanism behind the memory features of leading AI products.
20
-
21
- ## 2. Architecture
22
-
23
- ```
24
- ┌────────────────────────────────────────────┐
25
- │ OpenCode │
26
- │ │
27
- │ ┌──────────────────┐ ┌───────────────┐ │
28
- │ │ learning-capture │ │ AI Agent │ │
29
- │ │ Plugin (Bun) │ │ │ │
30
- │ │ - message.updated│ │ calls MCP │ │
31
- │ │ - tool.execute.* │ │ tools │ │
32
- │ │ - compacting* │ │ │ │
33
- │ └────────┬─────────┘ └──────┬────────┘ │
34
- └───────────┼─────────────────────┼──────────┘
35
- │ write (bun:sqlite) │ read/write (stdio JSON-RPC)
36
- ▼ ▼
37
- ┌─────────────────────────────────────┐
38
- │ th-memory-mcp v1.1.0 (Node+SDK) │
39
- │ better-sqlite3 (WAL) ◀── shared ── │
40
- │ Tools (9): remember, recall, │
41
- │ get_profile, save_lesson, │
42
- │ search_history, forget, │
43
- │ memory_stats, │
44
- │ get_recent_interactions, │
45
- │ export_memory │
46
- └─────────────────────────────────────┘
47
-
48
-
49
- D:/Coding_Project/mcp/data/memory.db
50
- ```
51
-
52
- (*) compaction hook = `experimental.session.compacting` used in Phase 3
53
-
54
- ### Learning loop
55
- 1. **Capture** — plugin auto-writes prompts/tool usage to `interactions`; AI also saves preferences/lessons via tools
56
- 2. **Distill** — summarize raw logs into profile: rule-based via `npm run distill` (Thai tokenization with Intl.Segmenter + prune older than 30 days) and AI-assisted via the Smart Distill workflow in the protocol
57
- 3. **Recall** — new session: AI calls `get_profile` + `recall(topic)` per the Memory Protocol (global instructions)
58
- 4. **Inject** — plugin auto-injects the profile on session compaction (`experimental.session.compacting`)
59
-
60
- ## 3. Data Model (SQLite)
61
-
62
- DB file: `data/memory.db` (path overridable via `MEMORY_DB_PATH`)
63
- WAL mode + busy_timeout=5000 on every connection
64
-
65
- ```sql
66
- -- raw behavior (plugin writes)
67
- CREATE TABLE interactions (
68
- id INTEGER PRIMARY KEY AUTOINCREMENT,
69
- ts TEXT NOT NULL, -- ISO datetime
70
- session_id TEXT,
71
- kind TEXT NOT NULL, -- 'prompt' | 'tool_call' | 'error'
72
- content TEXT NOT NULL, -- text (truncated per rules)
73
- meta TEXT -- JSON extra, e.g. tool name, project dir
74
- );
75
-
76
- -- user preferences/requirements (AI/plugin writes)
77
- CREATE TABLE preferences (
78
- id INTEGER PRIMARY KEY AUTOINCREMENT,
79
- category TEXT NOT NULL, -- work_style | coding_pref | language | domain | other
80
- key TEXT NOT NULL,
81
- value TEXT NOT NULL,
82
- confidence REAL DEFAULT 0.5, -- 0..1, +0.1 per repeated confirmation
83
- source TEXT DEFAULT 'explicit', -- explicit | corrected | inferred
84
- updated_at TEXT NOT NULL,
85
- UNIQUE(category, key)
86
- );
87
-
88
- -- lessons from corrections
89
- CREATE TABLE lessons (
90
- id INTEGER PRIMARY KEY AUTOINCREMENT,
91
- situation TEXT NOT NULL, -- original situation
92
- mistake TEXT NOT NULL, -- what was done wrong
93
- correction TEXT NOT NULL, -- correct approach
94
- created_at TEXT NOT NULL
95
- );
96
-
97
- -- distilled profile
98
- CREATE TABLE profile (
99
- section TEXT PRIMARY KEY, -- identity | goals | style | notes
100
- content TEXT NOT NULL,
101
- updated_at TEXT NOT NULL
102
- );
103
-
104
- -- virtual table for search
105
- CREATE VIRTUAL TABLE search_index USING fts5(
106
- ref_table, ref_id, title, body
107
- );
108
- ```
109
-
110
- ## 4. MCP Tools spec
111
-
112
- Server name: `th-memory-mcp`, version **1.1.0**, transport stdio
113
- Every tool returns `{ content: [{ type: "text", text }] }`; errors must be caught and returned as a message (never crash)
114
-
115
- | Tool | Args (zod) | Behavior |
116
- |------|-----------|----------|
117
- | `remember` | `category` enum, `key`: string, `value`: string | upsert preferences; same key → confidence += 0.1 (cap 1.0), update value+updated_at |
118
- | `recall` | `topic`: string, `limit`?: number (default 8) | FTS5 search search_index (preferences+lessons) + latest 20 matching interactions; grouped text, ≤ ~2000 chars |
119
- | `get_profile` | (none) | profile sections + top preferences (confidence desc, limit 15) + latest 5 lessons |
120
- | `save_lesson` | `situation`, `mistake`, `correction`: string | insert lessons + update search_index |
121
- | `search_history` | `query`: string, `limit`?: number (default 10) | FTS5 in interactions (kind='prompt'), 200-char snippets per row |
122
- | `forget` | `target_id`: number, `type`? enum("preference","lesson","interaction") | delete from table by id (+type prevents cross-table id clash) + sync search_index |
123
- | `memory_stats` | (none) | counts by kind + DB size + oldest/newest interaction + profile sections; ≤1500 chars |
124
- | `get_recent_interactions` | `limit`? (default 20, max 100), `kind`? enum("prompt","tool_call","error") | latest rows formatted `[id] ts [kind] content(300)`; ≤4000 chars |
125
- | `export_memory` | `includeInteractions`? bool (default false), `filename`? string | write JSON only under `data/exports/` (sanitize filename `[A-Za-z0-9._-]`, no `..`); return path+size+preview ≤500 chars |
126
-
127
- ## 5. Plugin spec (learning-capture)
128
-
129
- File: `src/plugin/learning-capture.ts` → deploy to `~/.config/opencode/plugins/learning-capture.ts`
130
- Runtime: Bun (OpenCode plugins run on Bun) → uses `bun:sqlite` on the same DB (WAL supports multi-process)
131
-
132
- ```ts
133
- // as-built: self-contained single file — logic inline, synced with src/lib/capture-core.ts
134
- // (declares minimal types itself; does not import @opencode-ai/plugin to avoid module resolution issues)
135
- import { Database } from "bun:sqlite"
136
-
137
- export const LearningCapture = async (ctx) => {
138
- const db = new Database(process.env.MEMORY_DB_PATH ?? "D:/Coding_Project/mcp/data/memory.db")
139
- db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")
140
- // CREATE TABLE IF NOT EXISTS interactions (...) in case DB was never created
141
- const dedupe = createDedupe()
142
- return {
143
- event: async ({ event }) => {
144
- // message.updated (role=user) → insert kind='prompt' (truncate 4000, dedupe by message id)
145
- // session.error → insert kind='error'
146
- },
147
- "tool.execute.after": async (input, output) => {
148
- // insert kind='tool_call' (dedupe by callID, truncate 500)
149
- },
150
- "experimental.session.compacting": async (input, output) => {
151
- // buildProfileText(db): profile sections + top preferences (confidence desc, 15)
152
- // + latest 5 lessons → ≤3000 chars → output.context.push(txt)
153
- // wrap everything in try/catch silently — failed injection does no harm
154
- },
155
- }
156
- }
157
- ```
158
-
159
- Capture rules:
160
- - Dedupe by message id (prevent duplicate events) — keep a Set of recorded ids in process memory
161
- - Never store secrets: filter lines matching `/(api[_-]?key|secret|token|password)\s*[=:]/i` before saving
162
- - Every write must try/catch — the plugin must never crash OpenCode
163
-
164
- ## 6. Making the AI use memory (Memory Protocol)
165
-
166
- Installed at 2 levels:
167
-
168
- 1. **Global (in use)** — `~/.config/opencode/memory-protocol.md` attached via `"instructions"` in global opencode.json → covers **every agent, every session** without switching agents
169
- 2. **Project-level (alternative)** — copy from `AGENTS.memory.example.md` into a project's AGENTS.md
170
-
171
- Protocol essentials:
172
- - call `get_profile` + `recall` before a new/complex task
173
- - `save_lesson` immediately when the user corrects you / `remember` immediately when the user states a preference / never guess — if recall finds nothing, ask
174
- - `search_history` when suspecting a prior conversation / `forget` after confirming with the user
175
- - call memory tools only when necessary (not every message) / never store secrets / if memory is offline, continue gracefully
176
-
177
- **Smart Distill**: when the user asks "summarize memory" → `get_recent_interactions(limit=50)` → analyze real patterns → save insights via `remember`/`save_lesson` → summarize to the user with the list of new items
178
-
179
- ## 7. File structure
180
-
181
- ```
182
- D:\Coding_Project\mcp\
183
- ├── design.md # this document (rev.3 as-built)
184
- ├── README.md # usage guide + scripts + tools
185
- ├── package.json # type: module, scripts: build/start/distill/test
186
- ├── tsconfig.json # NodeNext, ES2022, strict; exclude src/plugin + test
187
- ├── .gitignore # node_modules, dist, data/
188
- ├── data\ # memory.db (+wal/shm) and exports\ (git ignored)
189
- ├── src\
190
- │ ├── index.ts # McpServer v1.1.0 + registerTool ×9 + StdioServerTransport
191
- │ ├── db.ts # schema init, WAL, helper query, FTS sync
192
- │ ├── lib\
193
- │ │ ├── capture-core.ts # pure logic: filterSecrets/truncate/dedupe/buildRow/INSERT_SQL
194
- │ │ └── distill-core.ts # pure logic: tokenize(Thai)/computeStats/formatProfileSections
195
- │ ├── distill.ts # CLI: runDistill(db) + prune (RETENTION_DAYS default 30)
196
- │ ├── tools\
197
- │ │ ├── remember.ts recall.ts profile.ts lesson.ts history.ts forget.ts
198
- │ │ ├── memory_stats.ts recent_interactions.ts export_memory.ts
199
- │ └── plugin\
200
- │ └── learning-capture.ts # self-contained Bun plugin → deploy copy to ~/.config/opencode/plugins/
201
- ├── test\
202
- │ ├── smoke.mjs # 53 checks end-to-end JSON-RPC (spawns real server)
203
- │ ├── capture.test.mjs # 8 checks (capture-core + SQL insert)
204
- │ └── distill.test.mjs # 9 checks (tokenize/stats/runDistill/prune/idempotent)
205
- ├── AGENTS.memory.example.md # Memory Protocol + Smart Distill (project-level)
206
- └── opencode.example.json # example mcp config
207
- ```
208
-
209
- ## 8. Technology
210
-
211
- | Part | Choice | Reason |
212
- |------|---------|--------|
213
- | MCP Server | Node.js ≥ 20 + TypeScript + `@modelcontextprotocol/sdk@1.30.0` + zod | official standard |
214
- | DB (server) | `better-sqlite3@12.x` + FTS5 | fast sync API, easy, prebuilt binary (no compile) |
215
- | DB (plugin) | `bun:sqlite` (built-in) | plugin runs on Bun, no native module install |
216
- | Thai tokenization | `Intl.Segmenter("th", { granularity: "word" })` + whitespace fallback | segment Thai (no spaces) built into Node |
217
-
218
- > as-built note: the plugin is **self-contained** (declares minimal types in-file), so `@opencode-ai/plugin` is not required
219
-
220
- ## 9. Sub-tasks
221
-
222
- ### Phase 1 — MVP: MCP Server ✅ 2026-08-25
223
- 1. Init project: `"type": "module"`, deps: `@modelcontextprotocol/sdk`, `zod`, `better-sqlite3`; devDeps: `typescript`, `@types/node`, `@types/better-sqlite3`, `@opencode-ai/plugin`
224
- 2. `src/db.ts`: schema per §3, WAL, busy_timeout, helper + FTS sync
225
- 3. First 6 tools per §4 spec (separate files in `src/tools/` — later expanded to 9 in Phase 4)
226
- 4. `src/index.ts`: McpServer("th-memory-mcp") + register + StdioServerTransport (**no console.log — stderr only**)
227
- 5. Build + smoke test with MCP Inspector (`npx @modelcontextprotocol/inspector node dist/index.js`) — remember → recall → forget
228
- 6. Create `opencode.example.json`:
229
-
230
- ```json
231
- {
232
- "$schema": "https://opencode.ai/config.json",
233
- "mcp": {
234
- "memory": {
235
- "type": "local",
236
- "command": ["node", "D:/Coding_Project/mcp/dist/index.js"],
237
- "enabled": true,
238
- "environment": {}
239
- }
240
- }
241
- }
242
- ```
243
-
244
- 7. Create `AGENTS.memory.example.md` per §6
245
- 8. Guide user: merge config → restart OpenCode → test "remember I prefer pnpm" then ask back in a new session
246
-
247
- ### Phase 2 — Plugin auto-capture ✅ 2026-08-26
248
- 9. `src/plugin/learning-capture.ts` per §5 (dedupe + secret filter + try/catch everywhere)
249
- 10. Copy to `~/.config/opencode/plugins/learning-capture.ts` → restart OpenCode → use a while → verify `interactions` has data (`search_history` finds old prompts)
250
-
251
- ### Phase 3 — Inject + Distill ✅ 2026-08-26
252
- 11. Add hook `"experimental.session.compacting"` to plugin: `output.context.push(profile text)` from get_profile logic
253
- 12. Distill script: rule-based summarize interactions → profile sections (`npm run distill`, Thai tokenize via Intl.Segmenter) + prune older than RETENTION_DAYS
254
-
255
- ### Phase 4 — Insight & Safety ✅ 2026-08-26
256
- 13. 3 new tools: `memory_stats` / `get_recent_interactions` / `export_memory` (sanitize filename + write only under data/exports/) — server bump v1.1.0
257
- 14. Smart Distill workflow added to memory-protocol.md (global) + AGENTS.memory.example.md + README.md
258
-
259
- > as-built note: global instructions (`memory-protocol.md` via `"instructions"` in opencode.json) replace a dedicated agent — covers every agent without switching; smoke test expanded to 53 checks including security cases (unsafe filename rejected)
260
-
261
- ## 10. Risks and mitigation
262
-
263
- | Risk | Impact | Mitigation |
264
- |------|--------|-----------|
265
- | Context bloat from long recall | token waste | cap 2000 chars/tool call, default limit |
266
- | Wrong/stale memory | AI goes wrong | confidence + updated_at + tool forget + user review |
267
- | SQLite accessed by 2 processes (Bun+Node) | lock error | WAL mode + busy_timeout=5000 |
268
- | `message.updated` fires often | DB bloat/duplicate | dedupe by message id + truncate |
269
- | Secret leaks to DB | security | regex filter before every write |
270
- | stdout mixed with logs | protocol breaks | stderr only in server code |
271
- | Invalid config | OpenCode won't start | add `$schema` validated against https://opencode.ai/config.json |
272
-
273
- ## 11. Dependencies
274
-
275
- - Node.js ≥ 20, npm
276
- - OpenCode supporting plugins + MCP (current version)
277
- - No external service/API — 100% local (privacy by design)
278
-
279
- ## 12. Performance Budget (acceptance criteria)
280
-
281
- Building Agent must implement within this budget:
282
-
283
- | Item | Budget | Check |
284
- |------|--------|-------|
285
- | Query latency per tool call | < 100 ms (local SQLite) | time in smoke test |
286
- | Max output per tool | `recall` ≤ 2000 chars, `search_history` ≤ 200 chars/row, `get_profile` ≤ 3000 chars | assert in code (always truncate) |
287
- | Default limit | recall=8, search_history=10 rows | default in zod schema |
288
- | Plugin write per event | < 5 ms, fire-and-forget (no event-loop block) | code review |
289
- | Server startup | < 2 s to ready for initialize | time it |
290
-
291
- **Measured (2026-08-26):** latency per tool call **1–9 ms**, startup **792–997 ms**, every tool within budget, tests **70/70** (smoke 53 + capture 8 + distill 9)
292
-
293
- ### Overhead prevention
294
- - Memory Protocol calls memory **only on new/complex tasks**, never every message
295
- - Graceful degradation: if DB/server errors, return a short error message and let the AI continue immediately; no tight retry until timeout
296
- - Never auto-inject profile every turn — inject only on compaction (Phase 3)
297
-
298
- ### Long-term risks to monitor
299
- - Memory quality decay (self-contradiction) → use confidence + updated_at + forget + distill (Phase 3)
300
- - DB growth → FTS5 index supports it; plan periodic VACUUM/optimize
301
-
302
- ## 13. Next phases (Optional / Future)
303
-
304
- - Semantic search with embeddings (local model or API) instead of FTS5
305
- - Usage statistics dashboard (small web app reading the DB)
306
- - Multi-project memory scoping (by directory/worktree)
307
- - Import memory from export file (export side done in Phase 4)
308
- - Automatic LLM-assisted distill via OpenCode SDK (instead of user-triggered command)