dory-memory 0.3.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 (49) hide show
  1. dory_memory-0.3.0/LICENSE +170 -0
  2. dory_memory-0.3.0/PKG-INFO +443 -0
  3. dory_memory-0.3.0/README.md +396 -0
  4. dory_memory-0.3.0/dory/__init__.py +13 -0
  5. dory_memory-0.3.0/dory/activation.py +213 -0
  6. dory_memory-0.3.0/dory/adapters/__init__.py +7 -0
  7. dory_memory-0.3.0/dory/adapters/langchain.py +156 -0
  8. dory_memory-0.3.0/dory/adapters/langgraph.py +174 -0
  9. dory_memory-0.3.0/dory/adapters/multi_agent.py +171 -0
  10. dory_memory-0.3.0/dory/consolidation.py +98 -0
  11. dory_memory-0.3.0/dory/export/__init__.py +5 -0
  12. dory_memory-0.3.0/dory/export/jsonld.py +244 -0
  13. dory_memory-0.3.0/dory/graph.py +177 -0
  14. dory_memory-0.3.0/dory/mcp_server.py +154 -0
  15. dory_memory-0.3.0/dory/memory.py +223 -0
  16. dory_memory-0.3.0/dory/pipeline/__init__.py +7 -0
  17. dory_memory-0.3.0/dory/pipeline/decayer.py +187 -0
  18. dory_memory-0.3.0/dory/pipeline/observer.py +375 -0
  19. dory_memory-0.3.0/dory/pipeline/prefixer.py +377 -0
  20. dory_memory-0.3.0/dory/pipeline/reflector.py +440 -0
  21. dory_memory-0.3.0/dory/pipeline/summarizer.py +549 -0
  22. dory_memory-0.3.0/dory/schema.py +143 -0
  23. dory_memory-0.3.0/dory/session.py +403 -0
  24. dory_memory-0.3.0/dory/store.py +248 -0
  25. dory_memory-0.3.0/dory/vector.py +112 -0
  26. dory_memory-0.3.0/dory/visualize.py +1005 -0
  27. dory_memory-0.3.0/dory_cli.py +187 -0
  28. dory_memory-0.3.0/dory_mcp.py +60 -0
  29. dory_memory-0.3.0/dory_memory.egg-info/PKG-INFO +443 -0
  30. dory_memory-0.3.0/dory_memory.egg-info/SOURCES.txt +47 -0
  31. dory_memory-0.3.0/dory_memory.egg-info/dependency_links.txt +1 -0
  32. dory_memory-0.3.0/dory_memory.egg-info/entry_points.txt +3 -0
  33. dory_memory-0.3.0/dory_memory.egg-info/requires.txt +34 -0
  34. dory_memory-0.3.0/dory_memory.egg-info/top_level.txt +3 -0
  35. dory_memory-0.3.0/pyproject.toml +51 -0
  36. dory_memory-0.3.0/setup.cfg +4 -0
  37. dory_memory-0.3.0/tests/test_activation.py +162 -0
  38. dory_memory-0.3.0/tests/test_consolidation.py +224 -0
  39. dory_memory-0.3.0/tests/test_decayer.py +149 -0
  40. dory_memory-0.3.0/tests/test_graph.py +230 -0
  41. dory_memory-0.3.0/tests/test_integration_live.py +165 -0
  42. dory_memory-0.3.0/tests/test_mcp.py +91 -0
  43. dory_memory-0.3.0/tests/test_observer.py +237 -0
  44. dory_memory-0.3.0/tests/test_prefixer.py +181 -0
  45. dory_memory-0.3.0/tests/test_reflector.py +208 -0
  46. dory_memory-0.3.0/tests/test_schema.py +169 -0
  47. dory_memory-0.3.0/tests/test_session.py +72 -0
  48. dory_memory-0.3.0/tests/test_store.py +176 -0
  49. dory_memory-0.3.0/tests/test_visualize.py +83 -0
