File size: 9,015 Bytes
9c9bb30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Memory

**Persistent memory for LLM applications.** Enable your AI to remember across conversations without carrying full history.

## 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.

---

## Quick Start

### Zero-Latency Memory (Recommended)

```python
from openai import OpenAI
from headroom.memory import with_fast_memory

# One line - that's it
client = with_fast_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_fast_memory()                        β”‚
β”‚                                                              β”‚
β”‚   1. INJECT: Search memories β†’ 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 memories with embeddings                   β”‚
β”‚   6. RETURN: Clean response (without memory block)          β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

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

---

## Two Approaches

### 1. Fast Memory (Inline Extraction)

```python
from headroom.memory import with_fast_memory

client = with_fast_memory(
    OpenAI(),
    user_id="alice",
    db_path="memory.db",           # SQLite storage
    top_k=5,                       # Memories to inject
    use_local_embeddings=True,    # Local model (fast) vs OpenAI API
)
```

**Characteristics:**
- Zero extra latency (extraction is part of response)
- ~100 extra output tokens per response
- Smart extraction (LLM decides what's important)
- Semantic retrieval (vector similarity)

### 2. Background Memory (Separate Extraction)

```python
from headroom.memory import with_memory

client = with_memory(
    OpenAI(),
    user_id="alice",
    db_path="memory.db",
)
```

**Characteristics:**
- Non-blocking (extraction happens in background worker)
- Separate LLM call for extraction
- Good when you don't want to modify responses

---

## Memory API

Both wrappers provide a `.memory` API for direct access:

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

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

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

# 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_chunks']}")
```

---

## Memory Categories

Memories are categorized for better organization:

| Category | Description | Examples |
|----------|-------------|----------|
| `preference` | Likes, dislikes, preferred approaches | "Prefers Python", "Likes async/await" |
| `fact` | Identity, role, constraints | "Works at fintech startup", "Senior engineer" |
| `context` | Current goals, ongoing tasks | "Migrating to microservices", "Working on auth" |

---

## Configuration

### Storage

```python
# SQLite (default, local)
client = with_fast_memory(OpenAI(), user_id="alice", db_path="memory.db")

# Custom path
client = with_fast_memory(OpenAI(), user_id="alice", db_path="/data/memories.db")
```

### Embeddings

```python
# Local embeddings (recommended - fast, free)
client = with_fast_memory(
    OpenAI(),
    user_id="alice",
    use_local_embeddings=True,
    embedding_model="all-MiniLM-L6-v2",  # 384 dimensions
)

# OpenAI embeddings (higher quality, costs money)
client = with_fast_memory(
    OpenAI(),
    user_id="alice",
    use_local_embeddings=False,  # Uses text-embedding-3-small
)
```

### Retrieval

```python
# Number of memories to inject
client = with_fast_memory(
    OpenAI(),
    user_id="alice",
    top_k=10,  # Inject up to 10 relevant memories
)
```

---

## Multi-User Isolation

Memories are isolated by `user_id`:

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

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

# Agent memories
agent_client = with_fast_memory(OpenAI(), user_id="agent-researcher")
```

---

## How Memory Enables Compression

Memory is *temporal compression*. Instead of carrying full conversation history:

```
WITHOUT MEMORY:
Context = Turn 1 + Turn 2 + ... + Turn 50 = 10,000 tokens

WITH MEMORY:
Context = 5 relevant memories = 100 tokens
Compression ratio: 100x
```

This lets you use aggressive rolling window truncation while preserving important facts.

```python
from headroom.memory import with_fast_memory
from headroom.transforms import RollingWindowTransform

# Memory + aggressive truncation = best of both worlds
client = with_fast_memory(OpenAI(), user_id="alice")
transform = RollingWindowTransform(max_tokens=4000)

# Old messages get truncated, but key facts live in memory
messages = transform.apply(very_long_conversation)
response = client.chat.completions.create(model="gpt-4o", messages=messages)
```

---

## Performance

| Operation | Latency | Notes |
|-----------|---------|-------|
| Memory injection | <50ms | Local embeddings + vector search |
| Memory extraction | +50-100ms | Part of LLM response (inline) |
| Memory storage | <10ms | SQLite write + cache update |

**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 anthropic import Anthropic
from groq import Groq

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

# Anthropic (via OpenAI-compatible wrapper)
client = with_fast_memory(OpenAI(base_url="..."), user_id="alice")

# Groq
client = with_fast_memory(Groq(), user_id="alice")

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

---

## Example: Multi-Turn Conversation

```python
from openai import OpenAI
from headroom.memory import with_fast_memory

client = with_fast_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."
    }]
)
# Memories extracted: "Python developer", "fintech startup", "uses PostgreSQL"

# 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
print(response.choices[0].message.content)
# β†’ "Given your experience with PostgreSQL at your fintech company..."
```

---

## 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. Check logs for parsing errors

### 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

### High latency

1. Switch to local embeddings: `use_local_embeddings=True`
2. Reduce `top_k` for fewer memories to retrieve
3. Check database size and consider pruning old memories

---

## Best Practices

1. **Use consistent `user_id`** - Same ID across sessions for continuity
2. **Start with local embeddings** - Faster, free, good enough for most cases
3. **Combine with rolling window** - Memory + truncation = aggressive compression
4. **Monitor memory growth** - Periodically review and prune if needed
5. **Use categories** - Helps with debugging and selective retrieval