memory-crystal 0.2.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 (104) hide show
  1. package/.env.example +20 -0
  2. package/CHANGELOG.md +6 -0
  3. package/LETTERS.md +22 -0
  4. package/LICENSE +21 -0
  5. package/README-ENTERPRISE.md +162 -0
  6. package/README-old.md +275 -0
  7. package/README.md +91 -0
  8. package/RELAY.md +88 -0
  9. package/TECHNICAL.md +379 -0
  10. package/ai/dev-updates/2026-02-25--cc-air--phase2-architecture-pivot.md +70 -0
  11. package/ai/dev-updates/2026-02-25--cc-air--phase2-worker-build.md +72 -0
  12. package/ai/dev-updates/2026-02-26--10-25-16--cc-mini--phase2-implementation.md +49 -0
  13. package/ai/dev-updates/2026-02-27--20-30-00--cc-mini--readme-overhaul-and-public-deploy.md +69 -0
  14. package/ai/notes/2026-02-26--cc-air--notes.md +412 -0
  15. package/ai/notes/2026-02-27--cc-mini--grok-feedback.md +44 -0
  16. package/ai/notes/2026-02-27--cc-mini--lesa-feedback.md +45 -0
  17. package/ai/notes/RESEARCH.md +1185 -0
  18. package/ai/notes/salience-research/README.md +29 -0
  19. package/ai/notes/salience-research/eurosla-salience-review.md +64 -0
  20. package/ai/notes/salience-research/full-research-summary.md +269 -0
  21. package/ai/notes/salience-research/salience-levels-diagram.png +0 -0
  22. package/ai/plan/2026-02-27--cc-mini--qr-pairing-spec.md +203 -0
  23. package/ai/plan/_archive/PLAN.md +194 -0
  24. package/ai/plan/_archive/PRD.md +1014 -0
  25. package/ai/plan/cc-plans-duplicates-from-dot-claude/2026-02-26--cc-mini--phase2-implementation-plan.md +245 -0
  26. package/ai/plan/dev-conventions-note.md +70 -0
  27. package/ai/plan/ldm-os-install-and-boot-architecture.md +285 -0
  28. package/ai/plan/memory-crystal-phase2-plan.md +192 -0
  29. package/ai/plan/memory-system-lay-of-the-land.md +214 -0
  30. package/ai/plan/phase2-ephemeral-relay.md +238 -0
  31. package/ai/plan/readme-first.md +68 -0
  32. package/ai/plan/roadmap.md +159 -0
  33. package/ai/todos/PUNCHLIST.md +44 -0
  34. package/ai/todos/README.md +31 -0
  35. package/ai/todos/inboxes/cc-air/2026-02-26--cc-air--post-relay-todos.md +85 -0
  36. package/ai/todos/inboxes/cc-mini/2026-02-26--cc-mini--phase2-status.md +100 -0
  37. package/ai/todos/inboxes/cc-mini/_archive/TODO.md +25 -0
  38. package/ai/todos/inboxes/parker/2026-02-25--cc-air--setup-checklist.md +139 -0
  39. package/ai/todos/inboxes/parker/2026-02-26--cc-mini--phase2-your-moves.md +72 -0
  40. package/dist/cc-hook.d.ts +1 -0
  41. package/dist/cc-hook.js +349 -0
  42. package/dist/chunk-3VFIJYS4.js +818 -0
  43. package/dist/chunk-52QE3YI3.js +1169 -0
  44. package/dist/chunk-AA3OPP4Z.js +432 -0
  45. package/dist/chunk-D3I3ZSE2.js +411 -0
  46. package/dist/chunk-EKSACBTJ.js +1070 -0
  47. package/dist/chunk-F3Y7EL7K.js +83 -0
  48. package/dist/chunk-JWZXYVET.js +1068 -0
  49. package/dist/chunk-KYVWO6ZM.js +1069 -0
  50. package/dist/chunk-L3VHARQH.js +413 -0
  51. package/dist/chunk-LOVAHSQV.js +411 -0
  52. package/dist/chunk-LQOYCAGG.js +446 -0
  53. package/dist/chunk-MK42FMEG.js +147 -0
  54. package/dist/chunk-NIJCVN3O.js +147 -0
  55. package/dist/chunk-O2UITJGH.js +465 -0
  56. package/dist/chunk-PEK6JH65.js +432 -0
  57. package/dist/chunk-PJ6FFKEX.js +77 -0
  58. package/dist/chunk-PLUBBZYR.js +800 -0
  59. package/dist/chunk-SGL6ISBJ.js +1061 -0
  60. package/dist/chunk-UNHVZB5G.js +411 -0
  61. package/dist/chunk-VAFTWSTE.js +1061 -0
  62. package/dist/chunk-XZ3S56RQ.js +1061 -0
  63. package/dist/chunk-Y72C7F6O.js +148 -0
  64. package/dist/cli.d.ts +1 -0
  65. package/dist/cli.js +325 -0
  66. package/dist/core.d.ts +188 -0
  67. package/dist/core.js +12 -0
  68. package/dist/crypto.d.ts +16 -0
  69. package/dist/crypto.js +18 -0
  70. package/dist/dev-update-SZ2Z4WCQ.js +6 -0
  71. package/dist/ldm.d.ts +17 -0
  72. package/dist/ldm.js +12 -0
  73. package/dist/mcp-server.d.ts +1 -0
  74. package/dist/mcp-server.js +250 -0
  75. package/dist/migrate.d.ts +1 -0
  76. package/dist/migrate.js +89 -0
  77. package/dist/mirror-sync.d.ts +1 -0
  78. package/dist/mirror-sync.js +130 -0
  79. package/dist/openclaw.d.ts +5 -0
  80. package/dist/openclaw.js +349 -0
  81. package/dist/poller.d.ts +1 -0
  82. package/dist/poller.js +272 -0
  83. package/dist/summarize.d.ts +19 -0
  84. package/dist/summarize.js +10 -0
  85. package/dist/worker.js +137 -0
  86. package/openclaw.plugin.json +11 -0
  87. package/package.json +40 -0
  88. package/scripts/migrate-lance-to-sqlite.mjs +217 -0
  89. package/skills/memory/SKILL.md +61 -0
  90. package/src/cc-hook.ts +447 -0
  91. package/src/cli.ts +356 -0
  92. package/src/core.ts +1472 -0
  93. package/src/crypto.ts +113 -0
  94. package/src/dev-update.ts +178 -0
  95. package/src/ldm.ts +117 -0
  96. package/src/mcp-server.ts +274 -0
  97. package/src/migrate.ts +104 -0
  98. package/src/mirror-sync.ts +175 -0
  99. package/src/openclaw.ts +250 -0
  100. package/src/poller.ts +345 -0
  101. package/src/summarize.ts +210 -0
  102. package/src/worker.ts +208 -0
  103. package/tsconfig.json +18 -0
  104. package/wrangler.toml +20 -0
