File size: 19,920 Bytes
9c9bb30
 
2ae9823
9c9bb30
 
 
 
 
 
 
 
 
 
 
 
 
2ae9823
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
2ae9823
 
 
9c9bb30
 
 
2ae9823
9c9bb30
 
2ae9823
9c9bb30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ae9823
 
 
9c9bb30
 
 
2ae9823
9c9bb30
2ae9823
9c9bb30
 
 
2ae9823
9c9bb30
 
 
 
 
2ae9823
9c9bb30
 
 
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
2ae9823
9c9bb30
 
2ae9823
 
9c9bb30
2ae9823
 
9c9bb30
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
2ae9823
9c9bb30
 
2ae9823
 
 
9c9bb30
2ae9823
9c9bb30
 
2ae9823
9c9bb30
2ae9823
 
 
 
 
9c9bb30
2ae9823
9c9bb30
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
 
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
 
 
 
 
2ae9823
9c9bb30
 
2ae9823
9c9bb30
2ae9823
9c9bb30
2ae9823
 
9c9bb30
 
2ae9823
 
 
 
 
9c9bb30
 
 
 
 
 
 
 
 
2ae9823
 
9c9bb30
 
 
 
2ae9823
9c9bb30
2ae9823
9c9bb30
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
 
 
 
 
2ae9823
9c9bb30
 
2ae9823
9c9bb30
2ae9823
 
 
 
 
9c9bb30
2ae9823
 
 
 
 
 
9c9bb30
2ae9823
 
 
 
 
9c9bb30
2ae9823
9c9bb30
2ae9823
 
 
 
 
 
 
 
 
 
 
9c9bb30
 
 
2ae9823
9c9bb30
 
2ae9823
9c9bb30
 
2ae9823
 
 
 
 
9c9bb30
 
 
 
 
2ae9823
9c9bb30
2ae9823
9c9bb30
2ae9823
9c9bb30
 
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
 
 
2ae9823
9c9bb30
2ae9823
9c9bb30
2ae9823
9c9bb30
2ae9823
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9c9bb30
2ae9823
 
 
9c9bb30
 
2ae9823
 
9c9bb30
2ae9823
 
9c9bb30
2ae9823
9c9bb30
 
 
 
 
 
 
 
2ae9823
 
 
 
9c9bb30
 
 
 
 
 
 
 
 
 
 
2ae9823
9c9bb30
 
2ae9823
9c9bb30
2ae9823
 
 
 
 
9c9bb30
 
2ae9823
 
9c9bb30
 
2ae9823
9c9bb30
 
 
 
2ae9823
9c9bb30
 
 
2ae9823
9c9bb30
2ae9823
9c9bb30
 
 
 
 
 
2ae9823
9c9bb30
 
2ae9823
 
 
 
9c9bb30
 
 
 
 
 
 
 
 
2ae9823
 
 
 
 
 
 
 
9c9bb30
 
 
 
 
 
 
 
 
 
2ae9823
9c9bb30
 
 
 
 
 
2ae9823
9c9bb30
 
 
2ae9823
9c9bb30
2ae9823
 
 
 
 
 
 
9c9bb30
 
 
 
 
 
2ae9823
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# Memory

**Hierarchical, temporal memory for LLM applications.** Enable your AI to remember across conversations with intelligent scoping and versioning.

## Why Memory?

LLMs have two fundamental limitations:
1. **Context windows overflow** - Too much history, need to truncate
2. **No persistence** - Every conversation starts from zero

Memory solves both: **extract key facts, persist them, inject when relevant.**

This is *temporal compression* - instead of carrying 10,000 tokens of conversation history, carry 100 tokens of extracted memories.

---

## What Makes Headroom Memory Different?

