reeve 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.
Files changed (51) hide show
  1. reeve-0.1.0/LICENSE +21 -0
  2. reeve-0.1.0/PKG-INFO +611 -0
  3. reeve-0.1.0/README.md +565 -0
  4. reeve-0.1.0/pyproject.toml +92 -0
  5. reeve-0.1.0/reeve/__init__.py +36 -0
  6. reeve-0.1.0/reeve/tools.py +26 -0
  7. reeve-0.1.0/reeve.egg-info/PKG-INFO +611 -0
  8. reeve-0.1.0/reeve.egg-info/SOURCES.txt +49 -0
  9. reeve-0.1.0/reeve.egg-info/dependency_links.txt +1 -0
  10. reeve-0.1.0/reeve.egg-info/entry_points.txt +5 -0
  11. reeve-0.1.0/reeve.egg-info/requires.txt +24 -0
  12. reeve-0.1.0/reeve.egg-info/top_level.txt +2 -0
  13. reeve-0.1.0/setup.cfg +4 -0
  14. reeve-0.1.0/tests/test_bedrock_llm.py +68 -0
  15. reeve-0.1.0/tests/test_cli.py +50 -0
  16. reeve-0.1.0/tests/test_config_bedrock.py +27 -0
  17. reeve-0.1.0/tests/test_evaluation.py +111 -0
  18. reeve-0.1.0/tests/test_ingestion_dataset.py +143 -0
  19. reeve-0.1.0/tests/test_operator.py +109 -0
  20. reeve-0.1.0/tests/test_reconciler_metadata.py +124 -0
  21. reeve-0.1.0/tests/test_retriever_temporal.py +54 -0
  22. reeve-0.1.0/tests/test_schemas.py +22 -0
  23. reeve-0.1.0/tests/test_utils.py +19 -0
  24. reeve-0.1.0/threelane_memory/__init__.py +102 -0
  25. reeve-0.1.0/threelane_memory/__main__.py +5 -0
  26. reeve-0.1.0/threelane_memory/auth.py +192 -0
  27. reeve-0.1.0/threelane_memory/backup.py +173 -0
  28. reeve-0.1.0/threelane_memory/bedrock_llm.py +123 -0
  29. reeve-0.1.0/threelane_memory/chat.py +133 -0
  30. reeve-0.1.0/threelane_memory/cli.py +292 -0
  31. reeve-0.1.0/threelane_memory/config.py +133 -0
  32. reeve-0.1.0/threelane_memory/database.py +225 -0
  33. reeve-0.1.0/threelane_memory/dual_ingest.py +685 -0
  34. reeve-0.1.0/threelane_memory/embeddings.py +68 -0
  35. reeve-0.1.0/threelane_memory/entity_dedup.py +174 -0
  36. reeve-0.1.0/threelane_memory/evaluation.py +77 -0
  37. reeve-0.1.0/threelane_memory/ingestion.py +474 -0
  38. reeve-0.1.0/threelane_memory/llm_interface.py +73 -0
  39. reeve-0.1.0/threelane_memory/mcp_server.py +182 -0
  40. reeve-0.1.0/threelane_memory/mem0_dual_ingest.py +11 -0
  41. reeve-0.1.0/threelane_memory/mem0_ingest.py +469 -0
  42. reeve-0.1.0/threelane_memory/oauth2.py +215 -0
  43. reeve-0.1.0/threelane_memory/operator.py +231 -0
  44. reeve-0.1.0/threelane_memory/reconciler.py +708 -0
  45. reeve-0.1.0/threelane_memory/reeve_dual_ingest.py +11 -0
  46. reeve-0.1.0/threelane_memory/reeve_ingest.py +484 -0
  47. reeve-0.1.0/threelane_memory/retriever.py +573 -0
  48. reeve-0.1.0/threelane_memory/schemas.py +39 -0
  49. reeve-0.1.0/threelane_memory/static/index.html +189 -0
  50. reeve-0.1.0/threelane_memory/tools.py +93 -0
  51. reeve-0.1.0/threelane_memory/utils.py +6 -0
