neatmem 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 (90) hide show
  1. neatmem-0.1.0/LICENSE +21 -0
  2. neatmem-0.1.0/PKG-INFO +513 -0
  3. neatmem-0.1.0/README.md +490 -0
  4. neatmem-0.1.0/neatmem/__init__.py +5 -0
  5. neatmem-0.1.0/neatmem/cli.py +162 -0
  6. neatmem-0.1.0/neatmem/client.py +349 -0
  7. neatmem-0.1.0/neatmem/config.py +197 -0
  8. neatmem-0.1.0/neatmem/embeddings.py +139 -0
  9. neatmem-0.1.0/neatmem/evaluation/__init__.py +0 -0
  10. neatmem-0.1.0/neatmem/evaluation/dataset/locomo10.json +66751 -0
  11. neatmem-0.1.0/neatmem/evaluation/metrics/__init__.py +0 -0
  12. neatmem-0.1.0/neatmem/evaluation/metrics/llm_judge.py +321 -0
  13. neatmem-0.1.0/neatmem/evaluation/metrics/utils.py +211 -0
  14. neatmem-0.1.0/neatmem/evaluation/prompts.py +148 -0
  15. neatmem-0.1.0/neatmem/evaluation/run_experiments.py +38 -0
  16. neatmem-0.1.0/neatmem/evaluation/src/__init__.py +0 -0
  17. neatmem-0.1.0/neatmem/evaluation/src/neatmem/__init__.py +0 -0
  18. neatmem-0.1.0/neatmem/evaluation/src/neatmem/add.py +145 -0
  19. neatmem-0.1.0/neatmem/evaluation/src/neatmem/client.py +51 -0
  20. neatmem-0.1.0/neatmem/evaluation/src/neatmem/ingest_locomo.py +189 -0
  21. neatmem-0.1.0/neatmem/evaluation/src/neatmem/search.py +213 -0
  22. neatmem-0.1.0/neatmem/exceptions.py +9 -0
  23. neatmem-0.1.0/neatmem/main.py +693 -0
  24. neatmem-0.1.0/neatmem/memory_add.py +1096 -0
  25. neatmem-0.1.0/neatmem/memory_search.py +183 -0
  26. neatmem-0.1.0/neatmem/memory_store.py +806 -0
  27. neatmem-0.1.0/neatmem/message_store.py +32 -0
  28. neatmem-0.1.0/neatmem/prompts/__init__.py +0 -0
  29. neatmem-0.1.0/neatmem/prompts/examples/dedup_en.example.txt +25 -0
  30. neatmem-0.1.0/neatmem/prompts/examples/dedup_zh.example.txt +25 -0
  31. neatmem-0.1.0/neatmem/prompts/examples/edit_en.example.txt +24 -0
  32. neatmem-0.1.0/neatmem/prompts/examples/extraction_en.example.txt +444 -0
  33. neatmem-0.1.0/neatmem/prompts/examples/rerank_en.example.txt +16 -0
  34. neatmem-0.1.0/neatmem/prompts/examples/rewrite_zh.example.txt +15 -0
  35. neatmem-0.1.0/neatmem/prompts/extraction.py +593 -0
  36. neatmem-0.1.0/neatmem/prompts/loader.py +84 -0
  37. neatmem-0.1.0/neatmem/rerank.py +214 -0
  38. neatmem-0.1.0/neatmem/signals/__init__.py +0 -0
  39. neatmem-0.1.0/neatmem/signals/bm25/__init__.py +19 -0
  40. neatmem-0.1.0/neatmem/signals/bm25/base.py +40 -0
  41. neatmem-0.1.0/neatmem/signals/bm25/factory.py +43 -0
  42. neatmem-0.1.0/neatmem/signals/bm25/noop.py +28 -0
  43. neatmem-0.1.0/neatmem/signals/bm25/qdrant_sparse.py +113 -0
  44. neatmem-0.1.0/neatmem/signals/bm25/scoring.py +43 -0
  45. neatmem-0.1.0/neatmem/signals/entity/__init__.py +15 -0
  46. neatmem-0.1.0/neatmem/signals/entity/base.py +23 -0
  47. neatmem-0.1.0/neatmem/signals/entity/boosting.py +178 -0
  48. neatmem-0.1.0/neatmem/signals/entity/extractor_llm.py +11 -0
  49. neatmem-0.1.0/neatmem/signals/entity/extractor_ner.py +15 -0
  50. neatmem-0.1.0/neatmem/signals/entity/factory.py +14 -0
  51. neatmem-0.1.0/neatmem/signals/graph/__init__.py +0 -0
  52. neatmem-0.1.0/neatmem/signals/graph/adapter.py +135 -0
  53. neatmem-0.1.0/neatmem/signals/graph/factory.py +77 -0
  54. neatmem-0.1.0/neatmem/signals/graph/prompts.py +138 -0
  55. neatmem-0.1.0/neatmem/signals/graph/tools.py +211 -0
  56. neatmem-0.1.0/neatmem/storage/__init__.py +0 -0
  57. neatmem-0.1.0/neatmem/storage/entity/__init__.py +10 -0
  58. neatmem-0.1.0/neatmem/storage/entity/base.py +39 -0
  59. neatmem-0.1.0/neatmem/storage/entity/factory.py +22 -0
  60. neatmem-0.1.0/neatmem/storage/entity/qdrant.py +244 -0
  61. neatmem-0.1.0/neatmem/storage/graph/__init__.py +0 -0
  62. neatmem-0.1.0/neatmem/storage/graph/kuzu_store.py +704 -0
  63. neatmem-0.1.0/neatmem/storage/history.py +361 -0
  64. neatmem-0.1.0/neatmem/storage/message/__init__.py +13 -0
  65. neatmem-0.1.0/neatmem/storage/message/base.py +143 -0
  66. neatmem-0.1.0/neatmem/storage/message/factory.py +55 -0
  67. neatmem-0.1.0/neatmem/storage/message/noop.py +88 -0
  68. neatmem-0.1.0/neatmem/storage/message/sqlite.py +418 -0
  69. neatmem-0.1.0/neatmem/storage/vector/__init__.py +7 -0
  70. neatmem-0.1.0/neatmem/storage/vector/base.py +65 -0
  71. neatmem-0.1.0/neatmem/storage/vector/factory.py +39 -0
  72. neatmem-0.1.0/neatmem/storage/vector/qdrant.py +493 -0
  73. neatmem-0.1.0/neatmem/utils/__init__.py +0 -0
  74. neatmem-0.1.0/neatmem/utils/llm_client.py +55 -0
  75. neatmem-0.1.0/neatmem/utils/spacy/__init__.py +10 -0
  76. neatmem-0.1.0/neatmem/utils/spacy/entity_extraction.py +367 -0
  77. neatmem-0.1.0/neatmem/utils/spacy/lemmatization.py +60 -0
  78. neatmem-0.1.0/neatmem/utils/spacy/spacy_models.py +101 -0
  79. neatmem-0.1.0/neatmem/utils/text_parsing.py +48 -0
  80. neatmem-0.1.0/neatmem.egg-info/PKG-INFO +513 -0
  81. neatmem-0.1.0/neatmem.egg-info/SOURCES.txt +88 -0
  82. neatmem-0.1.0/neatmem.egg-info/dependency_links.txt +1 -0
  83. neatmem-0.1.0/neatmem.egg-info/entry_points.txt +2 -0
  84. neatmem-0.1.0/neatmem.egg-info/requires.txt +16 -0
  85. neatmem-0.1.0/neatmem.egg-info/top_level.txt +1 -0
  86. neatmem-0.1.0/pyproject.toml +35 -0
  87. neatmem-0.1.0/setup.cfg +4 -0
  88. neatmem-0.1.0/tests/test_memory_store.py +163 -0
  89. neatmem-0.1.0/tests/test_prompt_examples.py +38 -0
  90. neatmem-0.1.0/tests/test_sqlite_message_store.py +528 -0