| Feature | Headroom | Letta (MemGPT) | Mem0 |
|---------|----------|----------------|------|
| **Hierarchical Scoping** | User β†’ Session β†’ Agent β†’ Turn | Flat (per-agent) | Flat (per-user) |
| **Temporal Versioning** | Full supersession chains | No | No |
| **Zero-Latency Extraction** | Inline (Letta-style) | Inline | Separate call |
| **One-Liner Integration** | `with_memory(client)` | Requires agent setup | Requires separate client |
| **Pluggable Backends** | SQLite, HNSW, FTS5, any embedder | PostgreSQL | Qdrant/Chroma |
| **Semantic + Full-Text Search** | Both | Semantic only | Semantic only |
| **Memory Bubbling** | Auto-promote important memories | No | No |
| **Protocol-Based Architecture** | Yes (dependency injection) | No | No |

---

## Quick Start

```python
from openai import OpenAI
from headroom import with_memory

# One line - that's it
client = with_memory(OpenAI(), user_id="alice")

# Use exactly like normal
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "I prefer Python for backend work"}]
)
# Memory extracted INLINE - zero extra latency

# Later, in a new conversation...
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What language should I use?"}]
)
# β†’ Response uses the Python preference from memory
```

---

## How It Works

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      with_memory()                          β”‚
β”‚                                                              β”‚
β”‚   1. INJECT: Semantic search β†’ prepend to user message      β”‚
β”‚   2. INSTRUCT: Add memory extraction instruction            β”‚
β”‚   3. CALL: Forward to LLM                                   β”‚
β”‚   4. PARSE: Extract <memory> block from response            β”‚
β”‚   5. STORE: Save with embeddings + vector index + FTS       β”‚
β”‚   6. RETURN: Clean response (without memory block)          β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

**Key insight**: Memory extraction happens *inline* as part of the LLM response (Letta-style). No extra API calls, no extra latency.

---

## Hierarchical Scoping

Memories exist at different scope levels, enabling fine-grained control:

```
USER (broadest)
 └── SESSION
      └── AGENT
           └── TURN (narrowest)
```

### Scope Levels

| Scope | Persists Across | Use Case |
|-------|-----------------|----------|
| **USER** | All sessions, all time | Long-term preferences, identity |
| **SESSION** | Current session only | Current task context |
| **AGENT** | Current agent in session | Agent-specific context |
| **TURN** | Single turn only | Ephemeral working memory |

### Example: Multi-Session Memory

```python
from openai import OpenAI
from headroom import with_memory

# Session 1: Morning
client1 = with_memory(
    OpenAI(),
    user_id="bob",
    session_id="morning-session",
)
response = client1.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "I prefer Go for performance-critical code"}]
)
# Memory stored at USER level (persists across sessions)

# Session 2: Afternoon (different session, same user)
client2 = with_memory(
    OpenAI(),
    user_id="bob",  # Same user
    session_id="afternoon-session",  # Different session
)
response = client2.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What language for my new microservice?"}]
)
# β†’ Recalls Go preference from morning session!
```

---

## Temporal Versioning (Supersession)

Memories evolve over time. When facts change, Headroom creates a **supersession chain** preserving history:

```python
from headroom.memory import HierarchicalMemory, MemoryConfig

memory = await HierarchicalMemory.create()

# Original fact
orig = await memory.add(
    content="User works at Google",
    user_id="alice",
    category=MemoryCategory.FACT,
)

# User changes jobs - supersede the old memory
new = await memory.supersede(
    old_memory_id=orig.id,
    new_content="User now works at Anthropic",
)

# Query current state (excludes superseded)
current = await memory.query(MemoryFilter(
    user_id="alice",
    include_superseded=False,  # Default
))
# β†’ Returns only "User now works at Anthropic"

# Query full history (includes superseded)
history = await memory.query(MemoryFilter(
    user_id="alice",
    include_superseded=True,
))
# β†’ Returns both memories with validity timestamps

# Get the chain
chain = await memory.get_history(new.id)
# β†’ [
#     Memory(content="User works at Google", valid_until=..., is_current=False),
#     Memory(content="User now works at Anthropic", valid_until=None, is_current=True),
#   ]
```

### Why Temporal Versioning Matters

1. **Audit trail** - Know what was true at any point in time
2. **Debugging** - Understand why the LLM made certain decisions
3. **Rollback** - Restore previous state if needed
4. **Analytics** - Track how user preferences evolve

---

## Memory Categories

Memories are categorized for better organization and retrieval:

