recallmem 0.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.
package/README.md ADDED
@@ -0,0 +1,735 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="./public/logo-hero-dark.svg">
4
+ <img src="./public/logo-hero.svg" alt="RecallMEM" width="320">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <strong>Persistent personal AI.</strong> Powered by Gemma 4 running locally on your own machine.
10
+ <br> <br>
11
+ This is not a chatbot (chatbots forget you) & this is not an agent (agents don't remember you). <br> <br> This IS a private AI, built on a deterministic memory framework where the LLM never touches your data.
12
+ </p>
13
+
14
+ <p align="center">
15
+ Two products in one repo:
16
+ <br>
17
+ <strong>👤 For users:</strong> install with <code>npx recallmem</code> and start chatting with an AI that actually remembers you.
18
+ <br>
19
+ <strong>👨‍💻 For developers:</strong> fork it and build your own AI app on top of the memory framework in <code>lib/</code>.
20
+ </p>
21
+
22
+ ```bash
23
+ npx recallmem
24
+ ```
25
+
26
+ That's the install. One command. The CLI handles the rest. It clones the repo, sets up the database, pulls the local AI models, writes the config file, opens the chat in your browser. If you've already got Node, Postgres, and Ollama installed, you're chatting with your own private AI in about 5 minutes.
27
+
28
+ <p align="center">
29
+ <img src="./public/screenshots/demo.png" alt="RecallMEM chat UI showing the AI remembering the user's name across conversations" width="900">
30
+ </p>
31
+
32
+ <p align="center">
33
+ <em>Two chats. Different sessions. The AI remembers.</em>
34
+ </p>
35
+
36
+ ---
37
+
38
+ ## Why I built this
39
+
40
+ I wanted my own private AI for the kind of conversations I don't want sitting on someone else's server. Personal stuff. The stuff you'd actually want a real friend to help you think through.
41
+
42
+ The default model is **Gemma 4** (Google's open weights model that just dropped, Apache 2.0) running locally via Ollama. You can pick any size from E2B (runs on a phone) up to the 31B Dense (best quality, needs a workstation). Or skip Ollama entirely and bring your own API key for Claude, GPT, Groq, Together, OpenRouter, or anything OpenAI-compatible. Your call.
43
+
44
+ The thing is, the memory is the actual differentiator. Not the model. Not the UI. The memory. The AI builds a profile of who you are over time. It extracts facts after every conversation. It vector-searches across every chat you've ever had to find relevant context. By the time you've used it for a week, it knows you better than ChatGPT ever will, because ChatGPT forgets you the second you close the tab.
45
+
46
+ <details>
47
+ <summary><strong>The longer version (what's wrong with every other "private AI" tool)</strong></summary>
48
+
49
+ Here's the problem with every "private AI" tool I tried: they all fall into one of three buckets.
50
+
51
+ 1. **Local chat UIs for Ollama.** Look pretty, but the AI has zero memory between conversations. Every chat is a stranger.
52
+ 2. **Memory libraries on GitHub.** Powerful, but they're SDKs. You have to build the whole UI yourself.
53
+ 3. **Cloud-based memory products like Mem0.** Have the full feature set, but your data goes to their servers. Defeats the whole point.
54
+
55
+ There's a gap right in the middle: a **complete personal AI app with real working memory that runs 100% on your machine**. So I built it.
56
+
57
+ </details>
58
+
59
+ ---
60
+
61
+ ## What it does
62
+
63
+ Persistent memory across every chat (profile + facts + vector search) with **temporal awareness** so the model knows what's current vs historical. Auto-extracts facts in real time, retires stale ones when the truth changes, stamps every memory with dates. Vector search over every past chat. Memory inspector you can edit. Custom rules. Wipe memory unrecoverably. File uploads (images, PDFs, code). Web search when using Anthropic. Bring your own LLM (Ollama, Anthropic, OpenAI, or any OpenAI-compatible API). Warm Claude-style dark mode.
64
+
65
+ <details>
66
+ <summary><strong>Full feature list</strong></summary>
67
+
68
+ - **Persistent memory across every chat.** Three layers: a synthesized profile of who you are, an extracted facts table, and vector search over all past conversations.
69
+ - **Live fact extraction.** Facts get extracted after every assistant reply, not just when the chat ends. Say "my birthday is 11/27" and refresh `/memory` a moment later, it's already there. Always uses the local FAST_MODEL so cloud users don't get billed per turn.
70
+ - **Temporal awareness solves context collapse.** Every fact is stamped with a `valid_from` date. When new information contradicts an old fact ("left Acme" replaces "works at Acme"), the old fact gets retired automatically. The model always sees what's current.
71
+ - **Self-healing categories.** Facts re-route to the correct category after every chat, edit, or delete. No LLM, just a deterministic loop. So when the categorizer improves, your existing memory improves with it.
72
+ - **Resumed-conversation markers.** Open a chat from last week and continue it, the AI sees a system marker like `[Conversation resumed 6 days later]` so it knows time passed and earlier turns are historical.
73
+ - **Dated recall.** When the vector search pulls relevant chunks from past chats, each one is prefixed with the date it came from so the model can tell history from the present.
74
+ - **Auto-builds your profile** from the extracted facts, with date stamps in every section. Updates after every reply.
75
+ - **Vector search across past conversations.** Ask about something you discussed last month, the AI finds it and uses it as context.
76
+ - **Memory inspector page.** View, edit, or delete every fact, with collapsible category sections and a search filter for navigating long lists.
77
+ - **Sidebar chat search.** Toggle between vector search (semantic, needs Ollama for embeddings) and text search (literal ILIKE on titles + transcripts, instant). Both search inside the conversations, not just titles.
78
+ - **Web search toggle.** When you're using an Anthropic provider, a globe button next to the input lets Claude actually browse the web. Hidden for Ollama since local models don't have it.
79
+ - **Custom rules.** Tell the AI how you want to be talked to. "Don't gaslight me." "I have dyslexia, no bullet points." "Don't add disclaimers." It applies them in every chat.
80
+ - **Wipe memory unrecoverably.** `DELETE` + `VACUUM FULL` + `CHECKPOINT`. Gone for good at the database level.
81
+ - **File uploads.** Drag and drop images, PDFs, code, text. Gemma 4 handles vision natively.
82
+ - **Warm dark mode.** Claude-style charcoal palette via CSS variables, persisted across refreshes with no flash-of-light.
83
+ - **Chat history sidebar** with date grouping, pinned chats, and the search toggle described above.
84
+ - **Markdown rendering** for headings, code blocks, tables.
85
+ - **Streaming responses** with smooth typewriter rendering.
86
+ - **Bring any LLM you want.** Local Gemma 4 via Ollama, or plug in Anthropic (Claude), OpenAI (GPT), or any OpenAI-compatible API (Groq, Together, OpenRouter, Mistral, vLLM, LM Studio, etc).
87
+ - **Test connection** for cloud providers before saving the API key, so you don't find out your key is wrong mid-chat.
88
+
89
+ </details>
90
+
91
+ ---
92
+
93
+ ## How is this different?
94
+
95
+ <details>
96
+ <summary><strong>Comparison table vs ChatGPT, Claude.ai, and Mem0</strong></summary>
97
+
98
+ | | RecallMEM | ChatGPT / Claude.ai | Mem0 |
99
+ |---|---|---|---|
100
+ | **Runs locally** | ✅ | ❌ | ❌ |
101
+ | **Memory retrieval is deterministic (no LLM tool calls)** | ✅ | ❌ | ❌ |
102
+ | **Persistent memory across chats** | ✅ | partial | ✅ |
103
+ | **Temporal awareness (memories know when they were true)** | ✅ | ❌ | ❌ |
104
+ | **Auto-retires stale facts when truth changes** | ✅ | ❌ | ❌ |
105
+ | **You can edit / delete memories** | ✅ | partial | ✅ |
106
+ | **Vector search over past chats** | ✅ | ❌ | ✅ |
107
+ | **Custom rules / behavior** | ✅ | ✅ | ❌ |
108
+ | **Bring your own LLM (any provider)** | ✅ | ❌ | ❌ |
109
+ | **Use local models (Gemma 4, Llama, etc)** | ✅ | ❌ | ❌ |
110
+ | **No account / no signup** | ✅ | ❌ | ❌ |
111
+ | **Free** | ✅ | partial | partial |
112
+ | **Source available** | ✅ Apache 2.0 | ❌ | partial |
113
+
114
+ </details>
115
+
116
+ <details>
117
+ <summary><strong>The actual differentiator nobody talks about (deterministic memory)</strong></summary>
118
+
119
+ The thing nobody is doing right is **how memory is read and written**.
120
+
121
+ In ChatGPT and Claude.ai with memory turned on, the LLM is in charge of memory. The model decides when to remember something during your conversation. The model decides what to remember. The model decides what to retrieve when you ask a question. The whole memory layer is implemented as model behavior. You're trusting the LLM to be a librarian, and LLMs are not librarians. They hallucinate.
122
+
123
+ RecallMEM does it backwards. **The chat LLM never touches your memory database.** Not for reads, not for writes. The LLM only ever sees a system prompt that's already been assembled by deterministic TypeScript and SQL. Here's the actual flow:
124
+
125
+ **When you send a message (memory READ path, 100% deterministic):**
126
+
127
+ 1. Plain SQL `SELECT` pulls your profile from `s2m_user_profiles`
128
+ 2. Plain SQL `SELECT` pulls your top *active* facts from `s2m_user_facts` (retired facts are excluded automatically)
129
+ 3. Each fact is stamped with its `valid_from` date so the model can reason about timelines
130
+ 4. EmbeddingGemma converts your message to a 768-dim vector (math, not generation)
131
+ 5. pgvector cosine similarity search ranks chunks from past conversations
132
+ 6. Each retrieved chunk is stamped with its source-chat date (`[from conversation on 2026-03-12]`) so the model can tell history from now
133
+ 7. If the chat is being resumed after a multi-hour gap, a one-time system marker like `[Conversation resumed 6 days later]` gets injected before the new user turn
134
+ 8. TypeScript template assembles all of it into a system prompt
135
+ 9. **Then** the chat LLM gets called, with the assembled context already in its prompt
136
+
137
+ The chat LLM never queries the database. It can't decide what to retrieve. It can't pick which facts are relevant. It can't hallucinate a memory that doesn't exist, because if it's not in the prompt, it doesn't exist for the model. The retrieval is 100% deterministic SQL + cosine similarity. No LLM tool calls touching your memory store.
138
+
139
+ **After every assistant reply (memory WRITE path, LLM proposes, TypeScript validates):**
140
+
141
+ A small local LLM (Gemma 4 E4B via Ollama) runs in the background to extract candidate facts from the running transcript. This happens fire-and-forget after the stream closes, so you never wait for it. It always uses the local model regardless of which provider the chat itself is using, so cloud users (Claude, GPT) don't get billed per turn for extraction.
142
+
143
+ The same LLM call also returns the IDs of any **existing** facts the new conversation contradicts. So when you say "I just left Acme to start a new job," the extractor returns the new fact AND flags the old "User works at Acme" fact for retirement. The TypeScript layer flips those rows to `is_active=false` and stamps `valid_to=NOW()`. History is preserved, the active set always reflects current truth.
144
+
145
+ But here's the key: the LLM only **proposes** facts and supersession decisions. It cannot write to the database. The TypeScript layer is the actual gatekeeper, and it runs every candidate fact through six validation steps before storage:
146
+
147
+ 1. **Quality gate.** Conversations under 100 characters get zero facts extracted. The LLM never even sees them.
148
+ 2. **JSON parse validation.** If the LLM returns malformed JSON or no array, the entire batch is dropped.
149
+ 3. **Type validation.** Only strings survive. Objects, numbers, nested arrays, all rejected.
150
+ 4. **Garbage pattern filtering.** A regex filter catches the most common LLM hallucinations: meta-observations like "user asked about X", AI behavior notes like "AI suggested Y", non-facts like "not mentioned", mood observations like "had a good conversation", and anything under 10 characters.
151
+ 5. **Deduplication.** Case-insensitive normalized match against the entire facts table. Duplicates get dropped.
152
+ 6. **Categorization.** The category (Identity, Family, Work, Health, etc.) is decided by **keyword matching in TypeScript**, not by the LLM. The LLM has no say in how facts get organized.
153
+
154
+ After all six steps, the surviving facts get a plain SQL `INSERT`. And even then, you can edit or delete any fact in the Memory page if you don't agree with it.
155
+
156
+ **Why this matters:**
157
+
158
+ - **Predictability.** When you mention "my dog" in a chat, RecallMEM **always** retrieves the facts that match "dog" via cosine similarity. ChatGPT retrieves whatever the model decides to retrieve, which can vary run to run.
159
+ - **No hallucinated retrieval.** The LLM cannot remember something that isn't actually in your facts table. If it's not in the database, it's not in the prompt.
160
+ - **Auditability.** You can look at any chat and trace exactly which facts and chunks were loaded into the system prompt. With ChatGPT, you can't see what the model decided to surface from memory.
161
+ - **No prompt injection memory leaks.** The LLM in RecallMEM only sees what the deterministic layer feeds it. It can't query the rest of the database. With ChatGPT, the model has tool access to memory, which means a prompt injection attack could theoretically make it dump memory contents.
162
+ - **Your data, your database.** Memory is data you control, not behavior you have to trust the model to do correctly. You can write a script that queries Postgres directly, edit facts manually, run analytics on your own conversations.
163
+
164
+ This is the actual reason RecallMEM exists. Not "another local chat UI." A memory architecture where the LLM is intentionally not in charge.
165
+
166
+ </details>
167
+
168
+ ---
169
+
170
+ ## For developers (the memory framework)
171
+
172
+ Underneath the chat UI, RecallMEM is a **deterministic memory framework** you can fork and use in your own AI app. The whole `lib/` folder is intentionally framework-shaped. It's not a polished SDK with a public API contract, but it IS a working, opinionated memory architecture you can copy into your own project.
173
+
174
+ <details>
175
+ <summary><strong>What's in <code>lib/</code> and how to embed it in your app</strong></summary>
176
+
177
+ **The core files in `lib/`:**
178
+
179
+ ```
180
+ lib/
181
+ ├── memory.ts Memory orchestrator. Loads profile + facts + vector recall in parallel.
182
+ ├── prompts.ts Assembles the system prompt with all the memory context.
183
+ ├── facts.ts Fact extraction (LLM proposes) + validation (TypeScript decides).
184
+ ├── profile.ts Synthesizes a structured profile from the active facts.
185
+ ├── chunks.ts Splits transcripts into chunks, embeds them, runs vector search.
186
+ ├── chats.ts Chat CRUD + transcript serialization with the smart parser.
187
+ ├── post-chat.ts The post-chat pipeline (title gen, fact extract, profile rebuild, embed).
188
+ ├── rules.ts Custom user rules / instructions.
189
+ ├── embeddings.ts EmbeddingGemma calls via Ollama.
190
+ ├── llm.ts LLM router (Ollama, Anthropic, OpenAI, OpenAI-compatible).
191
+ └── db.ts Postgres pool + the configurable user ID resolver.
192
+ ```
193
+
194
+ **Embedding it into your own app:**
195
+
196
+ The lib functions default to a single-user setup (`user_id = "local-user"`) but you can wire in your own auth system with two function calls at startup:
197
+
198
+ ```typescript
199
+ import { Pool } from "pg";
200
+ import { configureDb, setUserIdResolver } from "./lib/db";
201
+
202
+ // Use your existing Postgres pool (or skip this and let lib/ create its own)
203
+ const myPool = new Pool({ connectionString: process.env.DATABASE_URL });
204
+ configureDb({ pool: myPool });
205
+
206
+ // Wire in your auth system. Called whenever a lib function needs the current user.
207
+ // Can be sync or async. Return whatever string identifies the user in your app.
208
+ setUserIdResolver(() => getCurrentUserFromMyAuthSystem());
209
+ ```
210
+
211
+ That's it. No other changes needed. Every lib function (`getProfile`, `getActiveFacts`, `searchChunks`, `storeFacts`, `rebuildProfile`, etc.) reads from the configured resolver. Your auth system stays in your code, the memory framework stays in `lib/`.
212
+
213
+ **Using the memory layer in a chat request:**
214
+
215
+ ```typescript
216
+ import { buildMemoryAwareSystemPrompt } from "./lib/memory";
217
+ import { runPostChatPipeline } from "./lib/post-chat";
218
+ import { createChat, updateChat } from "./lib/chats";
219
+
220
+ // 1. Build the system prompt from the user's memory
221
+ const systemPrompt = await buildMemoryAwareSystemPrompt(
222
+ userMessage,
223
+ currentChatId
224
+ );
225
+
226
+ // 2. Send to your LLM however you want (Ollama, Claude, GPT, whatever)
227
+ const response = await yourLLM.chat([
228
+ { role: "system", content: systemPrompt },
229
+ ...conversationHistory,
230
+ { role: "user", content: userMessage },
231
+ ]);
232
+
233
+ // 3. Save the chat
234
+ await updateChat(chatId, [...conversationHistory, { role: "assistant", content: response }]);
235
+
236
+ // 4. (Async) Run the post-chat pipeline to extract facts, rebuild profile, embed chunks
237
+ runPostChatPipeline(chatId);
238
+ ```
239
+
240
+ The memory framework doesn't care which LLM you use. It just assembles context. Bring your own model.
241
+
242
+ **The schema lives in `migrations/001_init.sql`.** Run it against any Postgres 17+ database with the pgvector extension installed. Tables are prefixed `s2m_` (for "speak2me," the project this came from). Rename them in the migration if you want a different prefix.
243
+
244
+ **License:** Apache 2.0. Fork it, modify it, ship it commercially. The only ask is that you preserve the copyright notice and the NOTICE file. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
245
+
246
+ </details>
247
+
248
+ ---
249
+
250
+ ## Quick start
251
+
252
+ ```bash
253
+ npx recallmem
254
+ ```
255
+
256
+ You need three things on your machine first: **Node.js 20+**, **Postgres 17 with pgvector**, and **Ollama** (optional, skip if you only want cloud providers). If any are missing, the CLI tells you exactly what to install for your OS.
257
+
258
+ <details>
259
+ <summary><strong>Architecture diagrams (system, memory layers, post-chat sequence)</strong></summary>
260
+
261
+ ### System architecture
262
+
263
+ ```mermaid
264
+ flowchart TB
265
+ Browser["Browser<br/>Chat UI<br/>localhost:3000"]
266
+ NextJS["Next.js App<br/>API routes + SSR"]
267
+ Postgres[("Postgres + pgvector<br/>localhost:5432<br/>Chats, facts, profile, embeddings")]
268
+ Ollama["Ollama<br/>localhost:11434<br/>Gemma 4 + EmbeddingGemma"]
269
+ Cloud{{"Optional: Cloud LLMs<br/>Anthropic / OpenAI / etc.<br/>Only if you add a provider"}}
270
+
271
+ Browser <-->|HTTP / SSE| NextJS
272
+ NextJS <-->|SQL + vector queries| Postgres
273
+ NextJS <-->|"/api/chat<br/>/api/embed"| Ollama
274
+ NextJS -.->|Optional API call| Cloud
275
+
276
+ style Cloud stroke-dasharray: 5 5
277
+ style Ollama fill:#dfe
278
+ style Postgres fill:#dfe
279
+ style NextJS fill:#dfe
280
+ style Browser fill:#dfe
281
+ ```
282
+
283
+ Everything in green runs on your machine. The dashed cloud box only activates if you explicitly add a cloud provider in settings. Otherwise, nothing leaves your computer. Ever.
284
+
285
+ ### The three-layer memory system
286
+
287
+ ```mermaid
288
+ flowchart LR
289
+ Chat[New chat message]
290
+ Memory["Memory loader<br/>(parallel)"]
291
+ Profile["Layer 1: Profile<br/>Synthesized summary<br/>(IDENTITY, FAMILY,<br/>WORK, HEALTH...)"]
292
+ Facts["Layer 2: Facts<br/>Top 50 atomic statements<br/>(pinned to system prompt)"]
293
+ Vector["Layer 3: Vector search<br/>Top 5 chunks from past<br/>conversations<br/>(semantic similarity)"]
294
+ Rules["User custom rules<br/>(behavior instructions)"]
295
+ Prompt["System prompt<br/>(profile + facts + recall + rules)"]
296
+ LLM[LLM]
297
+ Response[Streaming response]
298
+
299
+ Chat --> Memory
300
+ Memory --> Profile
301
+ Memory --> Facts
302
+ Memory --> Vector
303
+ Memory --> Rules
304
+ Profile --> Prompt
305
+ Facts --> Prompt
306
+ Vector --> Prompt
307
+ Rules --> Prompt
308
+ Prompt --> LLM
309
+ LLM --> Response
310
+ ```
311
+
312
+ Each layer does a different job:
313
+
314
+ - **Profile** loads instantly. It's the "who am I talking to" baseline. One database row, always loaded into every system prompt.
315
+ - **Facts** are atomic statements you can view, edit, and delete. Stored as individual rows. Pinned into the prompt every conversation.
316
+ - **Vector search** finds semantically relevant prose from any past conversation. Catches the stuff that doesn't fit cleanly into facts, like that idea you were working through three weeks ago.
317
+
318
+ Together, they let the AI know your name, your family, your job, AND remember the specific thing you mentioned a month ago when it becomes relevant.
319
+
320
+ ### What happens when you end a chat
321
+
322
+ ```mermaid
323
+ sequenceDiagram
324
+ actor User
325
+ participant UI as Chat UI
326
+ participant API as /api/chat/finalize
327
+ participant LLM
328
+ participant DB as Postgres
329
+
330
+ User->>UI: Click "New chat"
331
+ UI->>UI: Show "Saving memory..."
332
+ UI->>API: POST chatId
333
+ API->>LLM: Generate title (Gemma E4B)
334
+ LLM-->>API: "Discussing project ideas"
335
+ API->>DB: Save title
336
+ API->>LLM: Extract facts (Gemma E4B)
337
+ LLM-->>API: ["User's name is...", "User works at...", ...]
338
+ API->>DB: Insert new facts (deduped)
339
+ API->>DB: Rebuild profile from all facts
340
+ API->>API: Embed transcript chunks
341
+ API->>DB: Insert embeddings
342
+ API-->>UI: Done
343
+ UI->>UI: Clear chat, ready for next
344
+ ```
345
+
346
+ Click "New chat", wait a few seconds, and the next conversation immediately sees the new memory.
347
+
348
+ </details>
349
+
350
+ <details>
351
+ <summary><strong>Hardware requirements (which model fits which machine)</strong></summary>
352
+
353
+ The biggest variable is which LLM you pick. RecallMEM lets you choose.
354
+
355
+ ### Fully open source (Ollama + Gemma 4 locally)
356
+
357
+ | Setup | Model | RAM | Speed | Quality |
358
+ |---|---|---|---|---|
359
+ | Phone / iPad | Gemma 4 E2B | 8GB | Fast | Basic |
360
+ | MacBook Air / Mac Mini M4 | Gemma 4 E4B | 16GB | Fast | Good |
361
+ | Mac Studio M2+ | Gemma 4 26B MoE | 32GB+ | Very fast | Great |
362
+ | Workstation / server | Gemma 4 31B Dense | 32GB+ | Slower | Best |
363
+
364
+ The 26B MoE is what I use as the default. It's a Mixture of Experts model, so it only activates 3.8B parameters per token even though it has 26B total. Much faster than the 31B Dense, almost the same quality. Ranked #6 globally on the Arena leaderboard.
365
+
366
+ ### Using cloud providers (Claude, GPT, Groq, etc.)
367
+
368
+ If you don't want to run a local LLM at all, you can plug in any cloud API:
369
+
370
+ | Setup | RAM | Notes |
371
+ |---|---|---|
372
+ | Any laptop | ~4GB free | Just runs Postgres + the Node.js app + browser. The LLM runs on the provider's servers. |
373
+
374
+ You bring your own API key. The database, memory, profile, and rules still stay on your machine. Only the chat messages get sent to the provider.
375
+
376
+ **One thing to know:** when you use a cloud provider, your conversation goes to their servers. Your facts and profile get sent as part of the system prompt so the cloud LLM has context. This breaks the local-only guarantee for those specific conversations. Use Ollama for anything you want fully private.
377
+
378
+ </details>
379
+
380
+ <details>
381
+ <summary><strong>CLI commands</strong></summary>
382
+
383
+ ```bash
384
+ npx recallmem # Setup if needed, then start the app
385
+ npx recallmem init # Setup only (deps check, DB, models, env)
386
+ npx recallmem start # Start the server (assumes setup was done)
387
+ npx recallmem doctor # Check what's missing or broken
388
+ npx recallmem upgrade # Pull latest code, run pending migrations
389
+ npx recallmem version # Print version
390
+ npx recallmem --help # Show help
391
+ ```
392
+
393
+ The default `npx recallmem` is what you'll use 99% of the time. It's smart about its state. On the first run it sets everything up, on subsequent runs it just starts the server.
394
+
395
+ If something breaks, run `npx recallmem doctor` first. It tells you exactly what's wrong and how to fix it.
396
+
397
+ </details>
398
+
399
+ <details>
400
+ <summary><strong>Two ways to use it (just-run-it vs fork-and-hack)</strong></summary>
401
+
402
+ The `npx recallmem` command auto-detects which workflow you're in.
403
+
404
+ ### Workflow 1: Just run it (most users)
405
+
406
+ You want to use RecallMEM as your daily AI tool. You don't care about the code.
407
+
408
+ ```bash
409
+ npx recallmem
410
+ ```
411
+
412
+ The CLI:
413
+ 1. Detects nothing is installed yet
414
+ 2. Clones the repo to `~/.recallmem` (one-time, ~50MB)
415
+ 3. Runs `npm install` inside `~/.recallmem`
416
+ 4. Checks your dependencies (Postgres, pgvector, Ollama)
417
+ 5. Pulls the embedding model if missing
418
+ 6. Asks if you want to pull a chat model (~18GB, optional)
419
+ 7. Creates the database, runs migrations, writes the config file
420
+ 8. Starts the server and opens the chat in your browser
421
+
422
+ Subsequent runs are instant. Just `npx recallmem` and the chat opens.
423
+
424
+ To upgrade later when I ship a new version:
425
+
426
+ ```bash
427
+ npx recallmem upgrade
428
+ ```
429
+
430
+ That does a `git pull`, runs `npm install` if deps changed, and applies any pending migrations.
431
+
432
+ ### Workflow 2: Fork it and hack on it (developers)
433
+
434
+ You want to modify the code, contribute back, run your own variant.
435
+
436
+ ```bash
437
+ git clone https://github.com/RealChrisSean/RecallMEM.git
438
+ cd RecallMEM
439
+ npm install
440
+ npx recallmem
441
+ ```
442
+
443
+ The CLI detects you're already inside a recallmem checkout and uses your current directory instead of cloning to `~/.recallmem`. Hot reload works. Edits to the code are reflected immediately on the next dev server reload.
444
+
445
+ Same `npx recallmem` command. Different behavior because the CLI is smart about where it's running.
446
+
447
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the dev workflow.
448
+
449
+ **Testing:**
450
+
451
+ ```bash
452
+ npm test # run the suite once
453
+ npm test:watch # re-run on file change
454
+ ```
455
+
456
+ The test suite uses Vitest and currently covers the deterministic memory primitives (keyword inflection, the categorization router, and the regression cases that have bitten us in the past — `son` matching `Sonnet`, `work` matching `framework`, etc). It's intentionally narrow and fast (~150ms). New tests go in `test/unit/` and follow the same shape as `test/unit/facts.test.ts`. No DB or LLM required, pure functions only.
457
+
458
+ **Optional observability (Langfuse):**
459
+
460
+ If you're hacking on RecallMEM and want full trace timelines for every chat turn (memory build, LLM generation, fact extraction, supersession decisions, etc), there's a built-in Langfuse integration. It's a peer dependency, so it's NOT installed by default and zero cost when unused.
461
+
462
+ ```bash
463
+ npm install langfuse
464
+ ```
465
+
466
+ Then set these in `.env.local`:
467
+
468
+ ```
469
+ LANGFUSE_PUBLIC_KEY=pk-lf-...
470
+ LANGFUSE_SECRET_KEY=sk-lf-...
471
+ LANGFUSE_BASEURL=http://localhost:3000 # optional, defaults to cloud.langfuse.com
472
+ ```
473
+
474
+ Self-host Langfuse via Docker so traces stay on your machine. This is a developer-only debugging tool. Trace payloads include the actual user message content, so don't enable it on machines where conversation contents shouldn't leave the local environment.
475
+
476
+ </details>
477
+
478
+ <details>
479
+ <summary><strong>Where things live on disk (and how to fully uninstall)</strong></summary>
480
+
481
+ The default install location is `~/.recallmem`. Override with `RECALLMEM_HOME=/custom/path npx recallmem` if you want it somewhere else.
482
+
483
+ What's in `~/.recallmem`:
484
+
485
+ - The full RecallMEM source code (cloned from GitHub)
486
+ - `node_modules/` with all dependencies
487
+ - `.env.local` with your config
488
+ - The Next.js build output (when you run it)
489
+
490
+ What's NOT in `~/.recallmem`:
491
+
492
+ - Your conversations, facts, profile, embeddings, rules, and API keys. Those all live in your Postgres database at `/opt/homebrew/var/postgresql@17/` (Mac) or `/var/lib/postgresql/` (Linux). The Postgres data directory is the actual source of truth.
493
+
494
+ To completely uninstall:
495
+
496
+ ```bash
497
+ rm -rf ~/.recallmem # Remove the app
498
+ dropdb recallmem # Remove the database (or use the in-app "Nuke everything" button first)
499
+ ```
500
+
501
+ </details>
502
+
503
+ ---
504
+
505
+ ## Privacy
506
+
507
+ If you only use Ollama, **nothing leaves your machine, ever**. You can air-gap the computer and it keeps working. If you add a cloud provider (Claude, GPT, etc.), only the chat messages and your assembled system prompt go to that provider's servers. Your database, embeddings, and saved API keys stay local.
508
+
509
+ <details>
510
+ <summary><strong>Privacy diagram + truly unrecoverable deletion</strong></summary>
511
+
512
+ ```mermaid
513
+ flowchart TB
514
+ subgraph Local["Your machine (always private)"]
515
+ DB[(Postgres database<br/>Chats, facts, profile, embeddings, API keys, rules)]
516
+ App[Next.js app]
517
+ Ollama_Box[Ollama]
518
+ end
519
+
520
+ subgraph CloudOpt["Optional cloud (only if you add a provider)"]
521
+ Anthropic[Anthropic API]
522
+ OpenAI_API[OpenAI API]
523
+ Other[Other LLM APIs]
524
+ end
525
+
526
+ User[You] <--> App
527
+ App <--> DB
528
+ App <--> Ollama_Box
529
+ App -.->|"Conversation messages<br/>+ system prompt<br/>(only if you pick a cloud provider)"| Anthropic
530
+ App -.-> OpenAI_API
531
+ App -.-> Other
532
+
533
+ style Local fill:#dfe
534
+ style CloudOpt stroke-dasharray: 5 5
535
+ ```
536
+
537
+ **Always on your machine, never sent anywhere:**
538
+ - Your chat history
539
+ - Your facts and profile
540
+ - Your custom rules
541
+ - Your vector embeddings
542
+ - Your saved API keys
543
+
544
+ **Sent only when you actively use a cloud provider:**
545
+ - The current conversation messages
546
+ - The system prompt (which includes your profile, facts, and rules so the cloud LLM has context)
547
+
548
+ ### Truly unrecoverable deletion
549
+
550
+ When you click "Wipe memory" or "Nuke everything" on the Memory page, the app runs:
551
+
552
+ 1. `DELETE` to remove rows from query results
553
+ 2. `VACUUM FULL <table>` to physically rewrite the table on disk and release the dead row space
554
+ 3. `CHECKPOINT` to force Postgres to flush WAL log files
555
+
556
+ After those three steps, the data is gone from the database in any practically recoverable way.
557
+
558
+ **One thing I want to be honest about:** filesystem-level forensic recovery (raw disk block scanning) is a separate problem. SSDs have wear leveling, so file overwrites don't always touch the original physical cells. The complete solution is **full-disk encryption** (FileVault on Mac, LUKS on Linux, BitLocker on Windows). With disk encryption and a strong login password, the data is genuinely unrecoverable. Not even Apple could read it.
559
+
560
+ </details>
561
+
562
+ ---
563
+
564
+ <details>
565
+ <summary><strong>What it doesn't do (yet), honest limitations</strong></summary>
566
+
567
+ I'm being honest about the limitations. This is v0.1.
568
+
569
+ - **No voice yet.** It's text only. I want to add Whisper for speech-to-text and Piper for text-to-speech, both local. On the roadmap.
570
+ - **Web search works on Anthropic and Ollama. OpenAI not yet.** Anthropic uses the native `web_search_20250305` tool, no setup. Ollama (Gemma) uses **Brave Search** as a backend, which needs a free API key (5 minute setup): sign up at [brave.com/search/api](https://brave.com/search/api), pick the Free tier (2,000 searches/month), and add `BRAVE_SEARCH_API_KEY=your_key_here` to your `.env.local`. Then restart RecallMEM. When you toggle web search on the chat UI, the first time you'll see a privacy modal explaining that Brave will see your message text but NOT your memory, profile, facts, or past conversations. If the key isn't set or the quota is exhausted, the toggle still works but the AI will tell you what to do instead of failing silently. OpenAI's native web search requires the Responses API path which isn't plumbed through yet.
571
+ - **No multi-user.** This is a personal app for one person on one machine. If you want a multi-user version, that's a separate fork.
572
+ - **Reasoning models (OpenAI o1/o3, Claude extended thinking) might have edge cases.** They use different API parameters that I don't fully handle yet. Standard chat models work fine.
573
+ - **OpenAI vision isn't fully wired up.** Gemma 4 (4B and up) handles images natively via Ollama. OpenAI uses a different format that I haven't plumbed through. Use Ollama or Anthropic for images.
574
+ - **No mobile app.** It's a web app you run locally. You access it from your browser at `localhost:3000`. A native iOS/Android app is theoretically possible but it's a separate project I haven't started.
575
+ - **Fact supersession is LLM-judged and conservative.** The local Gemma extractor decides whether a new fact contradicts an old one. It's intentionally cautious (only retires a fact when the replacement is unambiguous), so it might occasionally miss a real contradiction or, more rarely, retire something it shouldn't have. You can always inspect and edit/restore in the Memory page. For higher-stakes use cases, you'd want a stricter rule-based supersession layer on top, or a periodic profile-rebuild from full history.
576
+
577
+ </details>
578
+
579
+ <details>
580
+ <summary><strong>Tech stack</strong></summary>
581
+
582
+ - **Frontend / Backend:** Next.js 16 (App Router) + TypeScript + Tailwind CSS v4
583
+ - **Database:** Postgres 17 + pgvector (HNSW vector indexes)
584
+ - **Local LLM:** Ollama with Gemma 4 (E2B / E4B / 26B MoE / 31B Dense)
585
+ - **Embeddings:** EmbeddingGemma 300M (768 dimensions, runs in Ollama)
586
+ - **PDF parsing:** pdf-parse v2
587
+ - **Markdown rendering:** react-markdown + remark-gfm + @tailwindcss/typography
588
+ - **Cloud LLM transports (optional):** Anthropic Messages API, OpenAI Chat Completions, OpenAI-compatible
589
+
590
+ </details>
591
+
592
+ <details>
593
+ <summary><strong>Manual install (for the curious or for when <code>npx recallmem</code> can't be used)</strong></summary>
594
+
595
+ If you want to know what `npx recallmem` is doing under the hood, or you don't want to use the CLI for some reason, here's the manual install.
596
+
597
+ ### macOS
598
+
599
+ ```bash
600
+ # 1. Install Node.js
601
+ brew install node
602
+
603
+ # 2. Install Postgres 17 + pgvector
604
+ brew install postgresql@17 pgvector
605
+ brew services start postgresql@17
606
+
607
+ # 3. Install Ollama (skip if using cloud only)
608
+ brew install ollama
609
+ brew services start ollama
610
+
611
+ # 4. Pull the models
612
+ ollama pull embeddinggemma # ~600MB, REQUIRED
613
+ ollama pull gemma4:26b # ~18GB, recommended chat model
614
+ ollama pull gemma4:e4b # ~4GB, fast model for background tasks
615
+ ```
616
+
617
+ ### Linux (Ubuntu/Debian)
618
+
619
+ ```bash
620
+ # 1. Node.js
621
+ curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
622
+ sudo apt install -y nodejs
623
+
624
+ # 2. Postgres + pgvector
625
+ sudo apt install postgresql-17 postgresql-17-pgvector
626
+ sudo systemctl start postgresql
627
+
628
+ # 3. Ollama
629
+ curl -fsSL https://ollama.com/install.sh | sh
630
+
631
+ # 4. Pull models
632
+ ollama pull embeddinggemma
633
+ ollama pull gemma4:26b
634
+ ollama pull gemma4:e4b
635
+ ```
636
+
637
+ ### Windows
638
+
639
+ Use WSL2 with Ubuntu and follow the Linux steps. Native Windows works too but it's rougher.
640
+
641
+ ### Setup
642
+
643
+ ```bash
644
+ # 1. Clone the repo
645
+ git clone https://github.com/RealChrisSean/RecallMEM.git
646
+ cd RecallMEM
647
+
648
+ # 2. Install dependencies
649
+ npm install
650
+
651
+ # 3. Create the database
652
+ createdb recallmem
653
+
654
+ # 4. Run migrations
655
+ npm run migrate
656
+
657
+ # 5. Configure .env.local
658
+ cat > .env.local <<EOF
659
+ DATABASE_URL=postgres://$USER@localhost:5432/recallmem
660
+ OLLAMA_URL=http://localhost:11434
661
+ OLLAMA_CHAT_MODEL=gemma4:26b
662
+ OLLAMA_FAST_MODEL=gemma4:e4b
663
+ OLLAMA_EMBED_MODEL=embeddinggemma
664
+ EOF
665
+
666
+ # 6. Start the dev server
667
+ npm run dev
668
+ ```
669
+
670
+ Open [http://localhost:3000](http://localhost:3000).
671
+
672
+ </details>
673
+
674
+ <details>
675
+ <summary><strong>Troubleshooting (the real gotchas I hit)</strong></summary>
676
+
677
+ Stuff I've actually hit. If you run into something else, run `npx recallmem doctor` first. It tells you exactly what's broken.
678
+
679
+ **`createdb: command not found`**
680
+
681
+ Add Postgres to your PATH:
682
+ ```bash
683
+ export PATH="/opt/homebrew/opt/postgresql@17/bin:$PATH"
684
+ ```
685
+
686
+ **`extension "vector" is not available`**
687
+
688
+ You're running Postgres 16 or older. The `pgvector` Homebrew bottle only ships extensions for Postgres 17 and 18. Switch to `postgresql@17`. I learned this the hard way. The install error message is cryptic and the fix took me 30 minutes the first time.
689
+
690
+ **Ollama silently fails to pull a new model**
691
+
692
+ You've got a version mismatch between the Ollama CLI and the Ollama server. This bites you if you have both Homebrew Ollama AND the desktop Ollama app installed. Check `ollama --version`. Both client and server should match.
693
+
694
+ ```bash
695
+ brew upgrade ollama
696
+ pkill -f "Ollama" # kill the old desktop app server
697
+ brew services start ollama # start the new server from Homebrew
698
+ ```
699
+
700
+ **Gemma 4 31B is slow**
701
+
702
+ Two reasons:
703
+
704
+ 1. **Thinking mode is on.** The app already disables it via `think: false`, but if you bypass the app and call Ollama directly, you'll see slow responses. Gemma 4 spends a ton of tokens "thinking" before answering when it's enabled.
705
+ 2. **Dense vs MoE.** 31B Dense activates all 31B parameters per token. Switch to `gemma4:26b` (Mixture of Experts, only 3.8B active per token) for ~3-5x the speed with minimal quality loss. This is what I use as the default.
706
+
707
+ **"My memory isn't being used in new chats"**
708
+
709
+ Make sure you click "New chat" (or switch to another chat in the sidebar) to trigger the synchronous "Saving memory..." finalize step. If you just refresh the browser without ending the chat, the post-chat pipeline runs as a best-effort `sendBeacon()` and may not finish before the next chat starts.
710
+
711
+ The fix: always click "New chat" or switch chats in the sidebar before closing the browser if you said something you want remembered.
712
+
713
+ </details>
714
+
715
+ ---
716
+
717
+ ## Contributing
718
+
719
+ Forks, PRs, bug reports, ideas, all welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the dev setup and how the codebase is organized.
720
+
721
+ If you build something cool on top of RecallMEM, I'd love to hear about it.
722
+
723
+ ## License
724
+
725
+ Apache License 2.0. See [LICENSE](./LICENSE) for the full text and [NOTICE](./NOTICE) for third-party attributions. You can use, modify, fork, and redistribute this for any purpose, personal or commercial. The license includes a patent grant and the standard "no warranty, no liability" disclaimer.
726
+
727
+ ## Status
728
+
729
+ This is v0.1. It works. I use it every day.
730
+
731
+ It's also not "production ready" in the corporate sense. There's no CI, no error monitoring, no SLA. There's a small Vitest test suite that covers the deterministic memory primitives (keyword routing, inflection, regression cases), but it's intentionally narrow. If you want to use it as your daily AI tool, fork it, make it yours, and expect to read the code if something breaks. That's the deal.
732
+
733
+ I built RecallMEM because I wanted my own private AI. I'm sharing it because there's a real gap in the local AI ecosystem and someone needed to fill it. If this is useful to you, that's cool. If not, no hard feelings.
734
+
735
+ The repo: [github.com/RealChrisSean/RecallMEM](https://github.com/RealChrisSean/RecallMEM)