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 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
@@ -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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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)