| Category | Description | Examples |
|----------|-------------|----------|
| `PREFERENCE` | Likes, dislikes, preferred approaches | "Prefers Python", "Likes dark mode" |
| `FACT` | Identity, role, constraints | "Works at fintech startup", "Senior engineer" |
| `CONTEXT` | Current goals, ongoing tasks | "Migrating to microservices", "Working on auth" |
| `ENTITY` | Information about entities | "Project Apollo uses React", "Team lead is Sarah" |
| `DECISION` | Decisions made | "Chose PostgreSQL over MySQL", "Using REST not GraphQL" |
| `INSIGHT` | Derived insights | "User tends to prefer typed languages" |

---

## Memory API

The `with_memory()` wrapper provides a `.memory` API for direct access:

```python
client = with_memory(OpenAI(), user_id="alice")

# Search memories (semantic)
results = client.memory.search("python preferences", top_k=5)
for memory in results:
    print(f"{memory.content}")

# Add manual memory
client.memory.add(
    "User is a senior engineer",
    category="fact",
    importance=0.9,
)

# Get all memories
all_memories = client.memory.get_all()

# Clear memories
client.memory.clear()

# Get stats
stats = client.memory.stats()
print(f"Total memories: {stats['total']}")
print(f"By category: {stats['categories']}")
```

---

## Advanced Usage: Direct HierarchicalMemory API

For full control, use the `HierarchicalMemory` class directly:

```python
import asyncio
from headroom.memory import (
    HierarchicalMemory,
    MemoryConfig,
    MemoryCategory,
    EmbedderBackend,
)
from headroom.memory.ports import MemoryFilter, VectorFilter

async def main():
    # Create with custom configuration
    config = MemoryConfig(
        db_path="my_memory.db",
        embedder_backend=EmbedderBackend.LOCAL,  # or OPENAI, OLLAMA
        vector_dimension=384,
        cache_max_size=2000,
    )
    memory = await HierarchicalMemory.create(config)

    # Add memory with full control
    mem = await memory.add(
        content="User prefers functional programming",
        user_id="alice",
        session_id="sess-123",
        agent_id="code-assistant",
        category=MemoryCategory.PREFERENCE,
        importance=0.9,
        entity_refs=["functional-programming", "coding-style"],
        metadata={"source": "conversation", "confidence": 0.95},
    )

    # Semantic search
    results = await memory.search(
        query="programming paradigm preferences",
        user_id="alice",
        top_k=5,
        min_similarity=0.5,
        categories=[MemoryCategory.PREFERENCE],
    )
    for r in results:
        print(f"[{r.similarity:.3f}] {r.memory.content}")

    # Full-text search
    text_results = await memory.text_search(
        query="functional",
        user_id="alice",
    )

    # Query with filters
    memories = await memory.query(MemoryFilter(
        user_id="alice",
        categories=[MemoryCategory.PREFERENCE, MemoryCategory.FACT],
        min_importance=0.7,
        limit=10,
    ))

    # Convenience methods
    await memory.remember("Likes coffee", user_id="alice", importance=0.6)
    relevant = await memory.recall("beverage preferences", user_id="alice")

asyncio.run(main())
```

---

## Configuration

### Embedder Backends

```python
from headroom.memory import MemoryConfig, EmbedderBackend

# Local embeddings (recommended - fast, free, private)
config = MemoryConfig(
    embedder_backend=EmbedderBackend.LOCAL,
    embedder_model="all-MiniLM-L6-v2",  # 384 dimensions, fast
)

# OpenAI embeddings (higher quality, costs money)
config = MemoryConfig(
    embedder_backend=EmbedderBackend.OPENAI,
    openai_api_key="sk-...",
    embedder_model="text-embedding-3-small",
)

# Ollama embeddings (local server, many models)
config = MemoryConfig(
    embedder_backend=EmbedderBackend.OLLAMA,
    ollama_base_url="http://localhost:11434",
    embedder_model="nomic-embed-text",
)
```

### Storage Configuration

