voltmem 0.1.0__tar.gz
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.
- voltmem-0.1.0/LICENSE +21 -0
- voltmem-0.1.0/PKG-INFO +247 -0
- voltmem-0.1.0/README.md +216 -0
- voltmem-0.1.0/pyproject.toml +37 -0
- voltmem-0.1.0/setup.cfg +4 -0
- voltmem-0.1.0/tests/test_client.py +95 -0
- voltmem-0.1.0/tests/test_voltmem.py +501 -0
- voltmem-0.1.0/voltmem/__init__.py +68 -0
- voltmem-0.1.0/voltmem/client.py +239 -0
- voltmem-0.1.0/voltmem/domains.py +102 -0
- voltmem-0.1.0/voltmem/embeddings.py +161 -0
- voltmem-0.1.0/voltmem/extract.py +265 -0
- voltmem-0.1.0/voltmem/integrations/__init__.py +1 -0
- voltmem-0.1.0/voltmem/integrations/langchain.py +176 -0
- voltmem-0.1.0/voltmem/memory.py +591 -0
- voltmem-0.1.0/voltmem/scoring.py +163 -0
- voltmem-0.1.0/voltmem/store.py +202 -0
- voltmem-0.1.0/voltmem.egg-info/PKG-INFO +247 -0
- voltmem-0.1.0/voltmem.egg-info/SOURCES.txt +20 -0
- voltmem-0.1.0/voltmem.egg-info/dependency_links.txt +1 -0
- voltmem-0.1.0/voltmem.egg-info/requires.txt +12 -0
- voltmem-0.1.0/voltmem.egg-info/top_level.txt +1 -0
voltmem-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VoltMem contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
voltmem-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voltmem
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Current-truth memory for LLM agents — protect stable facts, update volatile ones.
|
|
5
|
+
Author: VoltMem contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Rouche01/voltmem
|
|
8
|
+
Project-URL: Repository, https://github.com/Rouche01/voltmem
|
|
9
|
+
Project-URL: Issues, https://github.com/Rouche01/voltmem/issues
|
|
10
|
+
Keywords: llm,agent,memory,rag,embeddings
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: embeddings
|
|
22
|
+
Requires-Dist: sentence-transformers>=3.0; extra == "embeddings"
|
|
23
|
+
Provides-Extra: langchain
|
|
24
|
+
Requires-Dist: langchain-core>=0.3.0; extra == "langchain"
|
|
25
|
+
Requires-Dist: langchain>=0.3.0; extra == "langchain"
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: sentence-transformers>=3.0; extra == "all"
|
|
28
|
+
Requires-Dist: langchain-core>=0.3.0; extra == "all"
|
|
29
|
+
Requires-Dist: langchain>=0.3.0; extra == "all"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# VoltMem
|
|
33
|
+
|
|
34
|
+
**Current-truth memory for LLM agents.**
|
|
35
|
+
|
|
36
|
+
Most memory layers treat every fact the same — your hometown and today's mood get equal
|
|
37
|
+
weight. That forces a bad tradeoff: go **stale** on fast-changing facts, or get
|
|
38
|
+
**corrupted** when a confident-but-wrong update overwrites something durable.
|
|
39
|
+
|
|
40
|
+
VoltMem scales protection and retrieval freshness by **how fast each kind of fact
|
|
41
|
+
actually changes**. Volatile facts update; stable facts resist corruption; stale
|
|
42
|
+
volatile memories rank lower at search time.
|
|
43
|
+
|
|
44
|
+
> Mem0 remembers relevant facts. VoltMem remembers **current truth**.
|
|
45
|
+
|
|
46
|
+
**Research & benchmarks:** [docs/RESEARCH.md](docs/RESEARCH.md)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install voltmem[embeddings]
|
|
54
|
+
# from source:
|
|
55
|
+
# pip install -e ".[embeddings]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Core library has **zero required dependencies**. Embeddings extras pull in
|
|
59
|
+
`sentence-transformers` (recommended). LangChain: `pip install -e ".[langchain]"`.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from voltmem import create_memory
|
|
67
|
+
|
|
68
|
+
mem = create_memory("app.db", user_id="alice")
|
|
69
|
+
|
|
70
|
+
mem.add("I live in Berlin")
|
|
71
|
+
mem.add("I prefer concise, direct answers")
|
|
72
|
+
mem.add("Actually I moved to Paris last month") # updates location, not prefs
|
|
73
|
+
|
|
74
|
+
hits = mem.search("where does the user live?", limit=3)
|
|
75
|
+
print(hits[0]["memory"]) # Actually I moved to Paris last month
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Message pairs (auto fact extraction)
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
mem.add([
|
|
82
|
+
{"role": "user", "content": "I moved to Paris. I'm working on a DB migration."},
|
|
83
|
+
], extract=True) # default for message lists — splits into atomic facts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Optional: `create_memory(..., llm_extract=True)` for Ollama-powered extraction.
|
|
87
|
+
|
|
88
|
+
### Inject into a prompt
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
memories = mem.search(user_message, limit=5)
|
|
92
|
+
context = "\n".join(f"- {m['memory']}" for m in memories)
|
|
93
|
+
system = f"What you know about this user:\n{context}"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## API
|
|
99
|
+
|
|
100
|
+
| Method | Description |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `create_memory(db, user_id)` | Factory with auto-detected embeddings |
|
|
103
|
+
| `Memory.add(text \| messages)` | Store a fact; slot-aware linking updates related memories |
|
|
104
|
+
| `Memory.search(query, limit=5)` | Ranked memories (relevance + freshness) |
|
|
105
|
+
| `Memory.get_all()` | All active memories for this user |
|
|
106
|
+
| `Memory.delete(id)` | Remove one memory |
|
|
107
|
+
| `Memory.clear()` | Wipe user namespace |
|
|
108
|
+
|
|
109
|
+
Advanced: `mem.layer` exposes `MemoryLayer` for low-level `observe()` / `write()`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Why VoltMem
|
|
114
|
+
|
|
115
|
+
| Problem | ADD-only memory | VoltMem |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| User moves cities | Berlin and Paris both stored | **Updates** to current city |
|
|
118
|
+
| Old project name in haystack | Ranks by similarity | **Down-ranks** stale volatile facts |
|
|
119
|
+
| Confident wrong blip on stable pref | Often accepted | **Resists** corruption |
|
|
120
|
+
|
|
121
|
+
### Example results (reproducible)
|
|
122
|
+
|
|
123
|
+
Run locally with `pip install -e ".[embeddings]"`. Embeddings:
|
|
124
|
+
`sentence-transformers` (`all-MiniLM-L6-v2`).
|
|
125
|
+
|
|
126
|
+
**`examples/contradiction_demo.py`** — 5-turn script vs naive always-add:
|
|
127
|
+
|
|
128
|
+
| After scenario | always-add | VoltMem |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| User moves Berlin → Paris | 2 location facts (stale + current) | **1** current fact |
|
|
131
|
+
| Paraphrase blip on stable pref | adopts blip ("really like short replies") | **keeps** original ("concise, direct answers") |
|
|
132
|
+
|
|
133
|
+
**`experiments/mem0_comparison.py`** — 3 scenarios, top-1 search (always-add baseline):
|
|
134
|
+
|
|
135
|
+
| Scenario | always-add | VoltMem |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `location_update` | WIN (2 facts stored) | WIN (**1** fact) |
|
|
138
|
+
| `stable_pref_blip` | LOSE | **WIN** |
|
|
139
|
+
| `volatile_mood` | LOSE (stale "great") | **WIN** (current "stressed") |
|
|
140
|
+
|
|
141
|
+
**VoltMem clearer wins: 2/3** (always-add also finds Paris on location, but keeps stale facts).
|
|
142
|
+
|
|
143
|
+
**`experiments/mem0_side_by_side.py`** — same 3 scenarios vs **real Mem0**
|
|
144
|
+
(open-source, `gpt-4o-mini` + `text-embedding-3-small`):
|
|
145
|
+
|
|
146
|
+
| Scenario | Mem0 | VoltMem |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `location_update` | LOSE (stale "Berlin", 2 facts) | **WIN** ("Paris", 1 fact) |
|
|
149
|
+
| `stable_pref_blip` | PARTIAL (adopts blip) | **WIN** (keeps "concise") |
|
|
150
|
+
| `volatile_mood` | LOSE (stale "great", 2 facts) | **WIN** ("stressed", 1 fact) |
|
|
151
|
+
|
|
152
|
+
**VoltMem clearer wins: 3/3.** Mem0 keeps contradictory facts; VoltMem updates volatile
|
|
153
|
+
slots and protects stable prefs via domain volatility + slot-aware linking.
|
|
154
|
+
|
|
155
|
+
**`experiments/memory_demo.py`** — 3 final Q&A checks vs ground truth:
|
|
156
|
+
|
|
157
|
+
| Policy | Score |
|
|
158
|
+
|---|---|
|
|
159
|
+
| **VoltMem** | **3/3** |
|
|
160
|
+
| never-overwrite | 2/3 |
|
|
161
|
+
| always-overwrite | 1/3 |
|
|
162
|
+
| reliability-threshold | 1/3 |
|
|
163
|
+
|
|
164
|
+
VoltMem is the only policy that both **rejects confident false blips** on stable
|
|
165
|
+
facts and **tracks weak-but-true updates** on volatile ones. Full distributions:
|
|
166
|
+
[docs/RESEARCH.md](docs/RESEARCH.md) (`llm_memory_bench.py`).
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python examples/contradiction_demo.py
|
|
170
|
+
python experiments/mem0_comparison.py
|
|
171
|
+
python experiments/mem0_side_by_side.py # pip install mem0ai; OPENAI_API_KEY or MEM0_BACKEND=ollama
|
|
172
|
+
python experiments/memory_demo.py
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Integrations
|
|
178
|
+
|
|
179
|
+
### LangChain
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install -e ".[langchain]"
|
|
183
|
+
python examples/langchain_agent.py
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from voltmem.integrations.langchain import VoltMemMemory
|
|
188
|
+
|
|
189
|
+
memory = VoltMemMemory(session_id="user-42", db_path="app.db")
|
|
190
|
+
memory.load_memory_variables({"input": "Where do I live?"})
|
|
191
|
+
memory.save_context({"input": "I moved to Paris"}, {"output": "Noted."})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Multi-tenant
|
|
195
|
+
|
|
196
|
+
One SQLite file, many users — `user_id` maps to an isolated namespace:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
alice = create_memory("app.db", user_id="alice")
|
|
200
|
+
bob = create_memory("app.db", user_id="bob")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Examples
|
|
206
|
+
|
|
207
|
+
| Script | What it shows |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `examples/contradiction_demo.py` | VoltMem vs always-add on contradictions |
|
|
210
|
+
| `experiments/mem0_comparison.py` | 3-scenario head-to-head vs always-add |
|
|
211
|
+
| `experiments/mem0_side_by_side.py` | 3-scenario head-to-head vs real Mem0 (3/3 wedge) |
|
|
212
|
+
| `examples/quickstart_batteries.py` | `remember()` / `recall()` low-level API |
|
|
213
|
+
| `examples/multi_tenant.py` | One DB, many users |
|
|
214
|
+
| `examples/langchain_agent.py` | LangChain adapter |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Domain volatility priors
|
|
219
|
+
|
|
220
|
+
| Domain | Volatility | Behavior |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `personality_trait` | 0.05 | Very protected |
|
|
223
|
+
| `core_preference` | 0.08 | Very protected |
|
|
224
|
+
| `biographical` | 0.10 | High protection |
|
|
225
|
+
| `current_project` | 0.55 | Updates readily |
|
|
226
|
+
| `emotional_context` | 0.80 | Fast-moving |
|
|
227
|
+
| `current_task` | 0.90 | Minimal protection |
|
|
228
|
+
|
|
229
|
+
Custom domains: `voltmem/domains.py`.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pip install -e ".[all]"
|
|
237
|
+
python tests/test_voltmem.py
|
|
238
|
+
python tests/test_client.py
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Experiments and benchmarks live in `experiments/` — see [docs/RESEARCH.md](docs/RESEARCH.md).
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
voltmem-0.1.0/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# VoltMem
|
|
2
|
+
|
|
3
|
+
**Current-truth memory for LLM agents.**
|
|
4
|
+
|
|
5
|
+
Most memory layers treat every fact the same — your hometown and today's mood get equal
|
|
6
|
+
weight. That forces a bad tradeoff: go **stale** on fast-changing facts, or get
|
|
7
|
+
**corrupted** when a confident-but-wrong update overwrites something durable.
|
|
8
|
+
|
|
9
|
+
VoltMem scales protection and retrieval freshness by **how fast each kind of fact
|
|
10
|
+
actually changes**. Volatile facts update; stable facts resist corruption; stale
|
|
11
|
+
volatile memories rank lower at search time.
|
|
12
|
+
|
|
13
|
+
> Mem0 remembers relevant facts. VoltMem remembers **current truth**.
|
|
14
|
+
|
|
15
|
+
**Research & benchmarks:** [docs/RESEARCH.md](docs/RESEARCH.md)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install voltmem[embeddings]
|
|
23
|
+
# from source:
|
|
24
|
+
# pip install -e ".[embeddings]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Core library has **zero required dependencies**. Embeddings extras pull in
|
|
28
|
+
`sentence-transformers` (recommended). LangChain: `pip install -e ".[langchain]"`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from voltmem import create_memory
|
|
36
|
+
|
|
37
|
+
mem = create_memory("app.db", user_id="alice")
|
|
38
|
+
|
|
39
|
+
mem.add("I live in Berlin")
|
|
40
|
+
mem.add("I prefer concise, direct answers")
|
|
41
|
+
mem.add("Actually I moved to Paris last month") # updates location, not prefs
|
|
42
|
+
|
|
43
|
+
hits = mem.search("where does the user live?", limit=3)
|
|
44
|
+
print(hits[0]["memory"]) # Actually I moved to Paris last month
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Message pairs (auto fact extraction)
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
mem.add([
|
|
51
|
+
{"role": "user", "content": "I moved to Paris. I'm working on a DB migration."},
|
|
52
|
+
], extract=True) # default for message lists — splits into atomic facts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Optional: `create_memory(..., llm_extract=True)` for Ollama-powered extraction.
|
|
56
|
+
|
|
57
|
+
### Inject into a prompt
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
memories = mem.search(user_message, limit=5)
|
|
61
|
+
context = "\n".join(f"- {m['memory']}" for m in memories)
|
|
62
|
+
system = f"What you know about this user:\n{context}"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
| Method | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `create_memory(db, user_id)` | Factory with auto-detected embeddings |
|
|
72
|
+
| `Memory.add(text \| messages)` | Store a fact; slot-aware linking updates related memories |
|
|
73
|
+
| `Memory.search(query, limit=5)` | Ranked memories (relevance + freshness) |
|
|
74
|
+
| `Memory.get_all()` | All active memories for this user |
|
|
75
|
+
| `Memory.delete(id)` | Remove one memory |
|
|
76
|
+
| `Memory.clear()` | Wipe user namespace |
|
|
77
|
+
|
|
78
|
+
Advanced: `mem.layer` exposes `MemoryLayer` for low-level `observe()` / `write()`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Why VoltMem
|
|
83
|
+
|
|
84
|
+
| Problem | ADD-only memory | VoltMem |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| User moves cities | Berlin and Paris both stored | **Updates** to current city |
|
|
87
|
+
| Old project name in haystack | Ranks by similarity | **Down-ranks** stale volatile facts |
|
|
88
|
+
| Confident wrong blip on stable pref | Often accepted | **Resists** corruption |
|
|
89
|
+
|
|
90
|
+
### Example results (reproducible)
|
|
91
|
+
|
|
92
|
+
Run locally with `pip install -e ".[embeddings]"`. Embeddings:
|
|
93
|
+
`sentence-transformers` (`all-MiniLM-L6-v2`).
|
|
94
|
+
|
|
95
|
+
**`examples/contradiction_demo.py`** — 5-turn script vs naive always-add:
|
|
96
|
+
|
|
97
|
+
| After scenario | always-add | VoltMem |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| User moves Berlin → Paris | 2 location facts (stale + current) | **1** current fact |
|
|
100
|
+
| Paraphrase blip on stable pref | adopts blip ("really like short replies") | **keeps** original ("concise, direct answers") |
|
|
101
|
+
|
|
102
|
+
**`experiments/mem0_comparison.py`** — 3 scenarios, top-1 search (always-add baseline):
|
|
103
|
+
|
|
104
|
+
| Scenario | always-add | VoltMem |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `location_update` | WIN (2 facts stored) | WIN (**1** fact) |
|
|
107
|
+
| `stable_pref_blip` | LOSE | **WIN** |
|
|
108
|
+
| `volatile_mood` | LOSE (stale "great") | **WIN** (current "stressed") |
|
|
109
|
+
|
|
110
|
+
**VoltMem clearer wins: 2/3** (always-add also finds Paris on location, but keeps stale facts).
|
|
111
|
+
|
|
112
|
+
**`experiments/mem0_side_by_side.py`** — same 3 scenarios vs **real Mem0**
|
|
113
|
+
(open-source, `gpt-4o-mini` + `text-embedding-3-small`):
|
|
114
|
+
|
|
115
|
+
| Scenario | Mem0 | VoltMem |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `location_update` | LOSE (stale "Berlin", 2 facts) | **WIN** ("Paris", 1 fact) |
|
|
118
|
+
| `stable_pref_blip` | PARTIAL (adopts blip) | **WIN** (keeps "concise") |
|
|
119
|
+
| `volatile_mood` | LOSE (stale "great", 2 facts) | **WIN** ("stressed", 1 fact) |
|
|
120
|
+
|
|
121
|
+
**VoltMem clearer wins: 3/3.** Mem0 keeps contradictory facts; VoltMem updates volatile
|
|
122
|
+
slots and protects stable prefs via domain volatility + slot-aware linking.
|
|
123
|
+
|
|
124
|
+
**`experiments/memory_demo.py`** — 3 final Q&A checks vs ground truth:
|
|
125
|
+
|
|
126
|
+
| Policy | Score |
|
|
127
|
+
|---|---|
|
|
128
|
+
| **VoltMem** | **3/3** |
|
|
129
|
+
| never-overwrite | 2/3 |
|
|
130
|
+
| always-overwrite | 1/3 |
|
|
131
|
+
| reliability-threshold | 1/3 |
|
|
132
|
+
|
|
133
|
+
VoltMem is the only policy that both **rejects confident false blips** on stable
|
|
134
|
+
facts and **tracks weak-but-true updates** on volatile ones. Full distributions:
|
|
135
|
+
[docs/RESEARCH.md](docs/RESEARCH.md) (`llm_memory_bench.py`).
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python examples/contradiction_demo.py
|
|
139
|
+
python experiments/mem0_comparison.py
|
|
140
|
+
python experiments/mem0_side_by_side.py # pip install mem0ai; OPENAI_API_KEY or MEM0_BACKEND=ollama
|
|
141
|
+
python experiments/memory_demo.py
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Integrations
|
|
147
|
+
|
|
148
|
+
### LangChain
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pip install -e ".[langchain]"
|
|
152
|
+
python examples/langchain_agent.py
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from voltmem.integrations.langchain import VoltMemMemory
|
|
157
|
+
|
|
158
|
+
memory = VoltMemMemory(session_id="user-42", db_path="app.db")
|
|
159
|
+
memory.load_memory_variables({"input": "Where do I live?"})
|
|
160
|
+
memory.save_context({"input": "I moved to Paris"}, {"output": "Noted."})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Multi-tenant
|
|
164
|
+
|
|
165
|
+
One SQLite file, many users — `user_id` maps to an isolated namespace:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
alice = create_memory("app.db", user_id="alice")
|
|
169
|
+
bob = create_memory("app.db", user_id="bob")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Examples
|
|
175
|
+
|
|
176
|
+
| Script | What it shows |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `examples/contradiction_demo.py` | VoltMem vs always-add on contradictions |
|
|
179
|
+
| `experiments/mem0_comparison.py` | 3-scenario head-to-head vs always-add |
|
|
180
|
+
| `experiments/mem0_side_by_side.py` | 3-scenario head-to-head vs real Mem0 (3/3 wedge) |
|
|
181
|
+
| `examples/quickstart_batteries.py` | `remember()` / `recall()` low-level API |
|
|
182
|
+
| `examples/multi_tenant.py` | One DB, many users |
|
|
183
|
+
| `examples/langchain_agent.py` | LangChain adapter |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Domain volatility priors
|
|
188
|
+
|
|
189
|
+
| Domain | Volatility | Behavior |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| `personality_trait` | 0.05 | Very protected |
|
|
192
|
+
| `core_preference` | 0.08 | Very protected |
|
|
193
|
+
| `biographical` | 0.10 | High protection |
|
|
194
|
+
| `current_project` | 0.55 | Updates readily |
|
|
195
|
+
| `emotional_context` | 0.80 | Fast-moving |
|
|
196
|
+
| `current_task` | 0.90 | Minimal protection |
|
|
197
|
+
|
|
198
|
+
Custom domains: `voltmem/domains.py`.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pip install -e ".[all]"
|
|
206
|
+
python tests/test_voltmem.py
|
|
207
|
+
python tests/test_client.py
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Experiments and benchmarks live in `experiments/` — see [docs/RESEARCH.md](docs/RESEARCH.md).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "voltmem"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Current-truth memory for LLM agents — protect stable facts, update volatile ones."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "VoltMem contributors" }]
|
|
13
|
+
keywords = ["llm", "agent", "memory", "rag", "embeddings"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
embeddings = ["sentence-transformers>=3.0"]
|
|
27
|
+
langchain = ["langchain-core>=0.3.0", "langchain>=0.3.0"]
|
|
28
|
+
all = ["sentence-transformers>=3.0", "langchain-core>=0.3.0", "langchain>=0.3.0"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/Rouche01/voltmem"
|
|
32
|
+
Repository = "https://github.com/Rouche01/voltmem"
|
|
33
|
+
Issues = "https://github.com/Rouche01/voltmem/issues"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["voltmem*"]
|
voltmem-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Tests for the product-facing Memory API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
sys.path.insert(0, str(ROOT))
|
|
10
|
+
|
|
11
|
+
from voltmem import Memory, create_memory # noqa: E402
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_create_memory_and_add_search():
|
|
15
|
+
with create_memory(":memory:", user_id="u1", embeddings=False) as mem:
|
|
16
|
+
mem.add("I live in Berlin")
|
|
17
|
+
mem.add("I live in Paris now")
|
|
18
|
+
hits = mem.search("where does the user live", limit=3)
|
|
19
|
+
assert hits
|
|
20
|
+
assert any("Paris" in h["memory"] for h in hits)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_add_messages_list():
|
|
24
|
+
with Memory(user_id="u2", db_path=":memory:") as mem:
|
|
25
|
+
out = mem.add([
|
|
26
|
+
{"role": "user", "content": "I prefer dark mode"},
|
|
27
|
+
{"role": "assistant", "content": "Noted."},
|
|
28
|
+
])
|
|
29
|
+
assert len(out) >= 1
|
|
30
|
+
assert mem.get_all()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_delete_and_clear():
|
|
34
|
+
with Memory(user_id="u3", db_path=":memory:") as mem:
|
|
35
|
+
row = mem.add("temporary fact")
|
|
36
|
+
mid = row["id"]
|
|
37
|
+
assert mem.delete(mid)
|
|
38
|
+
assert mem.get(mid) is None
|
|
39
|
+
mem.add("another")
|
|
40
|
+
mem.clear()
|
|
41
|
+
assert mem.get_all() == []
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_add_messages_extracts_sentences():
|
|
45
|
+
with Memory(user_id="u4", db_path=":memory:") as mem:
|
|
46
|
+
out = mem.add([
|
|
47
|
+
{"role": "user", "content": "I live in Berlin. I prefer tea."},
|
|
48
|
+
], extract=True)
|
|
49
|
+
assert len(out) >= 2
|
|
50
|
+
assert mem.get_all()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_add_messages_no_extract():
|
|
54
|
+
with Memory(user_id="u5", db_path=":memory:") as mem:
|
|
55
|
+
out = mem.add([
|
|
56
|
+
{"role": "user", "content": "I live in Berlin. I prefer tea."},
|
|
57
|
+
], extract=False)
|
|
58
|
+
assert len(out) == 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_multi_tenant_isolation():
|
|
62
|
+
fd, path = tempfile.mkstemp(suffix=".db")
|
|
63
|
+
os.close(fd)
|
|
64
|
+
try:
|
|
65
|
+
with Memory(user_id="alice", db_path=path) as a, \
|
|
66
|
+
Memory(user_id="bob", db_path=path) as b:
|
|
67
|
+
a.add("I live in Berlin")
|
|
68
|
+
b.add("I live in Paris")
|
|
69
|
+
assert "Berlin" in a.search("where live")[0]["memory"]
|
|
70
|
+
assert "Paris" in b.search("where live")[0]["memory"]
|
|
71
|
+
finally:
|
|
72
|
+
os.unlink(path)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
tests = [
|
|
77
|
+
test_create_memory_and_add_search,
|
|
78
|
+
test_add_messages_list,
|
|
79
|
+
test_add_messages_extracts_sentences,
|
|
80
|
+
test_add_messages_no_extract,
|
|
81
|
+
test_delete_and_clear,
|
|
82
|
+
test_multi_tenant_isolation,
|
|
83
|
+
]
|
|
84
|
+
passed = failed = 0
|
|
85
|
+
for t in tests:
|
|
86
|
+
try:
|
|
87
|
+
t()
|
|
88
|
+
print(f" PASS {t.__name__}")
|
|
89
|
+
passed += 1
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f" FAIL {t.__name__}: {e}")
|
|
92
|
+
failed += 1
|
|
93
|
+
print(f"\n{passed}/{passed + failed} tests passed")
|
|
94
|
+
if failed:
|
|
95
|
+
sys.exit(1)
|