package/RELAY.md ADDED
@@ -0,0 +1,88 @@
1
+ ###### WIP Computer
2
+
3
+ # Relay: Multi-Device Sync
4
+
5
+ Memory Crystal works on one machine out of the box. Multi-device sync lets your agent's memory follow you across machines. Conversations captured on your laptop are available on your desktop and vice versa.
6
+
7
+ Everything is encrypted before it leaves your machine. The relay never sees your data unencrypted.
8
+
9
+ ## Two Options
10
+
11
+ ### Use Our Relay (Default)
12
+
13
+ We host the relay infrastructure. You just set an encryption key.
14
+
15
+ ```
16
+ Open your AI and say:
17
+
18
+ I want to set up multi-device sync for Memory Crystal.
19
+ Walk me through the setup step by step.
20
+ ```
21
+
22
+ Your agent generates your encryption key, configures the connection, and tests it. Takes about two minutes.
23
+
24
+ **What you need:**
25
+ - Memory Crystal installed on both machines
26
+ - An encryption key (your agent generates this)
27
+
28
+ **Pricing:** Free during beta. When pricing is introduced, your agent will handle it via [AI CASH](https://github.com/wipcomputer/wip-agent-pay/blob/main/CASH.md).
29
+
30
+ ### Self-Host Your Own Relay
31
+
32
+ Run your own relay on Cloudflare Workers (free tier). Same code, your infrastructure. Full control.
33
+
34
+ **What you need:**
35
+ - A Cloudflare account (free tier works)
36
+ - About five minutes
37
+
38
+ **Steps:**
39
+ 1. Deploy the Worker from `worker/index.js` to Cloudflare
40
+ 2. Create a KV namespace called `PAY_TOKENS`, bind it as `KV`
41
+ 3. Set a `WORKER_SECRET` via `wrangler secret put WORKER_SECRET`
42
+ 4. Point Memory Crystal at your Worker:
43
+
44
+ ```bash
45
+ crystal relay --self-hosted --worker-url https://your-relay.your-domain.com
46
+ ```
47
+
48
+ Full deployment details in [Technical Documentation](https://github.com/wipcomputer/memory-crystal/blob/main/TECHNICAL.md).
49
+
50
+ No fees. No dependencies on us. The relay code is open source (MIT).
51
+
52
+ ## How Sync Works
53
+
54
+ 1. You work with your agent on Machine A
55
+ 2. After each session, Memory Crystal encrypts the conversation (AES-256-GCM) and drops it at the relay
56
+ 3. Machine B polls the relay, downloads the encrypted blob, decrypts it locally, and ingests it into its crystal.db
57
+ 4. The relay deletes the blob after pickup
58
+
59
+ Two one-way roads:
60
+ - **Device to home machine** ... encrypted conversation chunks
61
+ - **Home machine to devices** ... search-ready DB snapshot via mirror-sync
62
+
63
+ The relay is a dead drop. It stores encrypted blobs temporarily and serves them on request. It has no decryption capability. If someone compromises the relay, they get encrypted noise.
64
+
65
+ ## Encryption
66
+
67
+ - **AES-256-GCM** for encryption (authenticated encryption, no padding oracle attacks)
68
+ - **HMAC-SHA256** for signing (integrity verification before decryption)
69
+ - Shared key generated locally, never transmitted to the relay
70
+ - Key must be present on both machines (store in 1Password, AirDrop between Macs, or transfer manually)
71
+
72
+ ## More Info
73
+
74
+ - [README.md](https://github.com/wipcomputer/memory-crystal/blob/main/README.md) ... What Memory Crystal is and how to install it.
75
+ - [Technical Documentation](https://github.com/wipcomputer/memory-crystal/blob/main/TECHNICAL.md) ... Full technical documentation.
76
+
77
+ ---
78
+
79
+ ## License
80
+
81
+ ```
82
+ src/, skills/, cli.ts, mcp-server.ts MIT (use anywhere, no restrictions)
83
+ worker/ AGPL (relay server)
84
+ ```
85
+
86
+ AGPL for personal use is free.
87
+
88
+ Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code CLI (Claude Opus 4.6).
package/TECHNICAL.md ADDED
@@ -0,0 +1,379 @@
1
+ ###### WIP Computer
2
+
3
+ # Technical Documentation
4
+
5
+ How Memory Crystal works. Architecture, design decisions, integrations, encryption, search, and everything else the open source community is going to ask about.
6
+
7
+ ## How Does It Work?
8
+
9
+ Memory Crystal captures every conversation you have with any AI tool, embeds it into a local SQLite database, and makes it searchable with hybrid search (keyword + semantic). One database file. Runs on your machine. Nothing leaves your device unless you set up multi-device sync.
10
+
11
+ Every conversation produces three artifacts:
12
+ 1. **JSONL transcript** ... the raw session, archived to disk
13
+ 2. **Markdown summary** ... title, summary, key topics (generated by LLM or simple extraction)
14
+ 3. **Vector embeddings** ... chunked, embedded, and stored in crystal.db for search
15
+
16
+ ## How Does It Work with Claude?
17
+
18
+ Memory Crystal runs as a [Claude Code Stop hook](https://docs.anthropic.com/en/docs/claude-code). After every Claude Code response, the hook fires automatically:
19
+
20
+ 1. Reads Claude Code's JSONL transcript file via byte-offset watermarking (only reads new data since last capture)
21
+ 2. Extracts user, assistant, and thinking blocks
22
+ 3. Chunks them, embeds into sqlite-vec
23
+ 4. Archives the full JSONL transcript
24
+ 5. Generates a markdown session summary
25
+ 6. Appends a daily breadcrumb log
26
+
27
+ **Configuration:**
28
+ ```json
29
+ {
30
+ "hooks": {
31
+ "Stop": [{ "hooks": [{ "type": "command", "command": "node ~/.openclaw/extensions/memory-crystal/dist/cc-hook.js", "timeout": 30 }] }]
32
+ }
33
+ }
34
+ ```
35
+
36
+ ```bash
37
+ node dist/cc-hook.js --on # Enable capture
38
+ node dist/cc-hook.js --off # Pause capture
39
+ node dist/cc-hook.js --status # Check status
40
+ ```
41
+
42
+ Respects private mode. When capture is off, nothing is recorded. First run seeds the watermark at current file size so it doesn't replay your entire history.
43
+
44
+ ## How Does It Work with OpenClaw?
45
+
46
+ Memory Crystal is an OpenClaw plugin. It registers tools (`crystal_search`, `crystal_remember`, `crystal_forget`, `crystal_status`) and an `agent_end` hook that captures conversations after every agent turn.
47
+
48
+ Deployed to `~/.openclaw/extensions/memory-crystal/`. The plugin uses the same `core.ts` as every other interface. Same search, same database, same embeddings.
49
+
50
+ ## How Does It Work with ChatGPT, Codex, and Other Tools?
51
+
52
+ Any tool that can run shell commands or call an MCP server can use Memory Crystal.
53
+
54
+ - **MCP Server** ... `mcp-server.ts` exposes `crystal_search`, `crystal_remember`, `crystal_forget`, `crystal_status`, `crystal_sources_add`, `crystal_sources_sync`, `crystal_sources_status`. Works with Claude Desktop, Claude Code, or any MCP-compatible client.
55
+ - **CLI** ... `crystal search "query"` from any terminal. Any tool with shell access can call it.
56
+ - **Module** ... `import { MemoryCrystal } from 'memory-crystal'` for Node.js integration.
57
+
58
+ For tools that don't have hook support (like ChatGPT), you can manually export conversations and import them. Automatic capture requires hook support (Claude Code, OpenClaw).
59
+
60
+ ## Architecture
61
+
62
+ One core, five interfaces.
63
+
64
+ ```
65
+ sqlite-vec (vectors) + FTS5 (BM25) + SQLite (metadata/graph)
66
+ | | |
67
+ core.ts ... pure logic, zero framework deps
68
+ |-- cli.ts -> crystal search "query"
69
+ |-- mcp-server.ts -> crystal_search (Claude Code, Claude Desktop)
70
+ |-- openclaw.ts -> plugin (OpenClaw agents)
71
+ |-- cc-hook.ts -> Claude Code Stop hook (auto-capture)
72
+ +-- worker.ts -> Cloudflare Worker (encrypted relay)
73
+ ```
74
+
75
+ Every interface calls the same `core.ts`. The core has zero framework dependencies. It talks to SQLite and nothing else.
76
+
77
+ ## Search: How Does It Work?
78
+
79
+ Hybrid search. Two engines run in parallel and their results are fused.
80
+
81
+ ### The Pipeline
82
+
83
+ 1. Query goes to both FTS5 (keyword match) and sqlite-vec (vector similarity)
84
+ 2. FTS5 returns BM25-ranked results, normalized to [0..1) via `|score| / (1 + |score|)`
85
+ 3. sqlite-vec returns cosine-distance results via two-step query (MATCH first, then JOIN separately ... sqlite-vec hangs with JOINs in the same query)
86
+ 4. Reciprocal Rank Fusion merges both lists: `weight / (k + rank + 1)` with k=60, plus top-rank bonus
87
+ 5. Recency weighting applied on top: `max(0.5, 1.0 - age_days * 0.01)`
88
+ 6. Final results sorted by combined score
89
+
90
+ Inspired by and partially ported from [QMD](https://github.com/tobi/qmd) by Tobi Lutke (MIT, 2024-2026).
91
+
92
+ ### Why Hybrid?
93
+
94
+ Vector search alone misses exact matches. Keyword search alone misses semantic similarity. Hybrid catches both. A search for "deployment process" will find conversations that use the word "deployment" (BM25) and conversations about "shipping code to production" (vector similarity).
95
+
96
+ ### Recency Decay
97
+
98
+ Linear decay from 1.0 to 0.5 over 50 days. Fresh context wins ties. Old stuff still surfaces for strong matches.
99
+
100
+ Freshness flags: fresh (<3 days), recent (<7 days), aging (<14 days), stale (14+ days).
101
+
102
+ ### Content Dedup
103
+
104
+ SHA-256 hash of chunk text before embedding. Duplicate content is never re-embedded. This matters when the same conversation is captured by multiple hooks (e.g., Claude Code hook and OpenClaw plugin running simultaneously).
105
+
106
+ ## Database
107
+
108
+ Everything lives in one file: `crystal.db`. Inspectable with any SQLite tool. Backupable with `cp`.
109
+
110
+ ### Schema
111
+
112
+ | Table | Purpose |
113
+ |-------|---------|
114
+ | `chunks` | Chunk text, metadata, SHA-256 hash, timestamps |
115
+ | `chunks_vec` | sqlite-vec virtual table (cosine distance vectors) |
116
+ | `chunks_fts` | FTS5 virtual table (Porter stemming, BM25 scoring) |
117
+ | `memories` | Explicit remember/forget facts |
118
+ | `entities` | Knowledge graph nodes |
119
+ | `relationships` | Knowledge graph edges |
120
+ | `capture_state` | Watermarks for incremental ingestion |
121
+ | `sources` | Ingestion source metadata |
122
+ | `source_collections` | Directory collections for file indexing |
123
+ | `source_files` | Indexed file records with content hashes |
124
+
125
+ ### Why SQLite?
126
+
127
+ One file. No server. No Docker. No connection strings. Works on every platform. Inspectable with standard tools. Backupable with `cp`. Ships with every OS.
128
+
129
+ sqlite-vec adds vector search as a virtual table. FTS5 adds full-text search. Both are SQLite extensions that work within the same database file.
130
+
131
+ ### DB Location
132
+
133
+ `resolveConfig()` in `core.ts` checks in order:
134
+
135
+ 1. Explicit override (programmatic)
136
+ 2. `CRYSTAL_DATA_DIR` env var
137
+ 3. `~/.ldm/memory/crystal.db` (if it exists)
138
+ 4. `~/.openclaw/memory-crystal/` (legacy fallback)
139
+
140
+ ## Directory Structure
141
+
142
+ Memory Crystal manages `~/.ldm/` ... the universal agent home directory for [LDM OS](https://github.com/wipcomputer/dream-weaver-protocol).
143
+
144
+ ```
145
+ ~/.ldm/
146
+ config.json version, registered agents array
147
+ memory/
148
+ crystal.db shared vector DB (all agents)
149
+ agents/{agent_id}/
150
+ memory/
151
+ transcripts/ full JSONL session transcripts
152
+ sessions/ markdown session summaries
153
+ daily/ daily breadcrumb logs
154
+ journals/ agent journals
155
+ ```
156
+
157
+ Path resolution is centralized in `src/ldm.ts`:
158
+ - `getAgentId()` ... resolves from `CRYSTAL_AGENT_ID` env var, default `cc-mini`
159
+ - `ldmPaths(agentId?)` ... returns all paths as an object
160
+ - `scaffoldLdm(agentId?)` ... creates the full directory tree
161
+ - `ensureLdm(agentId?)` ... idempotent check, scaffolds if needed
162
+
163
+ ## Encryption: How Does It Work?
164
+
165
+ For multi-device sync. All encryption happens on-device before anything touches the network.
166
+
167
+ - **AES-256-GCM** for encryption. Authenticated encryption ... ciphertext tampering is detected.
168
+ - **HMAC-SHA256** for signing. Integrity verification before decryption. If the signature doesn't match, the blob is rejected.
169
+ - **Shared symmetric key** generated locally with `openssl rand -hex 32`. Never transmitted to the relay.
170
+ - The relay stores and serves encrypted blobs. It has no decryption capability. Compromising the relay yields encrypted noise.
171
+
172
+ ### Key Management
173
+
174
+ The same encryption key must be present on all devices. Options:
175
+ - **1Password** ... store the key, both machines pull from 1Password via SA token
176
+ - **AirDrop** ... direct transfer between Macs
177
+ - **Manual** ... copy the key securely between machines
178
+
179
+ ### Relay Architecture
180
+
181
+ 1. **Device side** (`cc-hook.ts` relay mode): Encrypts JSONL with AES-256-GCM, signs with HMAC-SHA256, drops at Cloudflare Worker
182
+ 2. **Worker** (`worker.ts`): Stores encrypted blobs in KV. Pure dead drop. No decryption.
183
+ 3. **Home machine** (`poller.ts`): Polls Worker, downloads blobs, decrypts, ingests into crystal.db. Reconstructs remote agent's file tree (JSONL, MD summary, daily breadcrumb).
184
+ 4. **Mirror sync** (`mirror-sync.ts`): Pulls crystal.db snapshot from home machine to devices for local search.
185
+
186
+ Two one-way roads:
187
+ - **Device -> Home machine** ... encrypted conversation chunks (ephemeral)
188
+ - **Home machine -> Devices** ... search-ready DB snapshot
189
+
190
+ ## Session Summaries
191
+
192
+ `src/summarize.ts` generates markdown summaries. Two modes:
193
+
194
+ **LLM mode** (default): Calls gpt-4o-mini with a condensed transcript. Returns title, slug, summary, key topics.
195
+
196
+ **Simple mode**: First user message becomes the title. First 10 messages as preview. No API call.
197
+
198
+ Controlled by `CRYSTAL_SUMMARY_MODE` env var (`llm` or `simple`).
199
+
200
+ ## Embedding Providers
201
+
202
+ | Provider | Model | Dimensions | Cost |
203
+ |----------|-------|-----------|------|
204
+ | OpenAI (default) | text-embedding-3-small | 1536 | ~$0.02/1M tokens |
205
+ | Ollama | nomic-embed-text | 768 | Free (local) |
206
+ | Google | text-embedding-004 | 768 | Free tier available |
207
+
208
+ Set via `CRYSTAL_EMBEDDING_PROVIDER` env var or `--provider` flag.
209
+
210
+ ### Why These Three?
211
+
212
+ - **OpenAI** ... best quality, lowest friction. Most people already have an API key.
213
+ - **Ollama** ... fully offline. Zero cost. Privacy-first. No data leaves your machine.
214
+ - **Google** ... free tier is generous. Good alternative if you don't want OpenAI.
215
+
216
+ ## Source File Indexing
217
+
218
+ Add directories as "collections". Files are chunked, embedded, and tagged with file path + collection name. Searchable alongside conversations and memories.
219
+
220
+ ```bash
221
+ crystal sources add /path/to/project --name my-project
222
+ crystal sources sync my-project
223
+ crystal sources status
224
+ ```
225
+
226
+ Incremental sync detects changed files via SHA-256 content hashing. Only re-embeds what changed.
227
+
228
+ ## Environment Variables
229
+
230
+ | Variable | Default | Description |
231
+ |----------|---------|-------------|
232
+ | `CRYSTAL_EMBEDDING_PROVIDER` | `openai` | `openai`, `ollama`, or `google` |
233
+ | `CRYSTAL_AGENT_ID` | `cc-mini` | Agent identifier for LDM paths |
234
+ | `CRYSTAL_SUMMARY_MODE` | `llm` | `llm` or `simple` |
235
+ | `CRYSTAL_SUMMARY_PROVIDER` | `openai` | Summary LLM provider |
236
+ | `CRYSTAL_SUMMARY_MODEL` | `gpt-4o-mini` | Summary LLM model |
237
+ | `CRYSTAL_DATA_DIR` | (auto) | Override DB location |
238
+ | `CRYSTAL_RELAY_KEY` | ... | Shared encryption key for relay |
239
+ | `CRYSTAL_RELAY_URL` | ... | Cloudflare Worker URL |
240
+ | `CRYSTAL_REMOTE_URL` | ... | Remote Worker URL |
241
+ | `CRYSTAL_REMOTE_TOKEN` | ... | Worker auth token |
242
+ | `OPENAI_API_KEY` | ... | OpenAI key |
243
+ | `GOOGLE_API_KEY` | ... | Google AI key |
244
+ | `CRYSTAL_OLLAMA_HOST` | `http://localhost:11434` | Ollama server URL |
245
+ | `CRYSTAL_OLLAMA_MODEL` | `nomic-embed-text` | Ollama model |
246
+
247
+ ### API Key Resolution
248
+
249
+ 1. Explicit override (programmatic)
250
+ 2. `process.env` (set by plugin or manually)
251
+ 3. `.env` file (`~/.openclaw/memory-crystal/.env`)
252
+ 4. 1Password CLI fallback
253
+
254
+ ## CLI Reference
255
+
256
+ ```bash
257
+ # Search
258
+ crystal search <query> [-n limit] [--agent <id>] [--provider <openai|ollama|google>]
259
+
260
+ # Remember / forget
261
+ crystal remember <text> [--category fact|preference|event|opinion|skill]
262
+ crystal forget <id>
263
+
264
+ # Status
265
+ crystal status [--provider <openai|ollama|google>]
266
+
267
+ # Source file indexing
268
+ crystal sources add <path> --name <name>
269
+ crystal sources sync [name]
270
+ crystal sources status
271
+
272
+ # LDM management
273
+ crystal init [--agent <id>]
274
+ crystal migrate-db
275
+ ```
276
+
277
+ ## MCP Tools
278
+
279
+ | Tool | Description |
280
+ |------|-------------|
281
+ | `crystal_search` | Hybrid search across all memories |
282
+ | `crystal_remember` | Store a fact or observation |
283
+ | `crystal_forget` | Deprecate a memory by ID |
284
+ | `crystal_status` | Chunk count, provider, agents |
285
+ | `crystal_sources_add` | Add a directory for indexing |
286
+ | `crystal_sources_sync` | Re-index changed files |
287
+ | `crystal_sources_status` | Collection stats |
288
+
289
+ ## Migration
290
+
291
+ ### Legacy DB to LDM
292
+
293
+ ```bash
294
+ crystal migrate-db
295
+ ```
296
+
297
+ Copies the database to `~/.ldm/memory/crystal.db`. Verifies chunk count. Creates symlinks at the old path.
298
+
299
+ ### LanceDB to sqlite-vec
300
+
301
+ ```bash
302
+ node scripts/migrate-lance-to-sqlite.mjs --dry-run # check counts
303
+ node scripts/migrate-lance-to-sqlite.mjs # full migration
304
+ ```
305
+
306
+ Reads vectors directly from LanceDB. No re-embedding needed. ~5,000 chunks/sec on M4 Pro.
307
+
308
+ ### context-embeddings.sqlite
309
+
310
+ ```bash
311
+ node dist/migrate.js [--dry-run] [--provider openai]
312
+ ```
313
+
314
+ Import from the older context-embeddings format (requires re-embedding).
315
+
316
+ ## Project Structure
317
+
318
+ ```
319
+ memory-crystal/
320
+ src/
321
+ core.ts Pure logic, zero framework deps
322
+ cli.ts CLI wrapper (crystal command)
323
+ mcp-server.ts MCP server (Claude Code, Claude Desktop)
324
+ openclaw.ts OpenClaw plugin wrapper
325
+ cc-hook.ts Claude Code Stop hook (auto-capture)
326
+ ldm.ts LDM scaffolding and path resolution
327
+ summarize.ts Markdown session summary generation
328
+ crypto.ts AES-256-GCM + HMAC-SHA256 encryption
329
+ worker.ts Cloudflare Worker (encrypted dead drop)
330
+ poller.ts Relay poller (home machine side)
331
+ mirror-sync.ts DB mirror sync (device side)
332
+ migrate.ts Legacy migration tools
333
+ dev-update.ts Auto dev-update generation
334
+ skills/
335
+ memory/SKILL.md Agent skill definition
336
+ scripts/
337
+ migrate-lance-to-sqlite.mjs
338
+ dist/ Built output
339
+ ai/ Plans, dev updates, todos
340
+ ```
341
+
342
+ ## Design Decisions
343
+
344
+ **Why sqlite-vec over pgvector, Pinecone, Weaviate, etc.?**
345
+ No server. No Docker. No cloud dependency. One file. Works offline. Backupable with `cp`. The tradeoff is scale ... sqlite-vec works great up to ~500K vectors. Beyond that, consider dedicated vector stores.
346
+
347
+ **Why FTS5 + vectors instead of just vectors?**
348
+ Vectors alone miss exact keyword matches. "error code 403" should match conversations containing "403", not just semantically similar conversations about HTTP errors. Hybrid search catches both.
349
+
350
+ **Why RRF for fusion?**
351
+ Reciprocal Rank Fusion is simple, robust, and doesn't require score calibration between the two engines. Each engine ranks results independently. RRF merges based on rank position, not raw scores.
352
+
353
+ **Why recency weighting?**
354
+ Without it, old conversations dominate. A conversation from 3 days ago about your current project should outrank a conversation from 3 months ago about a different project, even if the old one is a slightly better semantic match.
355
+
356
+ **Why AES-256-GCM for relay encryption?**
357
+ Authenticated encryption. Ciphertext tampering is detected. No padding oracle attacks. Standard, auditable, widely implemented. Combined with HMAC-SHA256 signing for belt-and-suspenders integrity verification.
358
+
359
+ **Why a dead drop instead of direct device-to-device sync?**
360
+ Devices aren't always online at the same time. A dead drop decouples sender and receiver. Your laptop drops encrypted blobs whenever it captures. Your desktop picks them up whenever it polls. No NAT traversal, no port forwarding, no peer discovery.
361
+
362
+ **Why LanceDB dual-write?**
363
+ Safety net during the sqlite-vec transition. Once sqlite-vec is proven stable at scale, LanceDB will be removed. Until then, both stores receive writes so rollback is possible.
364
+
365
+ ## Roadmap
366
+
367
+ - **Phase 1** ... Complete. Local memory with CLI, MCP, OpenClaw plugin, Claude Code hook.
368
+ - **Phase 2a** ... Complete. Source file indexing + QMD hybrid search (sqlite-vec + FTS5 + RRF).
369
+ - **Phase 2b** ... Complete. Historical session backfill (152K+ chunks).
370
+ - **Phase 2c** ... Complete. LDM scaffolding, JSONL archive, markdown summaries, relay merge.
371
+ - **Phase 3** ... Planned. Hosted relay deployment.
372
+ - **Phase 4** ... Planned. Remote MCP, GPT Action, multi-agent access.
373
+ - **Cleanup** ... Planned. Remove LanceDB once sqlite-vec is proven stable.
374
+
375
+ ---
376
+
377
+ Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code CLI (Claude Opus 4.6).
378
+
379
+ Search architecture inspired by [QMD](https://github.com/tobi/qmd) by Tobi Lutke (MIT, 2024-2026).
@@ -0,0 +1,70 @@
1
+ # 2026-02-25 — Phase 2 Architecture Pivot: Cloud Mirror → Ephemeral Relay
2
+
3
+ **Agent:** cc-air (Claude Code on MacBook Air)
4
+ **Repo:** `memory-crystal`
5
+ **Branch:** `cc-air/phase2-relay` (new, replaces `cc-mba/phase2-worker`)
6
+
7
+ ## What Happened
8
+
9
+ Built the Phase 2 Cloudflare Worker as a cloud database mirror (D1 + Vectorize + R2 + FTS5). Full search on the Worker, persistent data in Cloudflare, ~350 lines of Worker code. It worked — built clean, types passed.
10
+
11
+ Then Parker asked the right questions:
12
+ 1. What's the security risk of data in transit?
13
+ 2. What's the security risk of data sitting on Cloudflare?
14
+ 3. What happens when sync breaks and we lose context?
15
+
16
+ Read the Dream Weaver Protocol architecture. The entire memory system is designed around sovereignty — "your memory, your machine, your rules." Putting conversation data persistently on Cloudflare violates that principle. And it's not just code conversations — it's everything said to every agent. Personal.
17
+
18
+ Parker's core requirement: "I gotta be able to leave the house." Take the laptop, talk to cc-air, and have those conversations make it back to the master crystal on the Mini. But the data shouldn't *live* in the cloud. It should pass through.
19
+
20
+ ## The Pivot
21
+
22
+ **Cloud Mirror (old):** Worker is a searchable database. Data persists on Cloudflare. Air searches the cloud. Cloudflare sees plaintext. OpenAI API key on Cloudflare.
23
+
24
+ **Ephemeral Relay (new):** Worker is a dead drop. Data passes through encrypted, gets picked up, gets deleted. Nothing persists. Worker can't read what it holds. ~80 lines of code.
25
+
26
+ ### Architecture
27
+
28
+ ```
29
+ Any Device → encrypt → Worker (dead drop) → Mini picks up → decrypt → embed → master crystal
30
+
31
+ Mini → encrypt mirror snapshot → Worker → Device picks up → decrypt → local read-only mirror
32
+ ```
33
+
34
+ - **Mini is the master.** All embedding, indexing, and search intelligence lives there.
35
+ - **Devices are read-only.** They capture raw conversations and search against a local mirror. They never write to the crystal.
36
+ - **Worker is a mailbox.** Encrypted blobs in, encrypted blobs out. TTL auto-expires everything in 24h. No D1, no Vectorize, no search, no API keys.
37
+
38
+ ### Security Hardening
39
+
40
+ - **AES-256-GCM** client-side encryption. Key lives only on trusted machines, never on Cloudflare.
41
+ - **HMAC-SHA256 drop signing.** Mini verifies every blob came from a trusted device.
42
+ - **Random 96-bit nonces.** Prepended to ciphertext, collision-safe for billions of operations.
43
+ - **SHA-256 mirror integrity.** Devices verify DB snapshots before replacing their local mirror.
44
+ - **Key rotation mechanism.** 24h overlap for in-flight blobs, no re-encryption needed (ephemeral data).
45
+ - **Explicit threat model.** Every risk documented with mitigation and residual risk.
46
+
47
+ ## Why This Matters Beyond Us
48
+
49
+ This isn't just cc-air talking to the Mini. Any device, any agent, any interface can use the same pattern. Phone, laptop, tablet. Every conversation captured, encrypted, relayed, embedded, searchable. One master crystal, many readers. Sovereign memory that travels with you.
50
+
51
+ Open source. Auditable. No subscription. No cloud lock-in.
52
+
53
+ ## Files
54
+
55
+ | File | Status |
56
+ |------|--------|
57
+ | `ai/plan/phase2-ephemeral-relay.md` | NEW — full architecture spec with security model |
58
+ | `ai/plan/dev-conventions-note.md` | NEW — documented existing dev conventions |
59
+ | `ai/dev-updates/2026-02-25--cc-air--phase2-worker-build.md` | EXISTING — documents the original cloud mirror build |
60
+ | `src/worker.ts` | TO BE REBUILT — ephemeral relay (~80 lines replacing ~350) |
61
+ | `src/crypto.ts` | TO BE CREATED — AES-256-GCM + HMAC-SHA256 + HKDF |
62
+ | `src/cc-hook.ts` | TO BE MODIFIED — encrypt + relay instead of direct ingest |
63
+ | `src/poller.ts` | TO BE CREATED — Mini-side pickup + ingest |
64
+ | `src/mirror-sync.ts` | TO BE CREATED — Device-side mirror pull |
65
+ | `wrangler.toml` | TO BE SIMPLIFIED — R2 only, no D1/Vectorize |
66
+ | `schema.sql` | TO BE REMOVED — no database on Worker |
67
+
68
+ ## Previous Branch
69
+
70
+ `cc-mba/phase2-worker` — contains the cloud mirror build. Preserved so people can see the progression from "persistent cloud DB" to "ephemeral encrypted relay." The thinking is the product.
@@ -0,0 +1,72 @@
1
+ # 2026-02-25 — Phase 2 Worker Build
2
+
3
+ **Agent:** cc-a (Claude Code on MacBook Air)
4
+ **Repo:** `memory-crystal`
5
+ **Branch:** `cc-mba/phase2-worker`
6
+
7
+ ## What Was Done
8
+
9
+ ### 1. Read & Understood the Entire Codebase
10
+ - `core.ts` (1,346 lines) — hybrid search: sqlite-vec + FTS5 + RRF fusion + recency weighting
11
+ - `mcp-server.ts` — 7 MCP tools for Claude Code (search, remember, forget, status, sources)
12
+ - `cc-hook.ts` — Stop hook with byte-offset watermarking, batched ingestion
13
+ - `openclaw.ts` — Lesa's agent_end hook with compaction detection
14
+ - `resolveConfig()` — 4-level config resolution (params → env → .env → 1Password)
15
+
16
+ ### 2. Built the Cloudflare Worker (`src/worker.ts`)
17
+ - REST API endpoints: `/search`, `/ingest`, `/remember`, `/forget`, `/status`, `/health`
18
+ - Sync endpoints: `/sync/push` (Mini uploads snapshot to R2), `/sync/pull` (Mini pulls new remote writes)
19
+ - Auth: bearer token per agent (cc-a, cc, lesa) mapped to agent_id
20
+ - Search: FTS5 on D1 + Vectorize cosine similarity + RRF fusion + recency weighting
21
+ - Deduplication: SHA-256 hash check before ingest
22
+ - Embedding: OpenAI text-embedding-3-small via fetch
23
+
24
+ ### 3. Created D1 Schema (`schema.sql`)
25
+ - `chunks` table with hash-based dedup, agent_id, source tracking
26
+ - `chunks_fts` virtual table (FTS5 with Porter stemming)
27
+ - Triggers to keep FTS in sync with chunks
28
+ - `memories` table for explicit remember/forget
29
+ - `capture_state` and `sync_log` for sync tracking
30
+
31
+ ### 4. Created Wrangler Config (`wrangler.toml`)
32
+ - D1 binding (needs database_id after creation)
33
+ - Vectorize binding (memory-crystal-chunks, 1536 dimensions)
34
+ - R2 bucket binding (memory-crystal-snapshots)
35
+ - Secrets: AUTH_TOKEN_CC_A, AUTH_TOKEN_CC, AUTH_TOKEN_LESA, OPENAI_API_KEY
36
+
37
+ ### 5. Added Remote Mode to Core (`core.ts`)
38
+ - New `RemoteCrystal` class — same interface as `Crystal`, talks to Worker via HTTP
39
+ - `createCrystal()` factory — returns `RemoteCrystal` when `remoteUrl` + `remoteToken` are set, otherwise local `Crystal`
40
+ - `chunkText()` method on RemoteCrystal for cc-hook compatibility
41
+
42
+ ### 6. Updated MCP Server (`mcp-server.ts`)
43
+ - Uses `createCrystal()` — auto-detects remote mode
44
+ - Shows "(REMOTE)" in status when using cloud mirror
45
+ - Source indexing tools gracefully return "not available in remote mode"
46
+
47
+ ### 7. Updated CC Hook (`cc-hook.ts`)
48
+ - Uses `createCrystal()` for remote ingestion support
49
+ - Configurable `CRYSTAL_AGENT_ID` env var (defaults to 'claude-code', set to 'cc-a' on MacBook Air)
50
+ - Type-safe with `Crystal | RemoteCrystal` union
51
+
52
+ ### 8. Build Verification
53
+ - `npm run build:local` — succeeds, all types pass
54
+ - `npm run build:worker` — succeeds, worker.js = 12 KB
55
+
56
+ ## Current Status
57
+
58
+ **Code: COMPLETE.** All source files written and building clean.
59
+
60
+ **Not yet deployed.** Needs Cloudflare resources created and secrets set.
61
+
62
+ ## Files Changed/Created
63
+
64
+ | File | Action |
65
+ |------|--------|
66
+ | `src/worker.ts` | NEW — Cloudflare Worker |
67
+ | `schema.sql` | NEW — D1 database schema |
68
+ | `wrangler.toml` | NEW — Cloudflare config |
69
+ | `src/core.ts` | MODIFIED — added RemoteCrystal + createCrystal() |
70
+ | `src/mcp-server.ts` | MODIFIED — remote mode support |
71
+ | `src/cc-hook.ts` | MODIFIED — remote mode + configurable agent_id |
72
+ | `package.json` | MODIFIED — added build:worker and build:local scripts |
@@ -0,0 +1,49 @@
1
+ # Phase 2 Implementation
2
+
3
+ *Dev update by cc-mini, 2026-02-26 10:25 PST*
4
+
5
+ **Branch:** `mini/phase2-relay`
6
+
7
+ ## What happened
8
+
9
+ Built and merged Memory Crystal Phase 2. The system now produces all three file types (JSONL transcripts, MD session summaries, vector DB) and manages the full `~/.ldm/` directory tree. Also merged cc-air's ephemeral relay code and expanded the poller to reconstruct remote agent file trees.
10
+
11
+ ## New modules
12
+
13
+ - **`src/ldm.ts`** -- Central LDM path resolution and scaffolding. `getAgentId()`, `ldmPaths()`, `scaffoldLdm()`, `ensureLdm()`. Every other file imports paths from here.
14
+ - **`src/summarize.ts`** -- MD session summary generation. Two modes: `simple` (no API call, first message becomes title) and `llm` (calls gpt-4o-mini for title + summary + topics). Controlled by `CRYSTAL_SUMMARY_MODE` env var.
15
+
16
+ ## Changes to existing modules
17
+
18
+ - **`src/core.ts`** -- `resolveConfig()` checks `~/.ldm/memory/crystal.db` first, falls back to legacy path. Added `RemoteCrystal` class and `createCrystal()` factory from relay merge.
19
+ - **`src/cli.ts`** -- Added `crystal init [--agent]` and `crystal migrate-db` subcommands. Init scaffolds the full directory tree. Migrate copies the DB, verifies chunk count, creates symlink.
20
+ - **`src/cc-hook.ts`** -- Three new behaviors after each capture: (1) archive JSONL transcript to `~/.ldm/agents/{id}/transcripts/`, (2) generate MD session summary to `sessions/`, (3) relay mode from cc-air merge (encrypt + drop at Worker when env vars set).
21
+ - **`src/poller.ts`** -- After decrypting relay drops, now reconstructs the remote agent's full file tree: JSONL transcript, MD summary, daily breadcrumb. This means the Mini has everything even if a device is lost.
22
+ - **`src/mirror-sync.ts`** -- Updated paths to use `ldmPaths()` instead of hardcoded `~/.openclaw/memory-crystal/`.
23
+ - **`src/dev-update.ts`** -- Writes to each repo's own `ai/` folder instead of centralized `wip-dev-updates`. Disabled the git add/commit/push to the old repo.
24
+ - **`src/mcp-server.ts`** -- Uses `createCrystal()` factory. Remote mode guards on source indexing operations.
25
+
26
+ ## Portability fix
27
+
28
+ All hardcoded `/Users/lesa` and `/Users/parker` HOME fallbacks replaced with `process.env.HOME || ''` across every source file. The old fallbacks were cc-air writing `/Users/parker` and the Mini having `/Users/lesa`. Neither matters in practice (HOME is always set), but it was wrong for anyone else installing.
29
+
30
+ ## Relay merge
31
+
32
+ Merged `origin/cc-air/phase2-relay` into the branch. cc-air built the ephemeral encrypted relay: `src/crypto.ts` (AES-256-GCM + HMAC), `src/worker.ts` (Cloudflare dead drop), `src/poller.ts` (Mini-side ingest), `src/mirror-sync.ts` (device-side DB pull). One conflict in cc-hook.ts (imports + dev-update section), resolved to keep both relay mode and the new summary/archive code.
33
+
34
+ ## ai/ folder cleanup
35
+
36
+ Established the inbox convention: `ai/todos/inboxes/{recipient}/`. Each agent or human gets an inbox. Moved salience-research from `artifacts/` to `ai/notes/`. Added `PUNCHLIST.md` for blockers to ship. Updated `wip-dev-resources/DEVELOPMENT-PROCESS.md` with the new convention.
37
+
38
+ ## Verified
39
+
40
+ - `npm run build` (local + worker): zero errors, 12 entry points
41
+ - `crystal status`: 159,574 chunks from existing DB
42
+ - `crystal init --agent cc-mini`: scaffolds `~/.ldm/` correctly
43
+ - `crystal search "test"`: returns results
44
+ - `node poller.js --status`: "not configured" (correct)
45
+ - `node mirror-sync.js --status`: shows state
46
+
47
+ ## What's next
48
+
49
+ Parker's moves: run `crystal migrate-db`, deploy plugin, merge to main, release. Relay setup (Cloudflare) when ready. See `ai/todos/inboxes/parker/` and `ai/todos/PUNCHLIST.md`.