```python
config = MemoryConfig(
    db_path="memory.db",          # SQLite database path
    vector_dimension=384,          # Must match embedder output
    hnsw_ef_construction=200,      # HNSW index quality (higher = better, slower)
    hnsw_m=16,                     # HNSW connections per node
    hnsw_ef_search=50,             # HNSW search quality
    cache_enabled=True,            # Enable LRU cache
    cache_max_size=1000,           # Max cached memories
)
```

### Wrapper Configuration

```python
client = with_memory(
    OpenAI(),
    user_id="alice",
    db_path="memory.db",
    top_k=5,                       # Memories to inject per request
    session_id="optional-session",
    agent_id="optional-agent",
    embedder_backend=EmbedderBackend.LOCAL,
)
```

---

## Architecture

### Protocol-Based Design

Headroom Memory uses **Protocol interfaces** (ports) for all components, enabling easy swapping:

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   HierarchicalMemory                        β”‚
β”‚                     (Orchestrator)                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚ MemoryStore β”‚  β”‚ VectorIndex β”‚  β”‚  TextIndex  β”‚        β”‚
β”‚  β”‚  Protocol   β”‚  β”‚  Protocol   β”‚  β”‚  Protocol   β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚         β”‚                β”‚                β”‚                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚   SQLite    β”‚  β”‚    HNSW     β”‚  β”‚    FTS5     β”‚        β”‚
β”‚  β”‚  Adapter    β”‚  β”‚   Adapter   β”‚  β”‚   Adapter   β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”‚
β”‚  β”‚  Embedder   β”‚  β”‚ MemoryCache β”‚                          β”‚
β”‚  β”‚  Protocol   β”‚  β”‚  Protocol   β”‚                          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                          β”‚
β”‚         β”‚                β”‚                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”                          β”‚
β”‚  β”‚Local/OpenAI/β”‚  β”‚  LRU Cache  β”‚                          β”‚
β”‚  β”‚   Ollama    β”‚  β”‚             β”‚                          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Components

| Component | Protocol | Default Adapter | Purpose |
|-----------|----------|-----------------|---------|
| **MemoryStore** | `MemoryStore` | `SQLiteMemoryStore` | CRUD + filtering + supersession |
| **VectorIndex** | `VectorIndex` | `HNSWVectorIndex` | Semantic similarity search |
| **TextIndex** | `TextIndex` | `FTS5TextIndex` | Full-text keyword search |
| **Embedder** | `Embedder` | `LocalEmbedder` | Text β†’ vector conversion |
| **Cache** | `MemoryCache` | `LRUMemoryCache` | Hot memory caching |

---

## Comparison with State of the Art

### vs Letta (MemGPT)

**Letta** pioneered inline memory extraction. Headroom builds on this with:

| Aspect | Headroom | Letta |
|--------|----------|-------|
| **Scoping** | 4-level hierarchy (user/session/agent/turn) | Flat per-agent |
| **Temporal** | Full supersession chains with history | No versioning |
| **Integration** | One-liner wrapper for any client | Requires Letta agent framework |
| **Search** | Semantic + full-text | Semantic only |
| **Storage** | SQLite + HNSW (embedded) | PostgreSQL (external) |
| **Extensibility** | Protocol-based adapters | Monolithic |

**When to use Letta**: You want a full agent framework with built-in memory.
**When to use Headroom**: You want memory as a layer on your existing stack.

### vs Mem0

**Mem0** provides a managed memory service. Headroom differs:

| Aspect | Headroom | Mem0 |
|--------|----------|------|
| **Deployment** | Embedded (no server) | Managed service or self-hosted |
| **Scoping** | 4-level hierarchy | Flat per-user |
| **Temporal** | Supersession chains | No versioning |
| **Extraction** | Inline (zero latency) | Separate API call |
| **Search** | Semantic + full-text | Semantic only |
| **Cost** | Free (local embeddings) | API costs or infra costs |
| **Privacy** | All local | Data leaves your infra |

**When to use Mem0**: You want a managed service and don't mind external dependencies.
**When to use Headroom**: You want embedded memory with no external services.

### Feature Matrix