neatmem-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NeatMem 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.
neatmem-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,513 @@
1
+ Metadata-Version: 2.4
2
+ Name: neatmem
3
+ Version: 0.1.0
4
+ Summary: A local mem0-compatible memory server for AI agents
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: fastapi==0.135.2
9
+ Requires-Dist: uvicorn[standard]==0.42.0
10
+ Requires-Dist: pydantic==2.12.3
11
+ Requires-Dist: numpy>=1.26.4
12
+ Requires-Dist: python-dotenv==1.2.2
13
+ Requires-Dist: langchain-community==0.4.1
14
+ Requires-Dist: qdrant-client==1.17.1
15
+ Requires-Dist: fastembed==0.8.0
16
+ Requires-Dist: openai==2.32.0
17
+ Requires-Dist: httpx==0.28.1
18
+ Provides-Extra: local-reranker
19
+ Requires-Dist: sentence-transformers==4.1.0; extra == "local-reranker"
20
+ Provides-Extra: nlp
21
+ Requires-Dist: spacy>=3.7; extra == "nlp"
22
+ Dynamic: license-file
23
+
24
+ # NeatMem
25
+
26
+ Lightweight local memory for agents, with cleaner deduplication, less memory pollution, and more relevant recall.
27
+
28
+ NeatMem is built for developers who want practical long-term memory without adopting a full Memory OS or hosted memory service. It focuses on keeping local agent memory clean: merging repeated facts, preventing AI suggestions, guesses, and tool noise from being saved as user facts, saving memories with enough context, and filtering irrelevant recalls.
29
+
30
+ > Status: v0.1-preview. NeatMem is usable for local development and mem0-compatible integrations, but APIs, packaging, and integrations may still change.
31
+
32
+ > **Benchmark**: 90.80% accuracy on LOCOMO, fully reproducible locally (3-run mean; MiniMax-M3 answer + judge, SiliconFlow bge-m3 embedding). See the [evaluation guide](https://github.com/kanhaoning/NeatMem/blob/main/neatmem/evaluation/README.md) for benchmark reproduction steps.
33
+
34
+ ## Why NeatMem?
35
+
36
+ Agent memory is easy to start but hard to keep clean.
37
+
38
+ Common problems include:
39
+
40
+ - duplicate memories accumulating over time
41
+ - assistant suggestions being stored as user facts
42
+ - vague memories losing their original context
43
+ - semantically related memories not being merged
44
+ - irrelevant memories being recalled because of weak vector matches
45
+ - local agent tools needing a simple self-hosted memory backend
46
+
47
+ NeatMem focuses on one narrow goal:
48
+
49
+ > Local agent memory that stays clean, inspectable, and easy to tune.
50
+
51
+ It is not a full Memory OS and not an enterprise multi-tenant memory system.
52
+
53
+ ## Features
54
+
55
+ - **LLM-assisted memory decisions**
56
+ - Classifies each new memory as `add`, `none`, or `update` (listwise, single LLM call).
57
+ - `DEDUP_MODE` controls behavior: `skip` (keep both), `replace` (overwrite), `rewrite` (LLM merge), `edit` (LLM patch).
58
+
59
+ - **Sequential memory updates**
60
+ - Processes new memories one by one so each merge sees the latest stored version.
61
+ - Helps avoid overwrite conflicts when several new facts update the same old memory.
62
+
63
+ - **Less memory pollution**
64
+ - Avoids saving AI suggestions, guesses, or tool noise as user facts.
65
+ - Tracks whether each memory came from the user, assistant, or tool output.
66
+
67
+ - **Memories with enough context**
68
+ - Adds missing context from the same message batch when needed.
69
+ - Example: “during development” can become “while developing a mem0-based memory module”.
70
+
71
+ - **More relevant recall**
72
+ - Multi-signal retrieval: dense vector search + BM25 sparse matching + entity boosting.
73
+ - LLM listwise rerank filters and reorders candidates before injection into agent context.
74
+
75
+ - **Lightweight local storage**
76
+ - Runs with local Qdrant (embedded or server mode) by default.
77
+ - Does not require Redis, a hosted memory service, or a full database stack.
78
+
79
+ - **Modular signal architecture**
80
+ - Message store, BM25, and entity modules are decoupled under `neatmem/storage/` and `neatmem/signals/`.
81
+ - Each signal can be toggled via environment variables (`ENABLE_BM25`, `ENABLE_ENTITY`, `ENABLE_GRAPH`).
82
+
83
+ - **Optional graph memory (opt-in)**
84
+ - Entity-relation storage via KuzuDB, toggled by `ENABLE_GRAPH`.
85
+ - Off by default; graph relations injection into answer prompt is experimental (`GRAPH_INJECT_RELATIONS`, known harmful on LOCOMO).
86
+
87
+ - **OpenClaw and mem0-style integration**
88
+ - Implements the core mem0-style memory endpoints needed for local agent workflows.
89
+ - Designed to support OpenClaw platform-mode memory integration.
90
+
91
+ ## Compatibility
92
+
93
+ NeatMem implements a mem0-compatible API subset for local agent memory workflows:
94
+
95
+ - add memory
96
+ - search memory
97
+ - list memories
98
+ - update memory
99
+ - delete memory
100
+ - health check
101
+
102
+ It is designed to work with OpenClaw's and Hermes' memory plugin flows and other mem0-style integrations. v0.1 does not aim to cover every mem0 SDK feature or mem0 hosted-platform behavior.
103
+
104
+ A remote client is provided for programmatic access:
105
+
106
+ ```python
107
+ from neatmem import MemoryClient
108
+
109
+ client = MemoryClient(host="http://localhost:8790")
110
+ client.add("My name is Alex", user_id="default_user")
111
+ results = client.search("What is my name?", filters={"user_id": "default_user"})
112
+ ```
113
+
114
+
115
+ ## Quick start
116
+
117
+ ### 1. Install
118
+
119
+ ```bash
120
+ pip install -r requirements.txt
121
+ pip install -e .
122
+ ```
123
+
124
+ The second command registers the `neatmem` CLI.
125
+
126
+ Optional:
127
+ - BM25 keyword search (enabled by default) needs spaCy:
128
+ ```bash
129
+ pip install -e ".[nlp]" && python -m spacy download en_core_web_sm
130
+ ```
131
+ - Local reranker model (alternative to LLM rerank):
132
+ ```bash
133
+ pip install -e ".[local-reranker]"
134
+ ```
135
+
136
+ ### 2. Configure environment variables
137
+
138
+ ```bash
139
+ cp .env.example .env
140
+ ```
141
+
142
+ Edit `.env` and configure your LLM and embedding provider.
143
+
144
+ Minimum configuration for OpenAI-compatible LLM providers:
145
+
146
+ ```env
147
+ OPENAI_API_KEY=your-api-key
148
+ OPENAI_BASE_URL=https://your-openai-compatible-endpoint/v1
149
+ LLM_MODEL=qwen-max-latest
150
+
151
+ EMBEDDING_PROVIDER=siliconflow
152
+ SILICONFLOW_API_KEY=your-siliconflow-api-key
153
+ ```
154
+
155
+ ### 3. Start the server
156
+
157
+ ```bash
158
+ neatmem serve
159
+ ```
160
+
161
+ The server listens on:
162
+
163
+ ```text
164
+ http://localhost:8790
165
+ ```
166
+
167
+ To use a different port:
168
+
169
+ ```bash
170
+ neatmem serve --port 9000
171
+ ```
172
+
173
+ View all options:
174
+
175
+ ```bash
176
+ neatmem serve --help
177
+ ```
178
+
179
+ CLI flags override `.env` environment variables; see the Configuration table below for the full list.
180
+
181
+ Alternatively, start directly with Python:
182
+
183
+ ```bash
184
+ python -m neatmem.main
185
+ ```
186
+
187
+ Check health:
188
+
189
+ ```bash
190
+ curl http://localhost:8790/health
191
+ ```
192
+
193
+ Expected response:
194
+
195
+ ```json
196
+ {"status":"healthy","timestamp":"..."}
197
+ ```
198
+
199
+ ## Configuration
200
+
201
+ NeatMem reads configuration from `.env`.
202
+
203
+ | Variable | Required | Default | Description |
204
+ |---|---:|---|---|
205
+ | `NEATMEM_HOST` | no | `0.0.0.0` | Server bind host |
206
+ | `NEATMEM_PORT` | no | `8790` | Server port |
207
+ | `NEATMEM_URL` | no | `http://localhost:8790` | Base URL used by `MemoryClient` |
208
+ | `NEATMEM_API_KEY` | no | - | API key sent as `Authorization: Token` header by `MemoryClient` (server ignores it) |
209
+ | `OPENAI_API_KEY` | yes | - | API key for OpenAI-compatible LLM provider |
210
+ | `OPENAI_BASE_URL` | yes | - | OpenAI-compatible API base URL |
211
+ | `LLM_MODEL` | no | `qwen-max-latest` | LLM model name |
212
+ | `EMBEDDING_PROVIDER` | no | `siliconflow` | `siliconflow` or `xinference` |
213
+ | `SILICONFLOW_API_KEY` | conditional | - | Required when `EMBEDDING_PROVIDER=siliconflow` |
214
+ | `EMBEDDING_MODEL` | no | `BAAI/bge-m3` | Embedding model name |
215
+ | `EMBEDDING_BASE_URL` | no | `https://api.siliconflow.cn/v1` | Embedding API base URL |
216
+ | `EMBEDDING_DIMS` | no | auto-detect | Embedding dimensions. When unset, auto-detected from a startup probe; set explicitly to enforce a dimension check at boot |
217
+ | `XINFERENCE_SERVER_URL` | conditional | `http://localhost:9997` | Required when using Xinference |
218
+ | `XINFERENCE_MODEL_UID` | conditional | `bge-m3` | Xinference embedding model UID |
219
+ | `QDRANT_PATH` | no | `qdrant_db` | Local Qdrant storage path (embedded mode) |
220
+ | `QDRANT_HOST` | no | - | Qdrant server host (sets server mode; overrides `QDRANT_PATH`) |
221
+ | `QDRANT_PORT` | no | `6333` | Qdrant server port |
222
+ | `DEDUP_MODE` | no | `skip` | Dedup behavior: `off`, `skip`, `replace`, `rewrite`, `edit` |
223
+ | `ENABLE_BM25` | no | `true` | Enable BM25 sparse search signal |
224
+ | `ENABLE_ENTITY` | no | `false` | Enable entity extraction and boosting |
225
+ | `ENABLE_GRAPH` | no | `false` | Enable graph memory (KuzuDB entity-relation storage). Graph hooks are no-op when disabled |
226
+ | `KUZU_DB_PATH` | conditional | - | KuzuDB database file path. Required when `ENABLE_GRAPH=true` |
227
+ | `GRAPH_THRESHOLD` | no | `0.7` | Entity match threshold for graph retrieval |
228
+ | `GRAPH_SEARCH_TOP_K` | no | `5` | Max relations returned per speaker from graph search |
229
+ | `GRAPH_INJECT_RELATIONS` | no | `false` | Inject graph relations into answer prompt. Only effective when `ENABLE_GRAPH=true`. Experimental: -0.57pp on LOCOMO (2026-07-22), off by default |
230
+ | `GRAPH_EMBEDDING_MODEL` | no | `BAAI/bge-m3` | Embedding model for graph entities (defaults to main embedding model) |
231
+ | `GRAPH_EMBEDDING_DIMS` | no | `1024` | Embedding dimensions for graph entities |
232
+ | `GRAPH_EMBEDDING_BASE_URL` | no | `https://api.siliconflow.cn/v1` | Embedding API base URL for graph entities |
233
+ | `GRAPH_EMBEDDING_API_KEY` | no | - | Embedding API key for graph entities. Defaults to `SILICONFLOW_API_KEY` |
234
+ | `LLM_RERANK` | no | `true` | Enable LLM listwise rerank for recall |
235
+ | `RERANK_MODE` | no | `llm_listwise` | Rerank strategy |
236
+ | `RERANK_CANDS` | no | `20` | Head size for LLM listwise rerank: only top N candidates are reordered, the rest are appended in original order. Only effective when `LLM_RERANK=true` |
237
+ | `RERANK_MAX_CONCURRENT` | no | `4` | Max concurrent LLM rerank calls (protects against API rate limits) |
238
+ | `MERGE_STRATEGY` | no | `off` | Deprecated; use `DEDUP_MODE` instead |
239
+ | `DEDUP_THINKING` | no | `false` | Enable LLM thinking for dedup |
240
+ | `EDIT_THINKING` | no | `false` | Enable LLM thinking for edit mode (DEDUP_MODE=edit) |
241
+ | `HISTORY_DB_PATH` | no | `{QDRANT_PATH}/history.db` | SQLite message history database path |
242
+ | `EXTRACT_LAST_K_MESSAGES` | no | `10` | Number of recent messages fed to extraction as context |
243
+ | `MESSAGE_STORE_BACKEND` | no | `sqlite` | Message store backend: `sqlite` or `none` |
244
+ | `ENTITY_EXTRACTOR_BACKEND` | no | `ner` | Entity extractor: `ner` or `llm` |
245
+ | `ENTITY_STORE_BACKEND` | no | `qdrant` | Entity store backend |
246
+ | `RERANKER_MODEL_PATH` | no | - | Optional local Sentence-Transformers reranker |
247
+ | `RERANKER_DEVICE` | no | `cpu` | Reranker device |
248
+ | `RERANKER_BATCH_SIZE` | no | `32` | Reranker batch size |
249
+ | `RERANKER_TOP_K` | no | `5` | Reranker top-k |
250
+ | `HF_ENDPOINT` | no | `https://hf-mirror.com` | HuggingFace mirror endpoint |
251
+
252
+ ## Custom prompts
253
+
254
+ Every core prompt can be replaced — either with a built-in variant id or with your own prompt file, `from_pretrained`-style. No code changes needed.
255
+
256
+ | Prompt | Env var / CLI flag | Built-in ids | Used when |
257
+ |---|---|---|---|
258
+ | Fact extraction | `EXTRACTION_PROMPT` / `--extraction-prompt` | - | always (write path) |
259
+ | Dedup decision | `DEDUP_PROMPT` / `--dedup-prompt` | `zh` (default), `en` | `DEDUP_MODE=skip/replace/rewrite/edit` |
260
+ | Merge rewrite | `REWRITE_PROMPT` / `--rewrite-prompt` | - | `DEDUP_MODE=rewrite` |
261
+ | Patch edit | `EDIT_PROMPT` / `--edit-prompt` | - | `DEDUP_MODE=edit` |
262
+ | Rerank | `RERANK_PROMPT` / `--rerank-prompt` | - | LLM listwise rerank |
263
+
264
+ Switch to the English dedup prompt (validated on LOCOMO, 2026-07-24):
265
+
266
+ ```bash
267
+ neatmem serve --dedup-prompt en
268
+ ```
269
+
270
+ Use your own prompt:
271
+
272
+ ```bash
273
+ # 1. Export the example templates (they are the exact built-in defaults)
274
+ # From a git clone:
275
+ mkdir -p my_prompts && cp neatmem/prompts/examples/*.txt my_prompts/
276
+ # From a pip install:
277
+ python - <<'EOF'
278
+ from importlib.resources import files
279
+ import shutil, os
280
+ os.makedirs("my_prompts", exist_ok=True)
281
+ for f in files("neatmem.prompts").joinpath("examples").iterdir():
282
+ shutil.copy(f, "my_prompts/")
283
+ EOF
284
+
285
+ # 2. Edit the one you want (keep every {placeholder} intact,
286
+ # including the {{ }} escaping in JSON examples)
287
+
288
+ # 3. Point the server at it
289
+ neatmem serve --dedup-prompt /absolute/path/to/my_prompts/dedup_zh.example.txt
290
+ ```
291
+
292
+ Notes:
293
+
294
+ - Prompts are loaded once at startup; restart the server after editing a file.
295
+ - A value that is neither a known id nor an existing file, a missing file, or a missing `{placeholder}` fails at startup with a clear error.
296
+ - Prefer absolute paths; relative paths resolve against the server's working directory.
297
+
298
+ ## OpenClaw integration
299
+
300
+ NeatMem includes an OpenClaw plugin under `openclaw/`. Build it and install it as a linked local plugin during development:
301
+
302
+ ```bash
303
+ cd /path/to/NeatMem/openclaw
304
+ npm install
305
+ npm run build
306
+
307
+ cd /path/to/NeatMem
308
+ openclaw plugins install ./openclaw --link
309
+ ```
310
+
311
+ After changing plugin TypeScript source, rebuild before reinstalling or restarting OpenClaw.
312
+
313
+ The plugin id is `openclaw-neatmem`. It talks to NeatMem through the local mem0-compatible HTTP API.
314
+
315
+ Example OpenClaw configuration:
316
+
317
+ ```json
318
+ {
319
+ "plugins": {
320
+ "slots": {
321
+ "memory": "openclaw-neatmem"
322
+ },
323
+ "entries": {
324
+ "openclaw-neatmem": {
325
+ "enabled": true,
326
+ "config": {
327
+ "mode": "platform",
328
+ "apiKey": "neatmem-local",
329
+ "userId": "default_user",
330
+ "baseUrl": "http://localhost:8790"
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ Then check:
339
+
340
+ ```bash
341
+ openclaw mem0 status
342
+ ```
343
+
344
+ The CLI command remains `openclaw mem0` for compatibility, but the active plugin id should be `openclaw-neatmem` and the backend should point to `http://localhost:8790`.
345
+
346
+ ## Hermes integration
347
+
348
+ NeatMem includes a Hermes Agent memory provider under `hermes/`. With the NeatMem server running at `http://localhost:8790`:
349
+
350
+ ```bash
351
+ hermes plugins install kanhaoning/NeatMem/hermes --enable
352
+ hermes config set memory.provider neatmem
353
+ ```
354
+
355
+ The plugin registers five memory tools (`neatmem_search`, `neatmem_add`, `neatmem_list`, `neatmem_update`, `neatmem_delete`) and recalls memories automatically on each turn. Optional configuration via `~/.hermes/neatmem.json`:
356
+
357
+ ```json
358
+ {
359
+ "base_url": "http://localhost:8790",
360
+ "user_id": "myname",
361
+ "rerank": true
362
+ }
363
+ ```
364
+
365
+ Verify: tell Hermes "remember that I prefer dark themes", then ask about it in a new session. See [hermes/README.md](https://github.com/kanhaoning/NeatMem/blob/main/hermes/README.md) for the full configuration reference and troubleshooting.
366
+
367
+ ## API examples
368
+
369
+ ### Health check
370
+
371
+ ```bash
372
+ curl http://localhost:8790/health
373
+ ```
374
+
375
+ ### Add memory
376
+
377
+ ```bash
378
+ curl -X POST http://localhost:8790/v1/memories/ \
379
+ -H "Content-Type: application/json" \
380
+ -d '{
381
+ "messages": [
382
+ {"role": "user", "content": "My name is Alex and I work on agent memory systems."},
383
+ {"role": "assistant", "content": "Nice to meet you, Alex."}
384
+ ],
385
+ "user_id": "default_user",
386
+ "infer": true
387
+ }'
388
+ ```
389
+
390
+ ### Search memory
391
+
392
+ ```bash
393
+ curl -X POST http://localhost:8790/v2/memories/search/ \
394
+ -H "Content-Type: application/json" \
395
+ -d '{
396
+ "query": "What is Alex working on?",
397
+ "filters": {"user_id": "default_user"},
398
+ "top_k": 10,
399
+ "threshold": 0.1
400
+ }'
401
+ ```
402
+
403
+ ### List memories
404
+
405
+ ```bash
406
+ curl -X POST http://localhost:8790/v2/memories/ \
407
+ -H "Content-Type: application/json" \
408
+ -d '{
409
+ "filters": {"user_id": "default_user"},
410
+ "page": 1,
411
+ "page_size": 100
412
+ }'
413
+ ```
414
+
415
+ ### Get memory
416
+
417
+ ```bash
418
+ curl http://localhost:8790/v1/memories/{memory_id}/
419
+ ```
420
+
421
+ ### Update memory
422
+
423
+ ```bash
424
+ curl -X PUT http://localhost:8790/v1/memories/{memory_id}/ \
425
+ -H "Content-Type: application/json" \
426
+ -d '{
427
+ "text": "Alex works on local-first agent memory systems.",
428
+ "metadata": {"source": "manual_update"}
429
+ }'
430
+ ```
431
+
432
+ ### Delete memory
433
+
434
+ ```bash
435
+ curl -X DELETE http://localhost:8790/v1/memories/{memory_id}/
436
+ ```
437
+
438
+ ## How it works
439
+
440
+ ### Add flow
441
+
442
+ ```text
443
+ messages
444
+
445
+ retrieve last-k messages as extraction context
446
+
447
+ LLM memory extraction (with last-k context)
448
+
449
+ context completion and source tracking
450
+
451
+ sequential LLM-assisted memory decisions
452
+ ├─ add -> store as new memory
453
+ ├─ none -> skip (duplicate)
454
+ └─ update -> merge per DEDUP_MODE (skip/replace/rewrite/edit)
455
+
456
+ write to vector store + BM25 index + entity store
457
+ ```
458
+
459
+ ### Search flow
460
+
461
+ ```text
462
+ query
463
+
464
+ dense vector search + BM25 sparse search + entity boosting
465
+
466
+ LLM listwise rerank
467
+
468
+ threshold filtering
469
+
470
+ results
471
+ ```
472
+
473
+ ## Development probes
474
+
475
+ Memory quality iteration is done through `probe/`, which contains OpenClaw end-to-end probes and extraction simulation scripts. It is not a benchmark suite.
476
+
477
+ ## Design notes
478
+
479
+ NeatMem is designed around a few constraints:
480
+
481
+ - keep the plugin layer thin
482
+ - keep the backend self-hosted and debuggable
483
+ - do not require Redis or a background scheduler
484
+ - prefer memory quality over feature breadth
485
+ - preserve compatibility with mem0-style APIs where possible
486
+
487
+ ## Limitations
488
+
489
+ NeatMem is in active development. Current limitations:
490
+
491
+ - APIs and packaging may still change.
492
+ - No dashboard or GUI.
493
+ - No multi-tenant permission system.
494
+ - OpenClaw is the primary tested integration path.
495
+ - Prompt behavior is still being iterated and may vary across models.
496
+ - BM25 lemmatization is basic; bilingual (Chinese/English) tokenization needs improvement.
497
+
498
+ ## Roadmap
499
+
500
+ - Bilingual multi-signal support (improved Chinese/English BM25 and entity extraction)
501
+ - PyPI package publication
502
+ - Memory inspection and export/import tools
503
+ - Richer recall diagnostics
504
+
505
+ ## License
506
+
507
+ MIT License.
508
+
509
+ ## Acknowledgements
510
+
511
+ NeatMem is inspired by the mem0 project and mem0-style memory API patterns, and is designed to interoperate with OpenClaw memory plugin flows. Upstream license notices should be preserved where applicable.
512
+
513
+ Some utility functions in `neatmem/utils/spacy/` (`spacy_models.py`, `entity_extraction.py`, `lemmatization.py`) are vendored from mem0 v2.0.0 (Apache-2.0); see file headers for modification notes.