reeve-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ankesh Kumar
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.
reeve-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,611 @@
1
+ Metadata-Version: 2.4
2
+ Name: reeve
3
+ Version: 0.1.0
4
+ Summary: A personal long-term memory system built on Neo4j and OpenAI — stores and recalls episodic memories across a 70-year lifespan using a hybrid 3-lane retrieval engine.
5
+ Author: Ankesh Kumar
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ankeshkumar/threelane-memory
8
+ Project-URL: Repository, https://github.com/ankeshkumar/threelane-memory
9
+ Project-URL: Issues, https://github.com/ankeshkumar/threelane-memory/issues
10
+ Project-URL: Documentation, https://github.com/ankeshkumar/threelane-memory#readme
11
+ Keywords: memory,neo4j,knowledge-graph,openai,embeddings,long-term-memory,personal-ai
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Database
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: neo4j>=5.0
26
+ Requires-Dist: langchain>=0.2
27
+ Requires-Dist: langchain-ollama>=0.2
28
+ Requires-Dist: numpy>=1.24
29
+ Requires-Dist: python-dotenv>=1.0
30
+ Provides-Extra: openai
31
+ Requires-Dist: langchain-openai>=0.1; extra == "openai"
32
+ Requires-Dist: openai>=1.0; extra == "openai"
33
+ Provides-Extra: streamlit
34
+ Requires-Dist: streamlit>=1.30; extra == "streamlit"
35
+ Provides-Extra: mcp
36
+ Requires-Dist: mcp>=1.6; extra == "mcp"
37
+ Requires-Dist: google-auth>=2.0.0; extra == "mcp"
38
+ Requires-Dist: uvicorn>=0.20.0; extra == "mcp"
39
+ Requires-Dist: starlette>=0.30.0; extra == "mcp"
40
+ Provides-Extra: dev
41
+ Requires-Dist: pytest>=7.0; extra == "dev"
42
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.4; extra == "dev"
44
+ Requires-Dist: mypy>=1.8; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ # threelane-memory
48
+
49
+ **Lifetime persistent memory for LLMs — a knowledge graph that remembers everything, knows what changed, and never forgets what matters.**
50
+
51
+ Every fact you store today is still retrievable 50 years from now — accurately, instantly, and in context.
52
+
53
+ ```python
54
+ from threelane_memory import store, query
55
+
56
+ store("I love playing football")
57
+ store("I just started at Google as a software engineer in San Francisco")
58
+
59
+ # Months later...
60
+ store("I got promoted to senior engineer at Google")
61
+ store("I moved from San Francisco to New York")
62
+
63
+ # A friend invites you to play football — should you go?
64
+ query("My friend invited me to play football, should I go?")
65
+ # → "Yes, you should go! You love playing football."
66
+
67
+ query("Where do I work?") # → "Google, as a senior software engineer." (role updated)
68
+ query("Where do I live?") # → "New York." (not SF — state was superseded)
69
+ query("Did I ever live in SF?") # → "Yes, before moving to New York." (history preserved)
70
+ ```
71
+
72
+ The football memory was stored months ago in a completely different conversation. But when you need it, Threelane connects the dots — because it actually *understands* what it stored, not just what's similar.
73
+
74
+ No context windows. No token limits. No forgetting. **Persistent memory that outlives any single conversation — or any single year.**
75
+
76
+ ---
77
+
78
+ ## Why This Exists
79
+
80
+ LLMs are stateless. Every conversation starts from zero. The common workarounds all break down over time:
81
+
82
+ | Approach | Works for a week | Breaks at scale |
83
+ |---|---|---|
84
+ | **Chat history** | ✓ | Grows unbounded, no structure, can't handle contradictions |
85
+ | **Vector stores** (Pinecone, ChromaDB) | ✓ | Flat chunks — "I moved to NYC" never invalidates "I live in SF" |
86
+ | **RAG pipelines** | ✓ | Retrieves similar text, not relevant *knowledge* |
87
+ | **Context-window managers** (MemGPT) | ✓ | Clever paging, but still raw text — no entity tracking, no state evolution |
88
+
89
+ Ask any of them: *"What changed about me since last year?"* — silence.
90
+
91
+ Ask Threelane — it knows, because it actually tracks entities, states, and time.
92
+
93
+ ## How It Works
94
+
95
+ Threelane doesn't store text. It **understands** it.
96
+
97
+ Every input is parsed by an LLM into structured semantics — entities, actions, states, roles, emotions, locations — and written into a **Neo4j knowledge graph** with typed relationships. This isn't an embedding dump. It's a living, evolving model of everything you've told it.
98
+
99
+ ### What Happens When You Store a Memory
100
+
101
+ *"I love playing football"* becomes a structured graph:
102
+
103
+ ```
104
+ (Episode) ──INVOLVES──▶ (Entity: you)
105
+
106
+ ├──HAS_STATE──▶ (State: hobby=football, sentiment=love) ──OF_ENTITY──▶ (Entity: you)
107
+ └──INVOLVES──▶ (Entity: football)
108
+ ```
109
+
110
+ *"I just started at Google as a software engineer in San Francisco"*:
111
+
112
+ ```
113
+ (Episode) ──INVOLVES──▶ (Entity: Google)
114
+ │ │
115
+ ├──HAS_ACTION──▶ (Action: started working) ──BY_ENTITY──▶ (Entity: you)
116
+ │ ──ON_ENTITY──▶ (Entity: Google)
117
+ ├──HAS_STATE──▶ (State: role=software engineer) ──OF_ENTITY──▶ (Entity: you)
118
+ └──AT_LOCATION──▶ (Location: San Francisco)
119
+ ```
120
+
121
+ Months later, when someone asks *"My friend invited me to play football, should I go?"*, Threelane retrieves the football episode by semantic similarity, sees the `sentiment=love` state, and answers: *"Yes! You love playing football."*
122
+
123
+ No explicit link was ever made between the question and the stored memory — the knowledge graph makes the connection automatically.
124
+
125
+ ### What Happens When Facts Change
126
+
127
+ *"I moved from San Francisco to New York"* — the old state is **superseded**, not deleted or duplicated. Full history is preserved:
128
+
129
+ ```
130
+ (State: city=New York, active=true) ──SUPERSEDES──▶ (State: city=San Francisco, active=false)
131
+ ```
132
+
133
+ Ask *"Where do I live?"* → gets the current answer: New York.
134
+ Ask *"Where was I living when I joined Google?"* → still knows it was San Francisco.
135
+
136
+ **This is what persistent memory actually means.** Not similarity search — structured, evolving knowledge with a complete audit trail.
137
+
138
+ ### 3-Lane Retrieval: Why the Right Memory Surfaces
139
+
140
+ Most systems rank by vector similarity alone. That works for a week. Over years, your "I got married" memory gets buried under thousands of newer, more recent entries.
141
+
142
+ Threelane scores every candidate across **three lanes** simultaneously:
143
+
144
+ ```
145
+ score = 0.65 × vector_similarity + 0.30 × importance + 0.05 × recency
146
+ ```
147
+
148
+ - **Vector similarity** — semantic relevance via ANN search
149
+ - **Importance** — LLM-assigned at ingestion (landmark life events score higher)
150
+ - **Recency** — exponential decay with a 365-day half-life
151
+
152
+ The key: memories above importance 0.75 **bypass recency decay entirely**. Your wedding, your child's birth, a cancer diagnosis — these surface instantly no matter how old they are. Yesterday's lunch order won't outrank them.
153
+
154
+ ### Entity Resolution: One Identity, Many Names
155
+
156
+ *"Google"*, *"my company"*, *"the office"*, *"work"* — all resolve to the same canonical entity through 3-layer matching:
157
+
158
+ 1. **Case-insensitive exact** — instant lookup
159
+ 2. **Substring containment** — "my company Google" → Google
160
+ 3. **Embedding similarity** — cosine ≥ 0.88 catches semantic equivalents
161
+
162
+ ### Designed to Last a Lifetime
163
+
164
+ Most memory systems assume weeks of data. Threelane is engineered for a **70-year lifespan**:
165
+
166
+ | What could go wrong | How Threelane handles it |
167
+ |---|---|
168
+ | Graph grows to millions of nodes | Dynamic candidate pool scales with size (2% of episodes, 50–500) |
169
+ | Old memories become noise | Consolidation engine LLM-summarizes old low-importance episodes |
170
+ | Entity names fragment over years | Background 3-pass deduplication merges fragmented nodes |
171
+ | Facts become outdated | SUPERSEDES chain — current state always queryable, history preserved |
172
+ | Embedding models get replaced | Model version tags + batch reindex utility |
173
+ | Disaster strikes | Full JSON backup with timestamped exports |
174
+
175
+ **Store a memory on day one. Query it on day 25,550 (year 70). It's still there, still accurate, still in context.**
176
+
177
+ ---
178
+
179
+ ## Features
180
+
181
+ - **Pluggable LLM providers** — Ollama (local, free) or OpenAI (cloud); switch via one env var
182
+ - **Semantic extraction** — LLM parses text into structured entities, actions, states, roles, emotions, importance, and location
183
+ - **Neo4j knowledge graph** — Episodes, Entities, Actions, States, Roles, Locations with typed relationships
184
+ - **3-layer entity resolution** — case-insensitive → substring → embedding similarity (≥ 0.88)
185
+ - **State contradiction handling** — new facts `SUPERSEDE` old ones with full audit trail
186
+ - **Vector ANN search** — Provider-matched embeddings indexed in Neo4j
187
+ - **70-year recency tuning** — 5% recency weight, 365-day half-life, importance floor bypass
188
+ - **Temporal queries** — supports "last 3 months", "in 2024", "March 2025" natively
189
+ - **Memory consolidation** — LLM-summarizes old low-importance episodes
190
+ - **Backup & export** — full graph dump to timestamped JSON
191
+ - **Entity deduplication** — 3-pass background scan and merge
192
+
193
+ ## Installation
194
+
195
+ ```bash
196
+ pip install threelane-memory
197
+ ```
198
+
199
+ If you want to run it as an MCP server, install MCP extras:
200
+
201
+ ```bash
202
+ pip install "threelane-memory[mcp]"
203
+ ```
204
+
205
+ Or install from source:
206
+
207
+ ```bash
208
+ git clone https://github.com/ankeshkumar/threelane-memory.git
209
+ cd threelane-memory
210
+ pip install -e .
211
+ ```
212
+
213
+ ### Prerequisites
214
+
215
+ - **Python 3.10+**
216
+ - **Neo4j 5.x** (Aura or self-hosted with vector index support)
217
+ - **LLM provider** — one of:
218
+ - **Ollama** (default, free, local) — [Install Ollama](https://ollama.com)
219
+ - **OpenAI** — requires API key from [platform.openai.com](https://platform.openai.com/api-keys)
220
+
221
+ ### Configuration
222
+
223
+ Copy the example environment file and fill in your credentials:
224
+
225
+ ```bash
226
+ cp .env.example .env
227
+ ```
228
+
229
+ #### Option A — Ollama (local, default)
230
+
231
+ ```bash
232
+ # Install & start Ollama
233
+ brew install ollama
234
+ ollama serve # keep running in a separate terminal
235
+
236
+ # Pull required models
237
+ ollama pull llama3.2:3b
238
+ ollama pull nomic-embed-text
239
+ ```
240
+
241
+ Your `.env` only needs Neo4j credentials — Ollama settings default automatically:
242
+
243
+ ```env
244
+ LLM_PROVIDER=ollama
245
+ NEO4J_URI=neo4j+s://your-instance.databases.neo4j.io
246
+ NEO4J_USER=neo4j
247
+ NEO4J_PASSWORD=your-password
248
+ ```
249
+
250
+ #### Option B — OpenAI (cloud)
251
+
252
+ ```env
253
+ LLM_PROVIDER=openai
254
+ OPENAI_API_KEY=sk-your-api-key
255
+ NEO4J_URI=neo4j+s://your-instance.databases.neo4j.io
256
+ NEO4J_USER=neo4j
257
+ NEO4J_PASSWORD=your-password
258
+ ```
259
+
260
+ Create the required Neo4j vector index (run once in Neo4j Browser — set dimension to match your provider):
261
+
262
+ ```cypher
263
+ -- Ollama (nomic-embed-text): 768 dimensions
264
+ -- OpenAI (text-embedding-ada-002): 1536 dimensions
265
+ CREATE VECTOR INDEX episode_embedding IF NOT EXISTS
266
+ FOR (ep:Episode) ON (ep.embedding)
267
+ OPTIONS {indexConfig: {
268
+ `vector.dimensions`: 768,
269
+ `vector.similarity_function`: 'cosine'
270
+ }};
271
+ ```
272
+
273
+ Verify your setup:
274
+
275
+ ```bash
276
+ threelane-memory config
277
+ ```
278
+
279
+ ## Quick Start
280
+
281
+ ### Python API
282
+
283
+ ```python
284
+ from threelane_memory import store, query, close
285
+
286
+ # Store memories
287
+ store("I love playing football", speaker="ankesh")
288
+ store("I work at Google as a software engineer", speaker="ankesh")
289
+
290
+ # Query — even months later, across different conversations
291
+ answer = query("My friend invited me to play football, should I go?", speaker="ankesh")
292
+ print(answer) # "Yes, you should! You love playing football."
293
+
294
+ close()
295
+ ```
296
+
297
+ ### CLI
298
+
299
+ ```bash
300
+ # Interactive chat
301
+ threelane-memory chat --speaker ankesh
302
+
303
+ # Store a single memory
304
+ threelane-memory store "I love playing football" --speaker ankesh
305
+
306
+ # Query — connects to stored knowledge automatically
307
+ threelane-memory query "My friend invited me to play football, should I go?" --speaker ankesh
308
+
309
+ # Show active provider, models & run health checks
310
+ threelane-memory config
311
+
312
+ # Admin operations
313
+ threelane-memory backup --speaker ankesh
314
+ threelane-memory dedup
315
+ threelane-memory dedup --dry-run
316
+ threelane-memory consolidate --speaker ankesh
317
+ threelane-memory reindex --speaker ankesh --old-model nomic-embed-text
318
+ ```
319
+
320
+ Or via `python -m`:
321
+
322
+ ```bash
323
+ python -m threelane_memory chat --speaker ankesh
324
+ ```
325
+
326
+ ### MCP Server
327
+
328
+ Run as an MCP server over stdio (recommended for MCP clients like Claude Desktop / VS Code):
329
+
330
+ ```bash
331
+ threelane-memory mcp
332
+ ```
333
+
334
+ Or run directly from the dedicated entrypoint:
335
+
336
+ ```bash
337
+ threelane-memory-mcp
338
+ ```
339
+
340
+ Network transport (SSE) is also supported:
341
+
342
+ ```bash
343
+ threelane-memory mcp --transport sse --host 127.0.0.1 --port 8000
344
+ ```
345
+
346
+ Exposed MCP tools include:
347
+
348
+ - `store_memory`
349
+ - `query_memory`
350
+ - `retrieve_memory_context`
351
+ - `memory_config`
352
+ - `backup_memory`
353
+ - `deduplicate_memory_entities`
354
+ - `consolidate_memory`
355
+
356
+ ### Deploy MCP Server
357
+
358
+ #### Option A — Docker (SSE on port 8000)
359
+
360
+ Deployment files:
361
+
362
+ - `deploy/mcp/Dockerfile`
363
+ - `deploy/mcp/docker-compose.yml`
364
+
365
+ Run:
366
+
367
+ ```bash
368
+ cd deploy/mcp
369
+ docker compose up -d --build
370
+ ```
371
+
372
+ Stop:
373
+
374
+ ```bash
375
+ cd deploy/mcp
376
+ docker compose down
377
+ ```
378
+
379
+ Client configuration templates for Claude and other MCP clients:
380
+
381
+ - `docs/mcp-client-configs.md`
382
+ - `deploy/mcp/client-configs/`
383
+
384
+ #### Option B — macOS launchd (always-on local service)
385
+
386
+ Template file:
387
+
388
+ - `deploy/mcp/com.threelane.memory.mcp.plist`
389
+
390
+ Install and start:
391
+
392
+ ```bash
393
+ cp deploy/mcp/com.threelane.memory.mcp.plist ~/Library/LaunchAgents/
394
+ launchctl load ~/Library/LaunchAgents/com.threelane.memory.mcp.plist
395
+ ```
396
+
397
+ Stop and unload:
398
+
399
+ ```bash
400
+ launchctl unload ~/Library/LaunchAgents/com.threelane.memory.mcp.plist
401
+ ```
402
+
403
+ ## Architecture
404
+
405
+ ```
406
+ User Input
407
+
408
+ ├── Statement ──▶ operator.py ──▶ reconciler.py ──▶ Neo4j Graph
409
+ │ (LLM) (entity resolution,
410
+ │ state contradiction,
411
+ │ graph writing)
412
+
413
+ └── Question ──▶ retriever.py ──▶ Neo4j Vector Index
414
+ (3-lane search) + Graph Traversal
415
+ ──▶ llm_interface.py ──▶ Answer
416
+ (LLM)
417
+ ```
418
+
419
+ See [docs/flowcharts.md](docs/flowcharts.md) for detailed architecture diagrams.
420
+
421
+ ## Switching Providers
422
+
423
+ threelane-memory auto-detects the correct embedding dimension and warns you about mismatches. Run `threelane-memory config` at any time to check your setup.
424
+
425
+ ### Ollama → OpenAI
426
+
427
+ ```bash
428
+ # 1. Update .env
429
+ LLM_PROVIDER=openai
430
+ OPENAI_API_KEY=sk-your-api-key
431
+
432
+ # 2. Install the optional OpenAI dependency
433
+ pip install threelane-memory[openai]
434
+
435
+ # 3. Verify (will warn if Neo4j index dimension doesn't match)
436
+ threelane-memory config
437
+
438
+ # 4. Recreate the vector index (1536-dim for text-embedding-ada-002)
439
+ # In Neo4j Browser:
440
+ # DROP INDEX episode_embedding;
441
+ # CREATE VECTOR INDEX episode_embedding IF NOT EXISTS
442
+ # FOR (ep:Episode) ON (ep.embedding)
443
+ # OPTIONS {indexConfig: {`vector.dimensions`: 1536, `vector.similarity_function`: 'cosine'}};
444
+
445
+ # 5. Re-embed all episodes with the new model
446
+ threelane-memory reindex --old-model nomic-embed-text
447
+ ```
448
+
449
+ ### OpenAI → Ollama
450
+
451
+ ```bash
452
+ # 1. Install & start Ollama
453
+ brew install ollama && ollama serve
454
+ ollama pull llama3.2:3b && ollama pull nomic-embed-text
455
+
456
+ # 2. Update .env
457
+ LLM_PROVIDER=ollama
458
+
459
+ # 3. Verify
460
+ threelane-memory config
461
+
462
+ # 4. Recreate the vector index (768-dim for nomic-embed-text)
463
+ # In Neo4j Browser:
464
+ # DROP INDEX episode_embedding;
465
+ # CREATE VECTOR INDEX episode_embedding IF NOT EXISTS
466
+ # FOR (ep:Episode) ON (ep.embedding)
467
+ # OPTIONS {indexConfig: {`vector.dimensions`: 768, `vector.similarity_function`: 'cosine'}};
468
+
469
+ # 5. Re-embed all episodes
470
+ threelane-memory reindex --old-model text-embedding-ada-002
471
+ ```
472
+
473
+ ### Using a Custom Model
474
+
475
+ Set these in `.env` to use any Ollama-compatible model:
476
+
477
+ ```env
478
+ OLLAMA_CHAT_MODEL=mistral # any chat model
479
+ OLLAMA_EMBED_MODEL=mxbai-embed-large # any embedding model
480
+ EMBEDDING_DIM=1024 # override auto-detection for unknown models
481
+ ```
482
+
483
+ ## Advanced Usage
484
+
485
+ Use the lower-level API for fine-grained control:
486
+
487
+ ```python
488
+ from threelane_memory.operator import operator_extract
489
+ from threelane_memory.reconciler import reconcile, consolidate
490
+ from threelane_memory.retriever import retrieve
491
+ from threelane_memory.backup import save_backup
492
+ from threelane_memory.entity_dedup import deduplicate_entities
493
+
494
+ # Extract semantics
495
+ semantics = operator_extract("Alice presented her paper at MIT")
496
+
497
+ # Write to graph
498
+ episode_id = reconcile(semantics, speaker="ankesh", raw_text="...")
499
+
500
+ # Retrieve context
501
+ context = retrieve("What did Alice do?", speaker="ankesh")
502
+
503
+ # Admin operations
504
+ consolidate("ankesh")
505
+ save_backup(speaker="ankesh")
506
+ deduplicate_entities(dry_run=False)
507
+ ```
508
+
509
+ ## Project Structure
510
+
511
+ ```
512
+ threelane-memory/
513
+ ├── threelane_memory/ # Core package
514
+ │ ├── __init__.py # Public API (store, query, close)
515
+ │ ├── __main__.py # python -m threelane_memory
516
+ │ ├── cli.py # CLI entry point
517
+ │ ├── config.py # Configuration from .env
518
+ │ ├── schemas.py # TypedDict data contracts
519
+ │ ├── database.py # Neo4j driver wrapper
520
+ │ ├── embeddings.py # Embedding wrapper (Ollama / OpenAI)
521
+ │ ├── llm_interface.py # LLM chat wrapper (Ollama / OpenAI)
522
+ │ ├── operator.py # Semantic extraction via LLM
523
+ │ ├── reconciler.py # Graph writer + entity resolution
524
+ │ ├── retriever.py # 3-lane retrieval engine
525
+ │ ├── entity_dedup.py # Entity deduplication
526
+ │ ├── backup.py # Graph export to JSON
527
+ │ ├── chat.py # Interactive chat loop
528
+ │ └── utils.py # Shared helpers
529
+ ├── examples/ # Example scripts
530
+ │ ├── basic_usage.py # Store and query
531
+ │ ├── advanced_pipeline.py # Lower-level API usage
532
+ │ ├── streamlit_app.py # Optional Streamlit web UI
533
+ │ └── debug_retrieval.py # Diagnostic tool
534
+ ├── tests/ # Test suite
535
+ ├── docs/ # Documentation
536
+ │ └── flowcharts.md # Architecture flowcharts
537
+ ├── pyproject.toml # Package metadata and dependencies
538
+ ├── .env.example # Environment variable template
539
+ ├── LICENSE # MIT License
540
+ ├── CONTRIBUTING.md # Contribution guidelines
541
+ └── README.md
542
+ ```
543
+
544
+ ## Configuration Reference
545
+
546
+ | Variable | Default | Description |
547
+ |---|---|---|
548
+ | `LLM_PROVIDER` | `ollama` | LLM backend: `ollama` (local) or `openai` (cloud) |
549
+ | `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama server address |
550
+ | `OLLAMA_CHAT_MODEL` | `llama3.2:3b` | Ollama chat model |
551
+ | `OLLAMA_EMBED_MODEL` | `nomic-embed-text` | Ollama embedding model (768-dim) |
552
+ | `OPENAI_API_KEY` | — | OpenAI API key (required when provider=openai) |
553
+ | `OPENAI_CHAT_MODEL` | `gpt-4o` | OpenAI chat model |
554
+ | `OPENAI_EMBED_MODEL` | `text-embedding-ada-002` | OpenAI embedding model (1536-dim) |
555
+ | `EMBEDDING_DIM` | auto | Override auto-detected embedding dimension |
556
+ | `WEIGHT_SIMILARITY` | `0.65` | Vector similarity weight in scoring |
557
+ | `WEIGHT_IMPORTANCE` | `0.30` | Importance weight in scoring |
558
+ | `WEIGHT_RECENCY` | `0.05` | Recency weight (low for 70-year safety) |
559
+ | `RECENCY_HALF_LIFE_DAYS` | `365` | Days for recency to decay by 50% |
560
+ | `IMPORTANCE_FLOOR` | `0.75` | Episodes above this ignore recency decay |
561
+ | `VECTOR_CANDIDATES_MIN` | `50` | Minimum ANN candidates |
562
+ | `VECTOR_CANDIDATES_MAX` | `500` | Maximum ANN candidates |
563
+ | `VECTOR_CANDIDATES_RATIO` | `0.02` | Fraction of total episodes to search |
564
+ | `CONSOLIDATION_AGE_DAYS` | `90` | Episodes older than this are eligible |
565
+ | `CONSOLIDATION_BATCH_SIZE` | `50` | Max episodes per consolidation round |
566
+ | `CONSOLIDATION_IMPORTANCE_CAP` | `0.3` | Only consolidate below this importance |
567
+
568
+ ## 70-Year Design Decisions
569
+
570
+ | Problem | Solution |
571
+ |---|---|
572
+ | Recency bias buries old memories | Recency weight = 5%, half-life = 365 days |
573
+ | Important memories forgotten | Importance floor bypass (≥ 0.75 → recency = 1.0) |
574
+ | Fixed candidate pool too small | Dynamic pool: 2% of graph, clamped 50–500 |
575
+ | Graph grows unbounded | Consolidation engine merges old trivia |
576
+ | State contradictions | SUPERSEDES chain — only latest value active |
577
+ | Entity fragmentation | 3-layer entity resolution + background dedup |
578
+ | Embedding model changes | Model version tag + batch reindex utility |
579
+ | Vector index lag | 5-minute recent-episode safety net |
580
+ | No disaster recovery | Full JSON export with incremental backup |
581
+
582
+ ## Development
583
+
584
+ ```bash
585
+ # Install with dev dependencies
586
+ pip install -e ".[dev]"
587
+
588
+ # Run tests
589
+ pytest tests/
590
+
591
+ # Lint
592
+ ruff check .
593
+
594
+ # Type check
595
+ mypy threelane_memory/
596
+ ```
597
+
598
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines.
599
+
600
+ ## Tech Stack
601
+
602
+ - **Python 3.10+**
603
+ - **Neo4j 5.x** — Graph database with vector index
604
+ - **Ollama** (default) — Local LLM & embeddings (llama3.2, nomic-embed-text)
605
+ - **OpenAI** (optional) — GPT-4o (chat) + text-embedding-ada-002 (embeddings)
606
+ - **LangChain** — Unified LLM/embedding wrappers
607
+ - **NumPy** — Cosine similarity computation
608
+
609
+ ## License
610
+
611
+ MIT — see [LICENSE](LICENSE).