| Feature | Headroom | Letta | Mem0 |
|---------|:--------:|:-----:|:----:|
| Hierarchical scoping | βœ… | ❌ | ❌ |
| Temporal versioning | βœ… | ❌ | ❌ |
| Zero-latency extraction | βœ… | βœ… | ❌ |
| Full-text search | βœ… | ❌ | ❌ |
| Embedded (no server) | βœ… | ❌ | ❌ |
| One-liner integration | βœ… | ❌ | ❌ |
| Protocol-based extensibility | βœ… | ❌ | ❌ |
| Memory bubbling | βœ… | ❌ | ❌ |
| Local embeddings | βœ… | ❌ | βœ… |
| Managed service option | ❌ | ❌ | βœ… |

---

## Multi-User Isolation

Memories are isolated by `user_id`:

```python
# Alice's memories
alice_client = with_memory(OpenAI(), user_id="alice")

# Bob's memories (completely separate)
bob_client = with_memory(OpenAI(), user_id="bob")

# Bob cannot see Alice's memories, even with the same database
```

---

## Performance

| Operation | Latency | Notes |
|-----------|---------|-------|
| Memory injection | <50ms | Local embeddings + HNSW search |
| Memory extraction | +50-100 tokens | Part of LLM response (inline) |
| Memory storage | <10ms | SQLite + HNSW + FTS5 indexing |
| Cache hit | <1ms | LRU cache lookup |

**Overhead**: ~100 extra output tokens per response for the `<memory>` block.

---

## Providers

Memory works with any OpenAI-compatible client:

```python
from openai import OpenAI
from headroom import with_memory

# OpenAI
client = with_memory(OpenAI(), user_id="alice")

# Azure OpenAI
client = with_memory(
    OpenAI(base_url="https://your-resource.openai.azure.com/..."),
    user_id="alice",
)

# Groq
from groq import Groq
client = with_memory(Groq(), user_id="alice")

# Any OpenAI-compatible client
client = with_memory(YourClient(), user_id="alice")
```

---

## Example: Full Conversation Flow

```python
from openai import OpenAI
from headroom import with_memory

client = with_memory(OpenAI(), user_id="developer_jane")

# Conversation 1: User shares context
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": "I'm a Python developer at a fintech startup. We use PostgreSQL and FastAPI."
    }]
)
# Memories extracted:
#   - [FACT] Python developer at fintech startup
#   - [PREFERENCE] Uses PostgreSQL for databases
#   - [PREFERENCE] Uses FastAPI for web APIs

# Conversation 2 (new session): User asks question
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": "What database should I use for my new project?"
    }]
)
# Response references PostgreSQL preference from memory:
# β†’ "Given your experience with PostgreSQL at your fintech company,
#    I'd recommend sticking with it for consistency..."

# Check stored memories
print("Stored memories:")
for m in client.memory.get_all():
    print(f"  [{m.category.value}] {m.content}")
```

---

## Troubleshooting

### Memories not being extracted

1. Check if the conversation has memory-worthy content (not just greetings)
2. Verify the LLM is following the memory instruction
3. Enable logging: `import logging; logging.basicConfig(level=logging.DEBUG)`

### Memories not being retrieved

1. Verify `user_id` matches between sessions
2. Check if memories exist: `client.memory.get_all()`
3. Try a more specific search query
4. Check similarity threshold

### High latency

1. Use local embeddings: `embedder_backend=EmbedderBackend.LOCAL`
2. Reduce `top_k` for fewer memories to retrieve
3. Enable caching (enabled by default)

### Memory not persisting

1. Check `db_path` is the same across sessions
2. Ensure the database file is writable
3. Check for exceptions in logs

---

## Best Practices

1. **Use consistent `user_id`** - Same ID across sessions for continuity
2. **Use session scoping** - Set `session_id` for session-specific context
3. **Start with local embeddings** - Faster, free, good enough for most cases
4. **Monitor memory growth** - Use `client.memory.stats()` to track
5. **Use importance scores** - Higher importance = more likely to be retrieved
6. **Leverage categories** - Helps with debugging and selective retrieval
7. **Consider supersession** - Use `supersede()` when facts change, not `add()`