@@ -0,0 +1,170 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other transformations
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean, as submitted to the Licensor for inclusion
48
+ in the Work by the copyright owner or by an individual or Legal Entity
49
+ authorized to submit on behalf of the copyright owner. For the purposes
50
+ of this definition, "submitted" means any form of electronic, verbal,
51
+ or written communication sent to the Licensor or its representatives,
52
+ including but not limited to communication on electronic mailing lists,
53
+ source code control systems, and issue tracking systems that are managed
54
+ by, or on behalf of, the Licensor for the purpose of developing and
55
+ improving the Work, but excluding communication that is conspicuously
56
+ marked or designated in writing by the copyright owner as "Not a
57
+ Contribution."
58
+
59
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
60
+ whom a Contribution has been received by the Licensor and included
61
+ within the Work.
62
+
63
+ 2. Grant of Copyright License. Subject to the terms and conditions of
64
+ this License, each Contributor hereby grants to You a perpetual,
65
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66
+ copyright license to reproduce, prepare Derivative Works of,
67
+ publicly display, publicly perform, sublicense, and distribute the
68
+ Work and such Derivative Works in Source or Object form.
69
+
70
+ 3. Grant of Patent License. Subject to the terms and conditions of
71
+ this License, each Contributor hereby grants to You a perpetual,
72
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
73
+ (except as stated in this section) patent license to make, have made,
74
+ use, offer to sell, sell, import, and otherwise transfer the Work,
75
+ where such license applies only to those patent claims licensable
76
+ by such Contributor that are necessarily infringed by their
77
+ Contribution(s) alone or by the combination of their Contribution(s)
78
+ with the Work to which such Contribution(s) was submitted. If You
79
+ institute patent litigation against any entity (including a cross-claim
80
+ or counterclaim in a lawsuit) alleging that the Work or any other
81
+ Contribution embodied in the Work constitutes patent or contributory
82
+ patent infringement, then any patent licenses granted to You under
83
+ this License for that Work shall terminate as of the date such
84
+ litigation is filed.
85
+
86
+ 4. Redistribution. You may reproduce and distribute copies of the
87
+ Work or Derivative Works thereof in any medium, with or without
88
+ modifications, and in Source or Object form, provided that You
89
+ meet the following conditions:
90
+
91
+ (a) You must give any other recipients of the Work or Derivative
92
+ Works a copy of this License; and
93
+
94
+ (b) You must cause any modified files to carry prominent notices
95
+ stating that You changed the files; and
96
+
97
+ (c) You must retain, in the Source form of any Derivative Works
98
+ that You distribute, all copyright, patent, trademark, and
99
+ attribution notices from the Source form of the Work,
100
+ excluding those notices that do not pertain to any part of
101
+ the Derivative Works; and
102
+
103
+ (d) If the Work includes a "NOTICE" text file, as part of its
104
+ distribution, You must include a readable copy of the
105
+ attribution notices contained within such NOTICE file, in
106
+ at least one of the following places: within a NOTICE text
107
+ file distributed as part of the Derivative Works; within
108
+ the Source form or documentation, if provided along with the
109
+ Derivative Works; or, within a display generated by the
110
+ Derivative Works, if and wherever such third-party notices
111
+ normally appear. The contents of the NOTICE file are for
112
+ informational purposes only and do not modify the License.
113
+ You may add Your own attribution notices within Derivative
114
+ Works that You distribute, alongside or in addition to the
115
+ NOTICE text from the Work, provided that such additional
116
+ attribution notices cannot be construed as modifying the License.
117
+
118
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
119
+ any Contribution intentionally submitted for inclusion in the Work
120
+ by You to the Licensor shall be under the terms and conditions of
121
+ this License, without any additional terms or conditions.
122
+
123
+ 6. Trademarks. This License does not grant permission to use the trade
124
+ names, trademarks, service marks, or product names of the Licensor,
125
+ except as required for reasonable and customary use in describing the
126
+ origin of the Work and reproducing the content of the NOTICE file.
127
+
128
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
129
+ to in writing, Licensor provides the Work (and each Contributor
130
+ provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES
131
+ OR CONDITIONS OF ANY KIND, either express or implied, including,
132
+ without limitation, any warranties or conditions of TITLE,
133
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
134
+ PURPOSE. You are solely responsible for determining the
135
+ appropriateness of using or reproducing the Work and assume any
136
+ risks associated with Your exercise of permissions under this License.
137
+
138
+ 8. Limitation of Liability. In no event and under no legal theory,
139
+ whether in tort (including negligence), contract, or otherwise,
140
+ unless required by applicable law (such as deliberate and grossly
141
+ negligent acts) or agreed to in writing, shall any Contributor be
142
+ liable to You for damages, including any direct, indirect, special,
143
+ incidental, or exemplary damages of any character arising as a
144
+ result of this License or out of the use or inability to use the
145
+ Work (even if such Contributor has been advised of the possibility
146
+ of such damages).
147
+
148
+ 9. Accepting Warranty or Liability. While redistributing the Work or
149
+ Derivative Works thereof, You may choose to offer, and charge a fee
150
+ for, acceptance of support, warranty, indemnity, or other liability
151
+ obligations and/or rights consistent by this License. However, in
152
+ accepting such obligations, You may offer only obligations consistent
153
+ with this License, and only if You can do so without additional
154
+ conditions that would restrict or modify the terms of this License.
155
+
156
+ END OF TERMS AND CONDITIONS
157
+
158
+ Copyright 2026 Michael Martin
159
+
160
+ Licensed under the Apache License, Version 2.0 (the "License");
161
+ you may not use this file except in compliance with the License.
162
+ You may obtain a copy of the License at
163
+
164
+ http://www.apache.org/licenses/LICENSE-2.0
165
+
166
+ Unless required by applicable law or agreed to in writing, software
167
+ distributed under the License is distributed on an "AS IS" BASIS,
168
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
169
+ See the License for the specific language governing permissions and
170
+ limitations under the License.
@@ -0,0 +1,443 @@
1
+ Metadata-Version: 2.4
2
+ Name: dory-memory
3
+ Version: 0.3.0
4
+ Summary: Agent memory that actually sticks. Local-first, graph-based, with principled forgetting.
5
+ Author: Michael Martin
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/MichaelWMartinII/Dory
8
+ Project-URL: Repository, https://github.com/MichaelWMartinII/Dory
9
+ Project-URL: Issues, https://github.com/MichaelWMartinII/Dory/issues
10
+ Keywords: agent,memory,llm,ai,graph,local,rag
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: ollama
22
+ Requires-Dist: ollama>=0.6.0; extra == "ollama"
23
+ Provides-Extra: vector
24
+ Requires-Dist: sqlite-vec>=0.1.6; extra == "vector"
25
+ Provides-Extra: anthropic
26
+ Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
27
+ Provides-Extra: openai
28
+ Requires-Dist: httpx>=0.27.0; extra == "openai"
29
+ Provides-Extra: mcp
30
+ Requires-Dist: mcp>=1.0.0; extra == "mcp"
31
+ Provides-Extra: langchain
32
+ Requires-Dist: langchain>=0.2.0; extra == "langchain"
33
+ Provides-Extra: langgraph
34
+ Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
35
+ Provides-Extra: full
36
+ Requires-Dist: ollama>=0.6.0; extra == "full"
37
+ Requires-Dist: sqlite-vec>=0.1.6; extra == "full"
38
+ Requires-Dist: anthropic>=0.40.0; extra == "full"
39
+ Requires-Dist: httpx>=0.27.0; extra == "full"
40
+ Requires-Dist: mcp>=1.0.0; extra == "full"
41
+ Requires-Dist: langchain>=0.2.0; extra == "full"
42
+ Requires-Dist: langgraph>=0.2.0; extra == "full"
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8.0; extra == "dev"
45
+ Requires-Dist: pytest-cov; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # Dory
49
+
50
+ **Agent memory that actually sticks.**
51
+
52
+ Named after the fish with no short-term memory — because that's your AI agent right now.
53
+
54
+ Dory is the best Python-native, local-first agent memory library. Drop it into any LLM pipeline and your agent stops forgetting between sessions.
55
+
56
+ ```python
57
+ pip install dory-memory
58
+ ```
59
+
60
+ ---
61
+
62
+ ## The problem
63
+
64
+ Every time you start a new session, your agent starts from zero. Even systems that claim to "remember" you are doing keyword search through a flat list of notes. That's not memory — that's ctrl+F.
65
+
66
+ The deeper problem: naive memory injection makes things *worse*. Dumping everything into context creates noise that degrades model performance. Research ([Chroma, 2025](https://research.trychroma.com/context-rot)) shows all major frontier models degrade starting at 500–750 tokens of context.
67
+
68
+ ## What Dory does differently
69
+
70
+ **Four memory types, all in one place**
71
+
72
+ | Type | What it stores | Status |
73
+ |---|---|---|
74
+ | Episodic | Past events, sessions, experiences | ✓ |
75
+ | Semantic | Facts, preferences, entities, relationships | ✓ |
76
+ | Procedural | Skills, workflows, repeatable processes | ✓ |
77
+ | Working | In-context window (managed by your LLM) | — |
78
+
79
+ **Spreading activation retrieval** — not vector similarity search. Relevant memories pull in connected memories through the graph. "AllergyFind" activates "Giovanni's" activates "FastAPI" activates "menu endpoint" because those things co-occurred. That's how human memory works.
80
+
81
+ **Cacheable prefix output** — instead of regenerating your full memory context every turn (which blows prompt caching), Dory splits output into a *stable prefix* (same until memory actually changes) and a *dynamic suffix* (query-specific). Result: cache hits every turn. 4–10x cheaper to run agents with memory than without.
82
+
83
+ **Principled forgetting** — three decay zones: active, archived, expired. Scores based on recency + frequency + relevance. Nothing is ever deleted — archived memories are queryable for historical context. No other production memory library ships this.
84
+
85
+ **Bi-temporal conflict resolution** — when a fact changes, the old version is archived with a `SUPERSEDES` edge and a timestamp. You can query "what was true in January" and get the right answer.
86
+
87
+ **Zero-server stack** — everything runs in a single SQLite file. `sqlite-vec` for vectors, FTS5 for keyword search, adjacency tables for the graph. No Postgres, no Neo4j, no Redis. Works offline.
88
+
89
+ ---
90
+
91
+ ## Quick start
92
+
93
+ ```python
94
+ from dory import DoryMemory
95
+
96
+ # Works with any model — local or cloud
97
+ mem = DoryMemory() # manual observations only
98
+ mem = DoryMemory(extract_model="qwen3:14b") # local via Ollama
99
+ mem = DoryMemory( # Claude
100
+ extract_model="claude-haiku-4-5-20251001",
101
+ extract_backend="anthropic",
102
+ extract_api_key="sk-ant-...",
103
+ )
104
+ mem = DoryMemory( # GPT / Grok / any compat
105
+ extract_model="gpt-4o-mini",
106
+ extract_backend="openai",
107
+ extract_api_key="sk-...",
108
+ )
109
+
110
+ # --- Query context at session start ---
111
+ context = mem.query("menu endpoint authentication") # inject into system prompt
112
+
113
+ # --- Or build API-ready messages with prompt caching ---
114
+ result = mem.build_context("menu endpoint authentication")
115
+ messages = result.as_anthropic_messages(user_query) # Anthropic SDK w/ cache_control
116
+ messages = result.as_openai_messages(user_query) # OpenAI / compat
117
+
118
+ # --- Log turns during the session ---
119
+ mem.add_turn("user", "I'm working on AllergyFind today, need to add a menu endpoint")
120
+ mem.add_turn("assistant", "What authentication approach are you using?")
121
+
122
+ # --- Or add memories manually ---
123
+ mem.observe("User prefers JWT for API auth", node_type="PREFERENCE")
124
+
125
+ # --- End of session: extract, consolidate, decay ---
126
+ stats = mem.flush()
127
+ ```
128
+
129
+ ### MCP server (Claude Code / Claude Desktop)
130
+
131
+ ```bash
132
+ pip install 'dory-memory[mcp]'
133
+
134
+ # Register globally across all Claude Code projects
135
+ claude mcp add --scope user dory -- dory-mcp
136
+
137
+ # Or with a specific DB path
138
+ claude mcp add --scope user dory -- dory-mcp --db /path/to/engram.db
139
+ ```
140
+
141
+ Five tools are exposed: `dory_query`, `dory_observe`, `dory_consolidate`, `dory_visualize`, `dory_stats`.
142
+
143
+ ---
144
+
145
+ ## Interactive demo
146
+
147
+ **[Live graph visualization →](https://michaelwmartinii.github.io/Dory/demo.html)**
148
+
149
+ Force-directed knowledge graph with spreading activation query mode, edge type coloring, archived/superseded nodes, and session summary chain. Click any of the pre-set queries to see retrieval in action.
150
+
151
+ ---
152
+
153
+ ### Framework adapters
154
+
155
+ **LangChain** — drop-in `BaseMemory` replacement:
156
+
157
+ ```python
158
+ from dory.adapters.langchain import DoryMemoryAdapter
159
+ from langchain.chains import ConversationChain
160
+ from langchain_anthropic import ChatAnthropic
161
+
162
+ memory = DoryMemoryAdapter(
163
+ extract_model="claude-haiku-4-5-20251001",
164
+ extract_backend="anthropic",
165
+ extract_api_key="sk-ant-...",
166
+ )
167
+ chain = ConversationChain(llm=ChatAnthropic(model="claude-sonnet-4-6"), memory=memory)
168
+ ```
169
+
170
+ **LangGraph** — graph nodes with the `(state) -> state` signature:
171
+
172
+ ```python
173
+ from dory.adapters.langgraph import DoryMemoryNode, MemoryState
174
+ from langgraph.graph import StateGraph, START, END
175
+
176
+ mem = DoryMemoryNode(extract_model="claude-haiku-4-5-20251001", extract_backend="anthropic")
177
+
178
+ builder = StateGraph(MemoryState)
179
+ builder.add_node("load_memory", mem.load_context) # or mem.aload_context for async
180
+ builder.add_node("record_turn", mem.record_turn)
181
+ builder.add_edge(START, "load_memory")
182
+ builder.add_edge("load_memory", "record_turn")
183
+ builder.add_edge("record_turn", END)
184
+ graph = builder.compile()
185
+ ```
186
+
187
+ **Multi-agent** — shared memory pool with thread-safe writes and agent attribution:
188
+
189
+ ```python
190
+ from dory.adapters.multi_agent import SharedMemoryPool
191
+
192
+ pool = SharedMemoryPool(db_path="shared.db")
193
+ pool.observe("User prefers dark mode", agent_id="agent-1")
194
+ pool.add_turn("user", "Let's ship it", agent_id="agent-2", session_id="s1")
195
+ results = pool.query("UI preferences")
196
+ agent_nodes = pool.get_agent_nodes("agent-1")
197
+ ```
198
+
199
+ ### Async API
200
+
201
+ All `DoryMemory` methods have async counterparts — safe to await from FastAPI, LangGraph, and any async framework:
202
+
203
+ ```python
204
+ context = await mem.aquery("current topic")
205
+ result = await mem.abuild_context("current topic")
206
+ await mem.aadd_turn("user", "message")
207
+ node_id = await mem.aobserve("User prefers JWT", node_type="PREFERENCE")
208
+ stats = await mem.aflush()
209
+ ```
210
+
211
+ ### Export / import
212
+
213
+ ```python
214
+ from dory.export.jsonld import JSONLDExporter
215
+
216
+ exporter = JSONLDExporter(graph)
217
+ exporter.export("memory.jsonld.json") # write to file
218
+ data = exporter.export() # or get dict
219
+
220
+ JSONLDExporter.import_into(graph, "memory.jsonld.json") # round-trip import
221
+ ```
222
+
223
+ ### Advanced: direct pipeline access
224
+
225
+ ```python
226
+ from dory import Graph, Observer, Prefixer
227
+
228
+ graph = Graph("myapp.db")
229
+ obs = Observer(graph, backend="ollama", model="qwen3:14b")
230
+ p = Prefixer(graph)
231
+ # ... same as DoryMemory but with full control
232
+ ```
233
+
234
+ ---
235
+
236
+ ## How it works
237
+
238
+ ### Knowledge graph
239
+
240
+ Every piece of information is a node. Nodes have types: `ENTITY`, `CONCEPT`, `EVENT`, `PREFERENCE`, `BELIEF`, `PROCEDURE`, `SESSION` (episodic narrative), `SESSION_SUMMARY` (structured episodic with `salient_counts`). Edges between them are typed and weighted: `USES`, `WORKS_ON`, `PREFERS`, `SUPERSEDES`, `CO_OCCURS`, `SUPPORTS_FACT`, `TEMPORALLY_AFTER`, etc.
241
+
242
+ Salience is computed, not assigned:
243
+ ```
244
+ salience = α × connectivity + β × activation_frequency + γ × recency
245
+ ```
246
+
247
+ High-salience nodes become **core memories** — they anchor the stable context prefix.
248
+
249
+ ### Observer
250
+
251
+ Every N conversation turns (configurable), the Observer calls a local LLM to extract structured memories from the raw conversation. Extractions have confidence scores — anything below the threshold is logged but not written to the graph, guarding against false memory.
252
+
253
+ Backends: Ollama (default), Anthropic (Claude), or any OpenAI-compatible endpoint (llama.cpp, Clanker, vLLM, GPT, Grok, etc.).
254
+
255
+ ### Prefixer
256
+
257
+ Builds context in two parts:
258
+
259
+ ```
260
+ [stable prefix] ← core memories + key relationships
261
+ same bytes across turns → prompt cache hits
262
+
263
+ [dynamic suffix] ← spreading activation for this specific query
264
+ + recent episodic observations
265
+ changes per query but small
266
+ ```
267
+
268
+ ### Decayer
269
+
270
+ Runs periodically to score every node:
271
+ ```
272
+ score = recency_weight × exp(-λ × days_since_activation)
273
+ + frequency_weight × log(1 + activation_count)
274
+ + relevance_weight × salience
275
+ ```
276
+
277
+ Nodes below the active floor → archived. Below the archive floor → expired. Core memories are shielded with a configurable multiplier.
278
+
279
+ ### Reflector
280
+
281
+ Finds near-duplicate nodes (Jaccard similarity ≥ 0.82, empirically tuned), merges them keeping the higher-salience one. Detects supersession — same subject, newer fact, Jaccard in [0.45, 0.82) — archives the old node, and adds a `SUPERSEDES` provenance edge. Old observations are compressed into summaries. Dedup thresholds are practical defaults chosen conservatively; sensitivity analysis is planned.
282
+
283
+ ---
284
+
285
+ ## Architecture
286
+
287
+ ```
288
+ dory/
289
+ ├── graph.py ← nodes, edges, salience computation
290
+ ├── schema.py ← NodeType, EdgeType, zone constants
291
+ ├── activation.py ← spreading activation engine
292
+ ├── consolidation.py ← edge decay, strengthen, prune, promote/demote core
293
+ ├── session.py ← session-level helpers: query, observe, write_turn, end_session
294
+ ├── memory.py ← DoryMemory — the high-level drop-in API (sync + async)
295
+ ├── visualize.py ← D3.js interactive graph visualization
296
+ ├── mcp_server.py ← MCP tools (dory_query, dory_observe, dory_consolidate, …)
297
+ ├── store.py ← SQLite backend (nodes, edges, FTS5, observations)
298
+
299
+ ├── pipeline/
300
+ │ ├── observer.py ← LLM extraction of memories from conversation turns
301
+ │ ├── summarizer.py ← episodic layer: SESSION nodes from conversation turns
302
+ │ ├── prefixer.py ← stable prefix + dynamic suffix builder
303
+ │ ├── decayer.py ← node decay scoring + zone management
304
+ │ └── reflector.py ← dedup, supersession, observation compression
305
+
306
+ ├── adapters/
307
+ │ ├── langchain.py ← DoryMemoryAdapter — LangChain BaseMemory drop-in
308
+ │ ├── langgraph.py ← DoryMemoryNode — LangGraph StateGraph nodes
309
+ │ └── multi_agent.py ← SharedMemoryPool — thread-safe multi-agent memory
310
+
311
+ └── export/
312
+ └── jsonld.py ← JSONLDExporter — portable JSON-LD round-trip
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Local LLM setup
318
+
319
+ Dory defaults to Ollama for LLM-based extraction (Observer) and embedding (vector search).
320
+
321
+ ```bash
322
+ # Pull the default models
323
+ ollama pull qwen3:14b # extraction
324
+ ollama pull nomic-embed-text # embeddings (768-dim, offline after pull)
325
+ ```
326
+
327
+ OpenAI-compatible endpoint (Clanker, llama.cpp server, vLLM):
328
+ ```python
329
+ obs = Observer(
330
+ graph,
331
+ backend="openai",
332
+ base_url="http://localhost:8000",
333
+ model="qwen3",
334
+ )
335
+ ```
336
+
337
+ Vector search activates automatically once `nomic-embed-text` is available. Falls back to FTS5 BM25 + substring search if no embedding model is running.
338
+
339
+ ---
340
+
341
+ ## Decay zones
342
+
343
+ | Zone | Behavior | How to query |
344
+ |---|---|---|
345
+ | `active` | Retrieved in all normal queries | `graph.all_nodes()` (default) |
346
+ | `archived` | Invisible to normal queries | `graph.all_nodes(zone="archived")` |
347
+ | `expired` | Completely invisible | `graph.all_nodes(zone=None)` |
348
+
349
+ User-meaningful memory is never deleted by forgetting — archived and expired nodes retain full provenance and can be restored if reactivated. The one exception: exact structural duplicates detected by the Reflector are hard-merged (the lower-salience copy is removed, all its edges are rewired to the winner).
350
+
351
+ ---
352
+
353
+ ## What's different from other memory libraries
354
+
355
+ | | mem0 | Zep | Letta | Mastra | **Dory** |
356
+ |---|---|---|---|---|---|
357
+ | Principled forgetting | ✗ | ✗ | ✗ | ✗ | ✓ |
358
+ | Spreading activation retrieval | ✗ | ✗ | ✗ | ✗ | ✓ |
359
+ | Cacheable prefix output | ✗ | ✗ | ✗ | ✓ (TS only) | ✓ |
360
+ | Bi-temporal conflict resolution | ✗ | ✓ | ✗ | ✗ | ✓ |
361
+ | Zero-server local stack | partial | ✗ | partial | ✗ | ✓ |
362
+ | Drop-in Python library | ✓ | partial | ✗ | ✗ | ✓ |
363
+ | Apache 2.0 | ✓ | ✓ | ✓ | ✓ | ✓ |
364
+
365
+ ---
366
+
367
+ ## Roadmap
368
+
369
+ **Shipped (v0.1)**
370
+ - [x] MCP server — expose Dory memory as MCP tools for Claude Code / Claude Desktop
371
+ - [x] LangChain adapter — `dory.adapters.langchain.DoryMemoryAdapter` implements `BaseMemory`
372
+ - [x] LangGraph adapter — `dory.adapters.langgraph.DoryMemoryNode` for StateGraph integration
373
+ - [x] Procedural memory — `PROCEDURE` node type for skills, workflows, and repeatable processes
374
+ - [x] Multi-agent shared memory — `dory.adapters.multi_agent.SharedMemoryPool` with thread-safe writes and agent attribution
375
+ - [x] Portable import/export format — `dory.export.jsonld.JSONLDExporter` for JSON-LD round-trips
376
+
377
+ **Shipped (v0.2)**
378
+ - [x] Episodic layer — `SESSION_SUMMARY` nodes with structured `salient_counts` metadata
379
+ - [x] Retrieval fusion — three-mode routing (graph / episodic / hybrid) via deterministic regex, no extra LLM calls
380
+ - [x] Staged retrieval — spreading activation → SUPPORTS_FACT traversal → SESSION_SUMMARY injection
381
+ - [x] Behavioral preference synthesis — `Reflector` detects repeated behavioral patterns across sessions and synthesizes PREFERENCE nodes without LLM calls
382
+
383
+ **Shipped (v0.3)**
384
+ - [x] Full 500-question LongMemEval run — 79.8% Sonnet/Sonnet (+13.0pp over v0.1)
385
+ - [x] Temporal arithmetic prompt — step-by-step date math before answering
386
+ - [x] Count cross-validation — `salient_counts` verified against EVENT nodes, low-confidence flagged
387
+ - [x] Behavioral preference synthesis — `Reflector` synthesizes PREFERENCE nodes from repeated patterns
388
+
389
+ **In progress (v0.4)**
390
+ - [ ] Preference inference — targeted improvement on single-session-preference (currently 46.7%)
391
+ - [ ] Graph topology demo — `demo_topology.py` showing provenance / evolution queries flat systems can't answer
392
+ - [ ] S-split benchmark — longer sessions (~115K tokens), better test of spreading activation value
393
+ - [ ] Production hardening — concurrent write safety, adversarial memory injection defense
394
+
395
+ ---
396
+
397
+ ## Research basis
398
+
399
+ Dory draws from:
400
+ - [MemGPT: Towards LLMs as Operating Systems](https://arxiv.org/abs/2310.08560) — two-tier memory architecture
401
+ - [Zep: A Temporal Knowledge Graph Architecture](https://arxiv.org/abs/2501.13956) — bi-temporal provenance
402
+ - [MAGMA: Multi-Graph based Agentic Memory](https://arxiv.org/abs/2601.03236) — multi-graph retrieval
403
+ - [Mastra Observational Memory](https://mastra.ai/research/observational-memory) — cacheable prefix architecture (Python port)
404
+ - [LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025) — the benchmark we care about. Published scores: Mem0 68.4%, Zep 71.2%, Mastra 94.87%¹.
405
+
406
+ | Version | Extract | Answer | Questions | Score | Notes |
407
+ |---|---|---|---|---|---|
408
+ | v0.1 | Haiku | Haiku | 500 (full) | 54.4% | Baseline |
409
+ | v0.1 | Sonnet | Sonnet | 500 (full) | 66.8% | |
410
+ | v0.3 | Haiku | Haiku | 40 (spot check) | 67.5% | Episodic hybrid, spot check |
411
+ | **v0.3** | **Sonnet** | **Sonnet** | **500 (full)** | **79.8%** | **Episodic hybrid, full run** |
412
+
413
+ Category breakdown (v0.3 Sonnet, 500q):
414
+
415
+ | Category | v0.1 Sonnet | v0.3 Sonnet | Δ |
416
+ |---|---|---|---|
417
+ | temporal-reasoning | 46.6% | 75.9% | +29.3pp |
418
+ | knowledge-update | 75.6% | 84.6% | +9.0pp |
419
+ | multi-session | 70.7% | 80.5% | +9.8pp |
420
+ | single-session-assistant | 82.1% | 87.5% | +5.4pp |
421
+ | single-session-user | 85.7% | 88.6% | +2.9pp |
422
+ | single-session-preference | 43.3% | 46.7% | +3.3pp |
423
+ | **Overall** | **66.8%** | **79.8%** | **+13.0pp** |
424
+
425
+ ¹ Mastra uses GPT-4o-mini (TypeScript). Dory uses Claude on Python. Architecturally
426
+ different stacks — not directly comparable. See [ablation study](benchmarks/ABLATION.md)
427
+ for component attribution.
428
+
429
+ **Disclaimer:** LongMemEval oracle split uses pre-filtered context (~15K tokens per question).
430
+ Production performance with live, noisy, unfiltered conversations will differ.
431
+ - Collins & Loftus (1975) — spreading activation in semantic memory
432
+ - Hebb (1949) — neurons that fire together wire together
433
+ - [Hopfield (1982) — Neural networks and physical systems with emergent collective computational abilities](https://www.pnas.org/doi/10.1073/pnas.79.8.2554) — statistical mechanics of associative memory; energy landscape formulation underlying spreading activation (Nobel Prize in Physics, 2024)
434
+
435
+ ---
436
+
437
+ ## License
438
+
439
+ Apache 2.0 — see [LICENSE](LICENSE).
440
+
441
+ ---
442
+
443
+ *Named after Dory from Finding Nemo, because your AI agent right now is Dory. This fixes it.*