@199-bio/engram 0.6.0 → 0.7.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 +114 -314
- package/dist/index.js +92 -12
- package/dist/retrieval/hybrid.d.ts.map +1 -1
- package/dist/storage/database.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/consolidation/consolidator.ts +2 -2
- package/src/index.ts +101 -12
- package/src/retrieval/hybrid.ts +32 -8
- package/src/storage/database.ts +31 -18
package/README.md
CHANGED
|
@@ -2,41 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
**Give your AI a perfect memory.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Every conversation you have with your AI disappears the moment it ends. Names you've mentioned, preferences you've shared, the context of your life—all gone. You find yourself repeating the same information, re-explaining who people are, reminding it of things you've already said.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Engram changes that.
|
|
8
|
+
|
|
9
|
+
It gives your AI the ability to remember. Not just store text, but truly remember—the way you do. Important things stick. Trivial things fade. And everything connects to everything else.
|
|
8
10
|
|
|
9
11
|
> *An engram is a unit of cognitive information imprinted in a physical substance—the biological basis of memory.*
|
|
10
12
|
|
|
11
13
|
---
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## How It Works
|
|
14
16
|
|
|
15
|
-
Tell your AI
|
|
17
|
+
Tell your AI something once:
|
|
16
18
|
|
|
17
19
|
> "My colleague Sarah is allergic to shellfish and prefers window seats. She's leading the Q1 product launch."
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Weeks later, ask:
|
|
20
22
|
|
|
21
23
|
> "I'm booking a team lunch and flights for the offsite—what should I know?"
|
|
22
24
|
|
|
23
|
-
Engram connects the dots
|
|
25
|
+
Engram connects the dots. It remembers Sarah, her allergy, her seating preference, and her current workload. Your AI can now actually help—suggesting restaurants without shellfish, booking her a window seat, and noting she might be busy with the launch.
|
|
26
|
+
|
|
27
|
+
This isn't keyword matching. It's understanding.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Memory That Feels Real
|
|
32
|
+
|
|
33
|
+
Engram models memory the way your brain does.
|
|
34
|
+
|
|
35
|
+
**Things fade.** A memory from six months ago that you've never thought about becomes harder to find. But something important—a name, a birthday, a preference—stays accessible even as time passes.
|
|
24
36
|
|
|
25
|
-
**
|
|
37
|
+
**Recall strengthens.** Every time a memory surfaces, it becomes more permanent. The things you think about often become the things you'll never forget.
|
|
38
|
+
|
|
39
|
+
**Everything connects.** People link to places. Places link to events. When you ask about one thing, related things come along for the ride. Ask about Sarah, and her company, her projects, and her preferences all surface together.
|
|
26
40
|
|
|
27
41
|
---
|
|
28
42
|
|
|
29
43
|
## Quick Start
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
Install globally:
|
|
32
46
|
|
|
33
47
|
```bash
|
|
34
48
|
npm install -g @199-bio/engram
|
|
35
49
|
```
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
51
|
+
Add to **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
40
52
|
|
|
41
53
|
```json
|
|
42
54
|
{
|
|
@@ -52,383 +64,173 @@ npm install -g @199-bio/engram
|
|
|
52
64
|
}
|
|
53
65
|
```
|
|
54
66
|
|
|
55
|
-
**Claude Code
|
|
67
|
+
Or with **Claude Code**:
|
|
56
68
|
|
|
57
69
|
```bash
|
|
58
70
|
claude mcp add engram -- npx -y @199-bio/engram
|
|
59
71
|
```
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
That's it. Your AI now has memory.
|
|
73
|
+
That's it. Your AI now remembers.
|
|
64
74
|
|
|
65
75
|
---
|
|
66
76
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
Engram models memory like your brain does—important things stick, unimportant things fade, and everything connects.
|
|
70
|
-
|
|
71
|
-
```mermaid
|
|
72
|
-
flowchart LR
|
|
73
|
-
subgraph Input
|
|
74
|
-
A[Your Message] --> B[Remember]
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
subgraph Storage
|
|
78
|
-
B --> C[Memory Store]
|
|
79
|
-
B --> D[Knowledge Graph]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
subgraph Retrieval
|
|
83
|
-
E[Your Question] --> F[Recall]
|
|
84
|
-
F --> G[Keyword Search]
|
|
85
|
-
F --> H[Semantic Search]
|
|
86
|
-
F --> I[Graph Traversal]
|
|
87
|
-
G & H & I --> J[Combine Results]
|
|
88
|
-
end
|
|
77
|
+
## What You Can Do
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
D --> F
|
|
92
|
-
J --> K[Relevant Memories]
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Three Key Ideas
|
|
96
|
-
|
|
97
|
-
**1. Memories Fade Over Time**
|
|
98
|
-
|
|
99
|
-
Just like real memory, things you haven't thought about recently become harder to recall. But important or emotional memories resist fading.
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
Fresh memory (today) → Easy to recall
|
|
103
|
-
Old but important memory → Still accessible
|
|
104
|
-
Old, trivial memory → Fades away
|
|
105
|
-
```
|
|
79
|
+
Just talk naturally. Your AI handles the rest.
|
|
106
80
|
|
|
107
|
-
**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
**3. Everything Connects**
|
|
112
|
-
|
|
113
|
-
People, places, and things form a web of relationships. When you ask about Sarah, Engram also knows she works at Acme Corp and is leading the Q1 launch.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## The Memory Lifecycle
|
|
118
|
-
|
|
119
|
-
```mermaid
|
|
120
|
-
flowchart TD
|
|
121
|
-
subgraph "1. Capture"
|
|
122
|
-
A[Conversation] --> B[Extract Key Info]
|
|
123
|
-
B --> C[Store Memory]
|
|
124
|
-
B --> D[Create Entities]
|
|
125
|
-
B --> E[Link Relationships]
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
subgraph "2. Search"
|
|
129
|
-
F[Query] --> G[Find by Keywords]
|
|
130
|
-
F --> H[Find by Meaning]
|
|
131
|
-
F --> I[Follow Connections]
|
|
132
|
-
G & H & I --> J[Rank by Relevance + Recency]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
subgraph "3. Consolidate"
|
|
136
|
-
K[Raw Memories] --> L[Compress & Summarize]
|
|
137
|
-
L --> M[Detect Contradictions]
|
|
138
|
-
M --> N[Update Knowledge]
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
C --> F
|
|
142
|
-
J --> K
|
|
143
|
-
```
|
|
81
|
+
**Store memories** by mentioning things:
|
|
82
|
+
- "Remember that my anniversary is March 15th"
|
|
83
|
+
- "Sarah prefers async communication"
|
|
84
|
+
- "I'm allergic to penicillin"
|
|
144
85
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
With an API key, Engram can periodically compress old memories into summaries—like how sleep consolidates your memories. This keeps important information while reducing storage.
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
# Run consolidation manually
|
|
151
|
-
consolidate # Compresses old memories, finds contradictions
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## What Makes It Special
|
|
157
|
-
|
|
158
|
-
| Feature | Why It Matters |
|
|
159
|
-
|---------|----------------|
|
|
160
|
-
| **Hybrid Search** | Finds memories by keywords AND meaning |
|
|
161
|
-
| **Knowledge Graph** | Understands relationships between people, places, things |
|
|
162
|
-
| **Natural Forgetting** | Old, unimportant memories fade; important ones persist |
|
|
163
|
-
| **Strengthening** | Frequently recalled memories become permanent |
|
|
164
|
-
| **Consolidation** | Compresses old memories, detects contradictions |
|
|
165
|
-
| **Fast** | ~50ms to recall, feels instant |
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## How to Use
|
|
170
|
-
|
|
171
|
-
Just talk naturally. Your AI will remember what matters.
|
|
172
|
-
|
|
173
|
-
### Storing Memories
|
|
174
|
-
|
|
175
|
-
Say things like:
|
|
176
|
-
- "Remember that Sarah is allergic to shellfish"
|
|
177
|
-
- "My anniversary is March 15th"
|
|
178
|
-
- "I prefer morning meetings, never schedule anything before 9am"
|
|
179
|
-
|
|
180
|
-
The AI automatically extracts:
|
|
181
|
-
- **Importance**: Key facts get higher priority
|
|
182
|
-
- **Entities**: People, places, organizations mentioned
|
|
183
|
-
- **Relationships**: How entities connect to each other
|
|
184
|
-
|
|
185
|
-
### Recalling
|
|
186
|
-
|
|
187
|
-
Just ask:
|
|
86
|
+
**Recall memories** by asking:
|
|
188
87
|
- "What do you know about Sarah?"
|
|
88
|
+
- "What are my allergies?"
|
|
189
89
|
- "When is my anniversary?"
|
|
190
|
-
- "What are my meeting preferences?"
|
|
191
|
-
|
|
192
|
-
Results are ranked by:
|
|
193
|
-
- **Relevance**: How well it matches your question
|
|
194
|
-
- **Recency**: Recent memories surface first
|
|
195
|
-
- **Importance**: High-priority info stays accessible
|
|
196
|
-
|
|
197
|
-
### The Knowledge Graph
|
|
198
|
-
|
|
199
|
-
Engram doesn't just store text—it understands relationships:
|
|
200
|
-
|
|
201
|
-
- **People**: Sarah, John, Dr. Martinez
|
|
202
|
-
- **Places**: Office, Conference Room A, Seattle HQ
|
|
203
|
-
- **Organizations**: Acme Corp, Stanford, FDA
|
|
204
|
-
- **Connections**: Sarah → works at → Acme Corp
|
|
205
|
-
- **Observations**: Sarah is allergic to shellfish
|
|
206
|
-
|
|
207
|
-
This means when you ask about Sarah's work, Engram knows to also surface relevant information about Acme Corp.
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## The Tools
|
|
212
|
-
|
|
213
|
-
Your AI gets these capabilities:
|
|
214
90
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
| `forget` | Remove a memory |
|
|
220
|
-
| `create_entity` | Add a person, place, or concept |
|
|
221
|
-
| `observe` | Note something about an entity |
|
|
222
|
-
| `relate` | Connect two entities |
|
|
223
|
-
| `query_entity` | Get everything about someone/something |
|
|
224
|
-
| `list_entities` | See all people, places, etc. |
|
|
225
|
-
| `stats` | Check memory statistics |
|
|
226
|
-
| `consolidate` | Compress old memories, find contradictions |
|
|
227
|
-
| `engram_web` | Launch visual memory browser |
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Examples
|
|
232
|
-
|
|
233
|
-
### Personal Assistant
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
You: "Remember that my anniversary is March 15th and my partner loves Italian food"
|
|
237
|
-
AI: ✓ Stored. I've noted this as an important date with preferences.
|
|
238
|
-
|
|
239
|
-
You: "I need to plan something special next month"
|
|
240
|
-
AI: Your anniversary is coming up on March 15th! Given your partner's love for
|
|
241
|
-
Italian food, perhaps a nice Italian restaurant reservation?
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Relationship Context
|
|
245
|
-
|
|
246
|
-
```
|
|
247
|
-
You: "My friend David lives in Austin and always recommends great coffee shops."
|
|
248
|
-
AI: ✓ Stored. Created entity: David (person), with observation about Austin and coffee expertise.
|
|
249
|
-
|
|
250
|
-
You: "I'm visiting Austin next week"
|
|
251
|
-
AI: David is in Austin—he'd probably have great coffee shop recommendations!
|
|
252
|
-
Want me to remind you to ask him?
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Professional Memory
|
|
256
|
-
|
|
257
|
-
```
|
|
258
|
-
You: "John Chen is our VP of Engineering. He prefers async communication and hates meetings before 11am."
|
|
259
|
-
Claude: ✓ Stored. Created entity: John Chen (person, VP of Engineering).
|
|
260
|
-
|
|
261
|
-
You: "I need to schedule a sync with John"
|
|
262
|
-
Claude: Given John's preferences, I'd suggest a late morning slot, maybe 11:30am,
|
|
263
|
-
or an async Loom video if it doesn't require real-time discussion.
|
|
264
|
-
```
|
|
91
|
+
**Build a knowledge graph** of your world:
|
|
92
|
+
- People, places, organizations, and how they connect
|
|
93
|
+
- Observations about each entity
|
|
94
|
+
- Relationships that span your entire life
|
|
265
95
|
|
|
266
96
|
---
|
|
267
97
|
|
|
268
98
|
## Privacy
|
|
269
99
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
- All data stored locally in `~/.engram/`
|
|
273
|
-
- No external APIs required for core functionality
|
|
274
|
-
- Consolidation uses Anthropic API (optional, requires key)
|
|
275
|
-
- You own your data completely
|
|
100
|
+
Your memories stay on your machine. Everything is stored locally in `~/.engram/`. The only external call is optional—if you provide an API key, Engram can periodically compress old memories into summaries. But the core functionality works entirely offline.
|
|
276
101
|
|
|
277
102
|
---
|
|
278
103
|
|
|
279
|
-
##
|
|
104
|
+
## The Details
|
|
280
105
|
|
|
281
106
|
<details>
|
|
282
|
-
<summary>
|
|
107
|
+
<summary><strong>Available Tools</strong></summary>
|
|
283
108
|
|
|
284
|
-
|
|
109
|
+
Your AI gets these capabilities:
|
|
285
110
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
111
|
+
| Tool | Purpose |
|
|
112
|
+
|------|---------|
|
|
113
|
+
| `remember` | Store new information with importance, emotions, and timing |
|
|
114
|
+
| `recall` | Find relevant memories ranked by relevance and recency |
|
|
115
|
+
| `forget` | Remove a specific memory |
|
|
116
|
+
| `create_entity` | Add a person, place, or concept to the knowledge graph |
|
|
117
|
+
| `observe` | Record a fact about an entity |
|
|
118
|
+
| `relate` | Connect two entities (e.g., "works at", "married to") |
|
|
119
|
+
| `query_entity` | Get everything known about someone or something |
|
|
120
|
+
| `list_entities` | See all tracked people, places, and things |
|
|
121
|
+
| `stats` | View memory statistics |
|
|
122
|
+
| `consolidate` | Compress old memories and detect contradictions |
|
|
123
|
+
| `engram_web` | Launch a visual memory browser |
|
|
289
124
|
|
|
290
|
-
|
|
291
|
-
- **Retention**: How fresh is this memory?
|
|
292
|
-
- **Salience**: How important/emotional is it?
|
|
125
|
+
</details>
|
|
293
126
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
Q[Query] --> BM25
|
|
297
|
-
Q --> Semantic
|
|
298
|
-
Q --> Graph
|
|
127
|
+
<details>
|
|
128
|
+
<summary><strong>How Search Works</strong></summary>
|
|
299
129
|
|
|
300
|
-
|
|
301
|
-
Semantic --> RRF
|
|
302
|
-
Graph --> RRF
|
|
130
|
+
Engram uses three search methods simultaneously:
|
|
303
131
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
132
|
+
1. **Keywords** — SQLite FTS5 finds exact matches for names, dates, and phrases
|
|
133
|
+
2. **Meaning** — Neural embeddings find conceptually related content
|
|
134
|
+
3. **Connections** — The knowledge graph expands to related entities
|
|
135
|
+
|
|
136
|
+
Results are fused together, then adjusted for how recent and important each memory is. Fresh memories surface first. Important memories resist fading.
|
|
308
137
|
|
|
309
138
|
</details>
|
|
310
139
|
|
|
311
140
|
<details>
|
|
312
|
-
<summary>How Forgetting Works</summary>
|
|
141
|
+
<summary><strong>How Forgetting Works</strong></summary>
|
|
313
142
|
|
|
314
|
-
Memories follow an exponential decay curve
|
|
143
|
+
Memories follow an exponential decay curve:
|
|
315
144
|
|
|
316
145
|
```
|
|
317
146
|
Retention = e^(-time / stability)
|
|
318
147
|
```
|
|
319
148
|
|
|
320
|
-
|
|
321
|
-
- **time
|
|
322
|
-
- **stability**: Memory strength (increases each time you recall it)
|
|
149
|
+
- **Time** is days since the memory was last accessed
|
|
150
|
+
- **Stability** is memory strength, which increases each time you recall something
|
|
323
151
|
|
|
324
|
-
High-importance and
|
|
152
|
+
High-importance and emotionally significant memories decay slower. Frequently accessed memories become essentially permanent.
|
|
325
153
|
|
|
326
154
|
</details>
|
|
327
155
|
|
|
328
156
|
<details>
|
|
329
|
-
<summary>How Consolidation Works</summary>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
flowchart TD
|
|
333
|
-
A[Old Memories] --> B{Important?}
|
|
334
|
-
B -->|Yes| C[Keep as-is]
|
|
335
|
-
B -->|No| D[Group Similar]
|
|
336
|
-
D --> E[Summarize with AI]
|
|
337
|
-
E --> F[Create Digest]
|
|
338
|
-
F --> G[Archive Originals]
|
|
339
|
-
|
|
340
|
-
H[All Memories] --> I[Find Contradictions]
|
|
341
|
-
I --> J[Flag for Review]
|
|
342
|
-
```
|
|
157
|
+
<summary><strong>How Consolidation Works</strong></summary>
|
|
158
|
+
|
|
159
|
+
With an API key, Engram can compress old memories—like how sleep consolidates your experiences into long-term storage.
|
|
343
160
|
|
|
344
|
-
|
|
345
|
-
1. Groups related low-importance memories
|
|
161
|
+
1. Groups related low-importance memories together
|
|
346
162
|
2. Creates AI-generated summaries (digests)
|
|
347
163
|
3. Detects contradictory information
|
|
348
|
-
4. Archives
|
|
164
|
+
4. Archives the originals
|
|
349
165
|
|
|
350
|
-
|
|
166
|
+
This keeps storage efficient while preserving everything important.
|
|
351
167
|
|
|
352
168
|
</details>
|
|
353
169
|
|
|
354
170
|
<details>
|
|
355
|
-
<summary>Architecture</summary>
|
|
171
|
+
<summary><strong>Architecture</strong></summary>
|
|
356
172
|
|
|
357
173
|
```
|
|
358
174
|
engram/
|
|
359
175
|
├── src/
|
|
360
|
-
│ ├── index.ts # MCP server
|
|
361
|
-
│ ├── storage/
|
|
362
|
-
│
|
|
363
|
-
│ ├── graph/
|
|
364
|
-
│ │ └── knowledge-graph.ts
|
|
176
|
+
│ ├── index.ts # MCP server
|
|
177
|
+
│ ├── storage/database.ts # SQLite with temporal fields
|
|
178
|
+
│ ├── graph/knowledge-graph.ts
|
|
365
179
|
│ ├── retrieval/
|
|
366
180
|
│ │ ├── colbert.ts # Semantic search
|
|
367
|
-
│ │ └── hybrid.ts #
|
|
368
|
-
│ ├── consolidation/
|
|
369
|
-
│
|
|
370
|
-
│ └── web/
|
|
371
|
-
│ └── server.ts # Visual browser
|
|
181
|
+
│ │ └── hybrid.ts # Fusion + decay + salience
|
|
182
|
+
│ ├── consolidation/consolidator.ts
|
|
183
|
+
│ └── web/server.ts # Visual browser
|
|
372
184
|
```
|
|
373
185
|
|
|
374
186
|
</details>
|
|
375
187
|
|
|
376
188
|
<details>
|
|
377
|
-
<summary>
|
|
189
|
+
<summary><strong>Configuration</strong></summary>
|
|
190
|
+
|
|
191
|
+
Environment variables:
|
|
192
|
+
|
|
193
|
+
| Variable | Purpose | Default |
|
|
194
|
+
|----------|---------|---------|
|
|
195
|
+
| `ENGRAM_DB_PATH` | Where to store data | `~/.engram/` |
|
|
196
|
+
| `ANTHROPIC_API_KEY` | Enable consolidation | None (optional) |
|
|
197
|
+
|
|
198
|
+
</details>
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary><strong>Building from Source</strong></summary>
|
|
378
202
|
|
|
379
203
|
```bash
|
|
380
204
|
git clone https://github.com/199-biotechnologies/engram.git
|
|
381
205
|
cd engram
|
|
382
206
|
npm install
|
|
383
207
|
npm run build
|
|
384
|
-
|
|
385
|
-
# Install globally from local build
|
|
386
208
|
npm install -g .
|
|
387
209
|
```
|
|
388
210
|
|
|
389
|
-
|
|
211
|
+
For semantic search, install Python dependencies:
|
|
212
|
+
|
|
390
213
|
```bash
|
|
391
214
|
pip install ragatouille torch
|
|
392
215
|
```
|
|
393
216
|
|
|
394
|
-
If
|
|
217
|
+
If unavailable, Engram falls back to keyword-only search automatically.
|
|
395
218
|
|
|
396
219
|
</details>
|
|
397
220
|
|
|
398
221
|
<details>
|
|
399
|
-
<summary>
|
|
400
|
-
|
|
401
|
-
Environment variables:
|
|
402
|
-
- `ENGRAM_DB_PATH`: Database location (default: `~/.engram/`)
|
|
403
|
-
- `ANTHROPIC_API_KEY`: Required for consolidation features
|
|
404
|
-
|
|
405
|
-
Claude Desktop full config:
|
|
406
|
-
```json
|
|
407
|
-
{
|
|
408
|
-
"mcpServers": {
|
|
409
|
-
"engram": {
|
|
410
|
-
"command": "engram",
|
|
411
|
-
"env": {
|
|
412
|
-
"ENGRAM_DB_PATH": "/custom/path/",
|
|
413
|
-
"ANTHROPIC_API_KEY": "sk-ant-..."
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
</details>
|
|
421
|
-
|
|
422
|
-
<details>
|
|
423
|
-
<summary>Performance</summary>
|
|
222
|
+
<summary><strong>Performance</strong></summary>
|
|
424
223
|
|
|
425
224
|
On M1 MacBook Air:
|
|
426
|
-
- **remember**: ~100ms
|
|
427
|
-
- **recall**: ~50ms (includes decay calculation)
|
|
428
|
-
- **graph queries**: ~5ms
|
|
429
|
-
- **consolidate**: ~2-5s per batch (API call)
|
|
430
225
|
|
|
431
|
-
|
|
226
|
+
| Operation | Time |
|
|
227
|
+
|-----------|------|
|
|
228
|
+
| Remember | ~100ms |
|
|
229
|
+
| Recall | ~50ms |
|
|
230
|
+
| Graph queries | ~5ms |
|
|
231
|
+
| Consolidate | ~2-5s per batch |
|
|
232
|
+
|
|
233
|
+
Storage: ~1KB per memory.
|
|
432
234
|
|
|
433
235
|
</details>
|
|
434
236
|
|
|
@@ -436,14 +238,12 @@ Database size: ~1KB per memory (text + embeddings + graph data)
|
|
|
436
238
|
|
|
437
239
|
## Roadmap
|
|
438
240
|
|
|
439
|
-
- [x]
|
|
440
|
-
- [x]
|
|
441
|
-
- [x]
|
|
442
|
-
- [x]
|
|
443
|
-
- [x]
|
|
444
|
-
- [
|
|
445
|
-
- [x] Web dashboard
|
|
446
|
-
- [ ] Export/import
|
|
241
|
+
- [x] Hybrid search (keywords + semantics)
|
|
242
|
+
- [x] Knowledge graph with relationships
|
|
243
|
+
- [x] Memory decay and strengthening
|
|
244
|
+
- [x] Consolidation with contradiction detection
|
|
245
|
+
- [x] Web interface
|
|
246
|
+
- [ ] Export and import
|
|
447
247
|
- [ ] Scheduled consolidation
|
|
448
248
|
|
|
449
249
|
---
|
|
@@ -455,10 +255,10 @@ Founder, [199 Biotechnologies](https://199bio.com)
|
|
|
455
255
|
|
|
456
256
|
## License
|
|
457
257
|
|
|
458
|
-
MIT
|
|
258
|
+
MIT
|
|
459
259
|
|
|
460
260
|
---
|
|
461
261
|
|
|
462
262
|
<p align="center">
|
|
463
|
-
<i>Built
|
|
263
|
+
<i>Built by <a href="https://github.com/199-biotechnologies">199 Biotechnologies</a></i>
|
|
464
264
|
</p>
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ const server = new Server({
|
|
|
59
59
|
const TOOLS = [
|
|
60
60
|
{
|
|
61
61
|
name: "remember",
|
|
62
|
-
description: "Store information
|
|
62
|
+
description: "Store NEW information the user shares. Extract entities (people, organizations, places) and relationships. Do NOT use for corrections - use edit_memory instead. Do NOT use before checking if info already exists - use recall first.",
|
|
63
63
|
inputSchema: {
|
|
64
64
|
type: "object",
|
|
65
65
|
properties: {
|
|
@@ -69,14 +69,14 @@ const TOOLS = [
|
|
|
69
69
|
},
|
|
70
70
|
importance: {
|
|
71
71
|
type: "number",
|
|
72
|
-
description: "0-1 score
|
|
72
|
+
description: "0-1 score with anchors: 0.9=core identity (name, birthday, family), 0.8=major preferences/life events, 0.6=notable facts, 0.5=general info (default), 0.3=casual mentions, 0.1=trivial/ephemeral",
|
|
73
73
|
minimum: 0,
|
|
74
74
|
maximum: 1,
|
|
75
75
|
default: 0.5,
|
|
76
76
|
},
|
|
77
77
|
emotional_weight: {
|
|
78
78
|
type: "number",
|
|
79
|
-
description: "0-1 emotional significance
|
|
79
|
+
description: "0-1 emotional significance with anchors: 0.9=major life events (births, deaths, achievements), 0.7=celebrations/disappointments, 0.5=neutral (default), 0.3=mild sentiment, 0.1=purely factual",
|
|
80
80
|
minimum: 0,
|
|
81
81
|
maximum: 1,
|
|
82
82
|
default: 0.5,
|
|
@@ -91,7 +91,7 @@ const TOOLS = [
|
|
|
91
91
|
items: {
|
|
92
92
|
type: "object",
|
|
93
93
|
properties: {
|
|
94
|
-
name: { type: "string", description: "Entity name (e.g., '
|
|
94
|
+
name: { type: "string", description: "Entity name (e.g., 'John Smith', 'Google', 'Paris')" },
|
|
95
95
|
type: { type: "string", enum: ["person", "organization", "place"], description: "Entity type" },
|
|
96
96
|
},
|
|
97
97
|
required: ["name", "type"],
|
|
@@ -123,7 +123,7 @@ const TOOLS = [
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
name: "recall",
|
|
126
|
-
description: "
|
|
126
|
+
description: "Search stored memories. Use FIRST before answering questions about the user, their preferences, history, or anything previously discussed. Also use before remember to check if information already exists.",
|
|
127
127
|
inputSchema: {
|
|
128
128
|
type: "object",
|
|
129
129
|
properties: {
|
|
@@ -154,13 +154,13 @@ const TOOLS = [
|
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
name: "forget",
|
|
157
|
-
description: "Delete a
|
|
157
|
+
description: "Delete a memory by its ID. Use when user explicitly asks to remove or forget specific stored information. Get the memory ID from recall results first.",
|
|
158
158
|
inputSchema: {
|
|
159
159
|
type: "object",
|
|
160
160
|
properties: {
|
|
161
161
|
id: {
|
|
162
162
|
type: "string",
|
|
163
|
-
description: "The memory ID to
|
|
163
|
+
description: "The memory ID to delete (get this from recall results)",
|
|
164
164
|
},
|
|
165
165
|
},
|
|
166
166
|
required: ["id"],
|
|
@@ -173,6 +173,37 @@ const TOOLS = [
|
|
|
173
173
|
openWorldHint: false,
|
|
174
174
|
},
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
name: "edit_memory",
|
|
178
|
+
description: "Correct or update an existing memory. Use instead of forget+remember when fixing mistakes, updating outdated info, or adjusting importance. Preserves the memory's history and relationships.",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
id: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "The memory ID to edit (get this from recall results)",
|
|
185
|
+
},
|
|
186
|
+
content: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "New content for the memory (replaces existing). Omit to keep current content.",
|
|
189
|
+
},
|
|
190
|
+
importance: {
|
|
191
|
+
type: "number",
|
|
192
|
+
description: "New importance (0-1): 0.9=core identity, 0.8=major, 0.5=normal, 0.3=minor. Omit to keep current.",
|
|
193
|
+
minimum: 0,
|
|
194
|
+
maximum: 1,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
required: ["id"],
|
|
198
|
+
},
|
|
199
|
+
annotations: {
|
|
200
|
+
title: "Edit Memory",
|
|
201
|
+
readOnlyHint: false,
|
|
202
|
+
destructiveHint: false,
|
|
203
|
+
idempotentHint: true,
|
|
204
|
+
openWorldHint: false,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
176
207
|
{
|
|
177
208
|
name: "engram_web",
|
|
178
209
|
description: "Launch the Engram web interface for browsing, searching, and editing memories visually. Returns a URL to open in your browser.",
|
|
@@ -196,7 +227,7 @@ const TOOLS = [
|
|
|
196
227
|
},
|
|
197
228
|
{
|
|
198
229
|
name: "consolidate",
|
|
199
|
-
description: "Run memory consolidation to compress episodes into memories and memories into digests. Like sleep for the memory system. Use periodically or when requested.",
|
|
230
|
+
description: "Run memory consolidation to compress episodes into memories and memories into digests. Like sleep for the memory system. Requires ANTHROPIC_API_KEY. Use periodically or when explicitly requested.",
|
|
200
231
|
inputSchema: {
|
|
201
232
|
type: "object",
|
|
202
233
|
properties: {
|
|
@@ -213,7 +244,7 @@ const TOOLS = [
|
|
|
213
244
|
readOnlyHint: false,
|
|
214
245
|
destructiveHint: false,
|
|
215
246
|
idempotentHint: false,
|
|
216
|
-
openWorldHint:
|
|
247
|
+
openWorldHint: true, // Calls Anthropic API for consolidation
|
|
217
248
|
},
|
|
218
249
|
},
|
|
219
250
|
];
|
|
@@ -317,13 +348,62 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
317
348
|
}
|
|
318
349
|
// Remove from semantic index
|
|
319
350
|
await search.removeFromIndex(id);
|
|
320
|
-
//
|
|
321
|
-
db.
|
|
351
|
+
// Soft-delete: mark as disabled instead of hard delete
|
|
352
|
+
db.updateMemory(id, { disabled: true });
|
|
353
|
+
return {
|
|
354
|
+
content: [
|
|
355
|
+
{
|
|
356
|
+
type: "text",
|
|
357
|
+
text: JSON.stringify({ success: true, disabled_id: id, message: "Memory disabled (soft-deleted)" }),
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
case "edit_memory": {
|
|
363
|
+
const { id, content, importance } = args;
|
|
364
|
+
const memory = db.getMemory(id);
|
|
365
|
+
if (!memory) {
|
|
366
|
+
return {
|
|
367
|
+
content: [
|
|
368
|
+
{
|
|
369
|
+
type: "text",
|
|
370
|
+
text: JSON.stringify({ success: false, error: "Memory not found" }),
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// Build updates object
|
|
376
|
+
const updates = {};
|
|
377
|
+
if (content !== undefined)
|
|
378
|
+
updates.content = content;
|
|
379
|
+
if (importance !== undefined)
|
|
380
|
+
updates.importance = importance;
|
|
381
|
+
if (Object.keys(updates).length === 0) {
|
|
382
|
+
return {
|
|
383
|
+
content: [
|
|
384
|
+
{
|
|
385
|
+
type: "text",
|
|
386
|
+
text: JSON.stringify({ success: false, error: "No updates provided" }),
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
// Update the memory
|
|
392
|
+
const updated = db.updateMemory(id, updates);
|
|
393
|
+
// Re-index if content changed
|
|
394
|
+
if (content !== undefined && updated) {
|
|
395
|
+
await search.removeFromIndex(id);
|
|
396
|
+
await search.indexMemory(updated);
|
|
397
|
+
}
|
|
322
398
|
return {
|
|
323
399
|
content: [
|
|
324
400
|
{
|
|
325
401
|
type: "text",
|
|
326
|
-
text: JSON.stringify({
|
|
402
|
+
text: JSON.stringify({
|
|
403
|
+
success: true,
|
|
404
|
+
memory_id: id,
|
|
405
|
+
updated_fields: Object.keys(updates),
|
|
406
|
+
}),
|
|
327
407
|
},
|
|
328
408
|
],
|
|
329
409
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AAEzF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAwDD,qBAAa,YAAY;IAErB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,SAAS;gBAFT,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,gBAAgB,GAAG,eAAe;IAGvD;;;;;OAKG;IACG,MAAM,CACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,OAAO,CAAC;KACnB,GACL,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAyIhC;;OAEG;YACW,UAAU;IASxB;;OAEG;YACW,cAAc;IAS5B
|
|
1
|
+
{"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AAEzF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAwDD,qBAAa,YAAY;IAErB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,SAAS;gBAFT,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,gBAAgB,GAAG,eAAe;IAGvD;;;;;OAKG;IACG,MAAM,CACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,OAAO,CAAC;KACnB,GACL,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAyIhC;;OAEG;YACW,UAAU;IASxB;;OAEG;YACW,cAAc;IAS5B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB;;;OAGG;YACW,WAAW;IA8BzB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAYhD;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -74,8 +74,8 @@ const EPISODE_EXTRACTION_SYSTEM = `You are extracting structured memories from a
|
|
|
74
74
|
"importance": 0.5,
|
|
75
75
|
"emotional_weight": 0.5,
|
|
76
76
|
"event_time": "2024-12-01 or null if not mentioned",
|
|
77
|
-
"entities": [{"name": "
|
|
78
|
-
"relationships": [{"from": "
|
|
77
|
+
"entities": [{"name": "John", "type": "person"}],
|
|
78
|
+
"relationships": [{"from": "John", "to": "Acme Corp", "type": "works_at"}]
|
|
79
79
|
}
|
|
80
80
|
]
|
|
81
81
|
}
|
package/src/index.ts
CHANGED
|
@@ -81,7 +81,7 @@ const TOOLS = [
|
|
|
81
81
|
{
|
|
82
82
|
name: "remember",
|
|
83
83
|
description:
|
|
84
|
-
"Store information
|
|
84
|
+
"Store NEW information the user shares. Extract entities (people, organizations, places) and relationships. Do NOT use for corrections - use edit_memory instead. Do NOT use before checking if info already exists - use recall first.",
|
|
85
85
|
inputSchema: {
|
|
86
86
|
type: "object" as const,
|
|
87
87
|
properties: {
|
|
@@ -91,14 +91,14 @@ const TOOLS = [
|
|
|
91
91
|
},
|
|
92
92
|
importance: {
|
|
93
93
|
type: "number",
|
|
94
|
-
description: "0-1 score
|
|
94
|
+
description: "0-1 score with anchors: 0.9=core identity (name, birthday, family), 0.8=major preferences/life events, 0.6=notable facts, 0.5=general info (default), 0.3=casual mentions, 0.1=trivial/ephemeral",
|
|
95
95
|
minimum: 0,
|
|
96
96
|
maximum: 1,
|
|
97
97
|
default: 0.5,
|
|
98
98
|
},
|
|
99
99
|
emotional_weight: {
|
|
100
100
|
type: "number",
|
|
101
|
-
description: "0-1 emotional significance
|
|
101
|
+
description: "0-1 emotional significance with anchors: 0.9=major life events (births, deaths, achievements), 0.7=celebrations/disappointments, 0.5=neutral (default), 0.3=mild sentiment, 0.1=purely factual",
|
|
102
102
|
minimum: 0,
|
|
103
103
|
maximum: 1,
|
|
104
104
|
default: 0.5,
|
|
@@ -113,7 +113,7 @@ const TOOLS = [
|
|
|
113
113
|
items: {
|
|
114
114
|
type: "object",
|
|
115
115
|
properties: {
|
|
116
|
-
name: { type: "string", description: "Entity name (e.g., '
|
|
116
|
+
name: { type: "string", description: "Entity name (e.g., 'John Smith', 'Google', 'Paris')" },
|
|
117
117
|
type: { type: "string", enum: ["person", "organization", "place"], description: "Entity type" },
|
|
118
118
|
},
|
|
119
119
|
required: ["name", "type"],
|
|
@@ -146,7 +146,7 @@ const TOOLS = [
|
|
|
146
146
|
{
|
|
147
147
|
name: "recall",
|
|
148
148
|
description:
|
|
149
|
-
"
|
|
149
|
+
"Search stored memories. Use FIRST before answering questions about the user, their preferences, history, or anything previously discussed. Also use before remember to check if information already exists.",
|
|
150
150
|
inputSchema: {
|
|
151
151
|
type: "object" as const,
|
|
152
152
|
properties: {
|
|
@@ -177,13 +177,13 @@ const TOOLS = [
|
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
name: "forget",
|
|
180
|
-
description: "Delete a
|
|
180
|
+
description: "Delete a memory by its ID. Use when user explicitly asks to remove or forget specific stored information. Get the memory ID from recall results first.",
|
|
181
181
|
inputSchema: {
|
|
182
182
|
type: "object" as const,
|
|
183
183
|
properties: {
|
|
184
184
|
id: {
|
|
185
185
|
type: "string",
|
|
186
|
-
description: "The memory ID to
|
|
186
|
+
description: "The memory ID to delete (get this from recall results)",
|
|
187
187
|
},
|
|
188
188
|
},
|
|
189
189
|
required: ["id"],
|
|
@@ -196,6 +196,37 @@ const TOOLS = [
|
|
|
196
196
|
openWorldHint: false,
|
|
197
197
|
},
|
|
198
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: "edit_memory",
|
|
201
|
+
description: "Correct or update an existing memory. Use instead of forget+remember when fixing mistakes, updating outdated info, or adjusting importance. Preserves the memory's history and relationships.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object" as const,
|
|
204
|
+
properties: {
|
|
205
|
+
id: {
|
|
206
|
+
type: "string",
|
|
207
|
+
description: "The memory ID to edit (get this from recall results)",
|
|
208
|
+
},
|
|
209
|
+
content: {
|
|
210
|
+
type: "string",
|
|
211
|
+
description: "New content for the memory (replaces existing). Omit to keep current content.",
|
|
212
|
+
},
|
|
213
|
+
importance: {
|
|
214
|
+
type: "number",
|
|
215
|
+
description: "New importance (0-1): 0.9=core identity, 0.8=major, 0.5=normal, 0.3=minor. Omit to keep current.",
|
|
216
|
+
minimum: 0,
|
|
217
|
+
maximum: 1,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
required: ["id"],
|
|
221
|
+
},
|
|
222
|
+
annotations: {
|
|
223
|
+
title: "Edit Memory",
|
|
224
|
+
readOnlyHint: false,
|
|
225
|
+
destructiveHint: false,
|
|
226
|
+
idempotentHint: true,
|
|
227
|
+
openWorldHint: false,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
199
230
|
{
|
|
200
231
|
name: "engram_web",
|
|
201
232
|
description: "Launch the Engram web interface for browsing, searching, and editing memories visually. Returns a URL to open in your browser.",
|
|
@@ -219,7 +250,7 @@ const TOOLS = [
|
|
|
219
250
|
},
|
|
220
251
|
{
|
|
221
252
|
name: "consolidate",
|
|
222
|
-
description: "Run memory consolidation to compress episodes into memories and memories into digests. Like sleep for the memory system. Use periodically or when requested.",
|
|
253
|
+
description: "Run memory consolidation to compress episodes into memories and memories into digests. Like sleep for the memory system. Requires ANTHROPIC_API_KEY. Use periodically or when explicitly requested.",
|
|
223
254
|
inputSchema: {
|
|
224
255
|
type: "object" as const,
|
|
225
256
|
properties: {
|
|
@@ -236,7 +267,7 @@ const TOOLS = [
|
|
|
236
267
|
readOnlyHint: false,
|
|
237
268
|
destructiveHint: false,
|
|
238
269
|
idempotentHint: false,
|
|
239
|
-
openWorldHint:
|
|
270
|
+
openWorldHint: true, // Calls Anthropic API for consolidation
|
|
240
271
|
},
|
|
241
272
|
},
|
|
242
273
|
];
|
|
@@ -375,14 +406,72 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
375
406
|
// Remove from semantic index
|
|
376
407
|
await search.removeFromIndex(id);
|
|
377
408
|
|
|
378
|
-
//
|
|
379
|
-
db.
|
|
409
|
+
// Soft-delete: mark as disabled instead of hard delete
|
|
410
|
+
db.updateMemory(id, { disabled: true });
|
|
380
411
|
|
|
381
412
|
return {
|
|
382
413
|
content: [
|
|
383
414
|
{
|
|
384
415
|
type: "text" as const,
|
|
385
|
-
text: JSON.stringify({ success: true,
|
|
416
|
+
text: JSON.stringify({ success: true, disabled_id: id, message: "Memory disabled (soft-deleted)" }),
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
case "edit_memory": {
|
|
423
|
+
const { id, content, importance } = args as {
|
|
424
|
+
id: string;
|
|
425
|
+
content?: string;
|
|
426
|
+
importance?: number;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const memory = db.getMemory(id);
|
|
430
|
+
if (!memory) {
|
|
431
|
+
return {
|
|
432
|
+
content: [
|
|
433
|
+
{
|
|
434
|
+
type: "text" as const,
|
|
435
|
+
text: JSON.stringify({ success: false, error: "Memory not found" }),
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Build updates object
|
|
442
|
+
const updates: { content?: string; importance?: number } = {};
|
|
443
|
+
if (content !== undefined) updates.content = content;
|
|
444
|
+
if (importance !== undefined) updates.importance = importance;
|
|
445
|
+
|
|
446
|
+
if (Object.keys(updates).length === 0) {
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text" as const,
|
|
451
|
+
text: JSON.stringify({ success: false, error: "No updates provided" }),
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Update the memory
|
|
458
|
+
const updated = db.updateMemory(id, updates);
|
|
459
|
+
|
|
460
|
+
// Re-index if content changed
|
|
461
|
+
if (content !== undefined && updated) {
|
|
462
|
+
await search.removeFromIndex(id);
|
|
463
|
+
await search.indexMemory(updated);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
content: [
|
|
468
|
+
{
|
|
469
|
+
type: "text" as const,
|
|
470
|
+
text: JSON.stringify({
|
|
471
|
+
success: true,
|
|
472
|
+
memory_id: id,
|
|
473
|
+
updated_fields: Object.keys(updates),
|
|
474
|
+
}),
|
|
386
475
|
},
|
|
387
476
|
],
|
|
388
477
|
};
|
package/src/retrieval/hybrid.ts
CHANGED
|
@@ -102,7 +102,7 @@ export class HybridSearch {
|
|
|
102
102
|
includeGraph = true,
|
|
103
103
|
bm25Weight = 1.0,
|
|
104
104
|
semanticWeight = 1.0,
|
|
105
|
-
graphWeight = 0.
|
|
105
|
+
graphWeight = 0.3,
|
|
106
106
|
useReranking = true, // Default: reranking mode for better quality
|
|
107
107
|
} = options;
|
|
108
108
|
|
|
@@ -257,22 +257,46 @@ export class HybridSearch {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Normalize a word for matching: lowercase, strip punctuation, handle possessives
|
|
262
|
+
* "John's" → "john", "Paris!" → "paris", "U.S.A." → "usa"
|
|
263
|
+
*/
|
|
264
|
+
private normalizeWord(word: string): string {
|
|
265
|
+
return word
|
|
266
|
+
.toLowerCase()
|
|
267
|
+
.replace(/'s$/, '') // Remove possessives: john's → john
|
|
268
|
+
.replace(/[^\p{L}\p{N}]/gu, '') // Keep only letters/numbers (Unicode-safe)
|
|
269
|
+
.trim();
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
/**
|
|
261
273
|
* Graph-based search: find known entities in query, traverse graph
|
|
274
|
+
* Uses normalized word matching to handle punctuation and possessives
|
|
262
275
|
*/
|
|
263
276
|
private async searchGraph(query: string): Promise<string[]> {
|
|
264
|
-
//
|
|
265
|
-
const
|
|
277
|
+
// Tokenize and normalize query words
|
|
278
|
+
const queryWords = query.split(/\s+/)
|
|
279
|
+
.map(w => this.normalizeWord(w))
|
|
280
|
+
.filter(w => w.length > 0);
|
|
281
|
+
const queryWordSet = new Set(queryWords);
|
|
282
|
+
|
|
283
|
+
// Find entities whose normalized names match query words
|
|
266
284
|
const allEntities = this.graph.listEntities(undefined, 500);
|
|
267
|
-
const matchedEntities = allEntities.filter(e =>
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
const matchedEntities = allEntities.filter(e => {
|
|
286
|
+
const entityWords = e.name.split(/\s+/)
|
|
287
|
+
.map(w => this.normalizeWord(w))
|
|
288
|
+
.filter(w => w.length > 0);
|
|
289
|
+
// Entity matches if ALL its words appear in the query
|
|
290
|
+
// e.g., "john" matches entity "John" but not "John Smith"
|
|
291
|
+
// e.g., "john's friend" matches entity "John" (possessive handled)
|
|
292
|
+
return entityWords.every(w => queryWordSet.has(w));
|
|
293
|
+
});
|
|
270
294
|
|
|
271
295
|
const memoryIds = new Set<string>();
|
|
272
296
|
|
|
273
297
|
for (const entity of matchedEntities) {
|
|
274
|
-
//
|
|
275
|
-
const relatedIds = this.graph.findRelatedMemoryIds(entity.name,
|
|
298
|
+
// Depth 1: only directly related memories, not transitive connections
|
|
299
|
+
const relatedIds = this.graph.findRelatedMemoryIds(entity.name, 1);
|
|
276
300
|
relatedIds.forEach(id => memoryIds.add(id));
|
|
277
301
|
}
|
|
278
302
|
|
package/src/storage/database.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface Memory {
|
|
|
19
19
|
last_accessed: Date | null;
|
|
20
20
|
stability: number; // Ebbinghaus stability score (increases with recalls)
|
|
21
21
|
emotional_weight: number; // Salience: emotional significance 0-1
|
|
22
|
+
disabled: boolean; // Soft-deleted: excluded from search results
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -112,23 +113,17 @@ export class EngramDatabase {
|
|
|
112
113
|
|
|
113
114
|
private initialize(): void {
|
|
114
115
|
// Memories table (Semantic Memory - neocortex analog)
|
|
116
|
+
// Create table with base columns first (for new databases)
|
|
115
117
|
this.db.exec(`
|
|
116
118
|
CREATE TABLE IF NOT EXISTS memories (
|
|
117
119
|
id TEXT PRIMARY KEY,
|
|
118
120
|
content TEXT NOT NULL,
|
|
119
121
|
source TEXT DEFAULT 'conversation',
|
|
120
122
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
121
|
-
event_time DATETIME,
|
|
122
123
|
importance REAL DEFAULT 0.5,
|
|
123
124
|
access_count INTEGER DEFAULT 0,
|
|
124
|
-
last_accessed DATETIME
|
|
125
|
-
stability REAL DEFAULT 1.0,
|
|
126
|
-
emotional_weight REAL DEFAULT 0.5
|
|
125
|
+
last_accessed DATETIME
|
|
127
126
|
);
|
|
128
|
-
|
|
129
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
130
|
-
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
|
|
131
|
-
CREATE INDEX IF NOT EXISTS idx_memories_event_time ON memories(event_time);
|
|
132
127
|
`);
|
|
133
128
|
|
|
134
129
|
// Episodes table (Episodic Memory - hippocampal buffer)
|
|
@@ -142,15 +137,22 @@ export class EngramDatabase {
|
|
|
142
137
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
143
138
|
consolidated INTEGER DEFAULT 0
|
|
144
139
|
);
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
// IMPORTANT: Migrate schema BEFORE creating indexes on new columns
|
|
143
|
+
// This adds event_time, stability, emotional_weight to existing databases
|
|
144
|
+
this.migrateSchema();
|
|
145
145
|
|
|
146
|
+
// Now create all indexes (after migration ensures columns exist)
|
|
147
|
+
this.db.exec(`
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_memories_event_time ON memories(event_time);
|
|
146
151
|
CREATE INDEX IF NOT EXISTS idx_episodes_session ON episodes(session_id);
|
|
147
152
|
CREATE INDEX IF NOT EXISTS idx_episodes_consolidated ON episodes(consolidated);
|
|
148
153
|
CREATE INDEX IF NOT EXISTS idx_episodes_timestamp ON episodes(timestamp);
|
|
149
154
|
`);
|
|
150
155
|
|
|
151
|
-
// Migrate existing tables: add new columns if they don't exist
|
|
152
|
-
this.migrateSchema();
|
|
153
|
-
|
|
154
156
|
// FTS5 for BM25 search
|
|
155
157
|
this.db.exec(`
|
|
156
158
|
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
@@ -286,6 +288,9 @@ export class EngramDatabase {
|
|
|
286
288
|
if (!memoryColumns.has("emotional_weight")) {
|
|
287
289
|
this.db.exec("ALTER TABLE memories ADD COLUMN emotional_weight REAL DEFAULT 0.5");
|
|
288
290
|
}
|
|
291
|
+
if (!memoryColumns.has("disabled")) {
|
|
292
|
+
this.db.exec("ALTER TABLE memories ADD COLUMN disabled INTEGER DEFAULT 0");
|
|
293
|
+
}
|
|
289
294
|
}
|
|
290
295
|
|
|
291
296
|
// ============ Memory Operations ============
|
|
@@ -321,7 +326,7 @@ export class EngramDatabase {
|
|
|
321
326
|
return row ? this.rowToMemory(row) : null;
|
|
322
327
|
}
|
|
323
328
|
|
|
324
|
-
updateMemory(id: string, updates: Partial<Pick<Memory, "content" | "importance">>): Memory | null {
|
|
329
|
+
updateMemory(id: string, updates: Partial<Pick<Memory, "content" | "importance" | "disabled">>): Memory | null {
|
|
325
330
|
const sets: string[] = [];
|
|
326
331
|
const values: unknown[] = [];
|
|
327
332
|
|
|
@@ -333,6 +338,10 @@ export class EngramDatabase {
|
|
|
333
338
|
sets.push("importance = ?");
|
|
334
339
|
values.push(updates.importance);
|
|
335
340
|
}
|
|
341
|
+
if (updates.disabled !== undefined) {
|
|
342
|
+
sets.push("disabled = ?");
|
|
343
|
+
values.push(updates.disabled ? 1 : 0);
|
|
344
|
+
}
|
|
336
345
|
|
|
337
346
|
if (sets.length === 0) return this.getMemory(id);
|
|
338
347
|
|
|
@@ -363,8 +372,11 @@ export class EngramDatabase {
|
|
|
363
372
|
`).run(id);
|
|
364
373
|
}
|
|
365
374
|
|
|
366
|
-
getAllMemories(limit: number = 1000): Memory[] {
|
|
367
|
-
const
|
|
375
|
+
getAllMemories(limit: number = 1000, includeDisabled: boolean = false): Memory[] {
|
|
376
|
+
const sql = includeDisabled
|
|
377
|
+
? "SELECT * FROM memories ORDER BY timestamp DESC LIMIT ?"
|
|
378
|
+
: "SELECT * FROM memories WHERE disabled = 0 ORDER BY timestamp DESC LIMIT ?";
|
|
379
|
+
const rows = this.stmt(sql).all(limit) as Record<string, unknown>[];
|
|
368
380
|
return rows.map((row) => this.rowToMemory(row));
|
|
369
381
|
}
|
|
370
382
|
|
|
@@ -474,7 +486,7 @@ export class EngramDatabase {
|
|
|
474
486
|
SELECT m.*, bm25(memories_fts) as score
|
|
475
487
|
FROM memories_fts fts
|
|
476
488
|
JOIN memories m ON fts.rowid = m.rowid
|
|
477
|
-
WHERE memories_fts MATCH ?
|
|
489
|
+
WHERE memories_fts MATCH ? AND m.disabled = 0
|
|
478
490
|
ORDER BY score
|
|
479
491
|
LIMIT ?
|
|
480
492
|
`).all(escapedQuery, limit) as Array<Record<string, unknown>>;
|
|
@@ -529,7 +541,7 @@ export class EngramDatabase {
|
|
|
529
541
|
|
|
530
542
|
/**
|
|
531
543
|
* Find a similar entity using fuzzy matching
|
|
532
|
-
* Catches "
|
|
544
|
+
* Catches "John D" matching "John Doe", "Jane" matching "Jane Smith"
|
|
533
545
|
*/
|
|
534
546
|
findSimilarEntity(name: string, threshold: number = 0.8): Entity | null {
|
|
535
547
|
const normalizedName = name.toLowerCase().trim();
|
|
@@ -575,7 +587,7 @@ export class EngramDatabase {
|
|
|
575
587
|
// Exact match
|
|
576
588
|
if (full1 === full2) return 1.0;
|
|
577
589
|
|
|
578
|
-
// One is prefix of the other (e.g., "
|
|
590
|
+
// One is prefix of the other (e.g., "John" vs "John Doe")
|
|
579
591
|
if (full1.startsWith(full2 + " ") || full2.startsWith(full1 + " ")) {
|
|
580
592
|
return 0.9;
|
|
581
593
|
}
|
|
@@ -588,7 +600,7 @@ export class EngramDatabase {
|
|
|
588
600
|
return 0.7 + (0.2 * shorter / longer);
|
|
589
601
|
}
|
|
590
602
|
|
|
591
|
-
// Check for abbreviated names (e.g., "
|
|
603
|
+
// Check for abbreviated names (e.g., "John D" vs "John Doe")
|
|
592
604
|
if (words1.length >= 2 && words2.length >= 2) {
|
|
593
605
|
const last1 = words1[words1.length - 1];
|
|
594
606
|
const last2 = words2[words2.length - 1];
|
|
@@ -1098,6 +1110,7 @@ export class EngramDatabase {
|
|
|
1098
1110
|
last_accessed: row.last_accessed ? new Date(row.last_accessed as string) : null,
|
|
1099
1111
|
stability: (row.stability as number) ?? 1.0,
|
|
1100
1112
|
emotional_weight: (row.emotional_weight as number) ?? 0.5,
|
|
1113
|
+
disabled: Boolean(row.disabled),
|
|
1101
1114
|
};
|
|
1102
1115
|
}
|
|
1103
1116
|
|