sandclaw-memory 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 (31) hide show
  1. sandclaw_memory-0.1.0/LICENSE +21 -0
  2. sandclaw_memory-0.1.0/PKG-INFO +308 -0
  3. sandclaw_memory-0.1.0/README.md +272 -0
  4. sandclaw_memory-0.1.0/pyproject.toml +88 -0
  5. sandclaw_memory-0.1.0/sandclaw_memory/__init__.py +71 -0
  6. sandclaw_memory-0.1.0/sandclaw_memory/brain.py +656 -0
  7. sandclaw_memory-0.1.0/sandclaw_memory/dispatcher.py +163 -0
  8. sandclaw_memory-0.1.0/sandclaw_memory/exceptions.py +103 -0
  9. sandclaw_memory-0.1.0/sandclaw_memory/loader.py +141 -0
  10. sandclaw_memory-0.1.0/sandclaw_memory/permanent.py +975 -0
  11. sandclaw_memory-0.1.0/sandclaw_memory/py.typed +0 -0
  12. sandclaw_memory-0.1.0/sandclaw_memory/renderer.py +155 -0
  13. sandclaw_memory-0.1.0/sandclaw_memory/session.py +538 -0
  14. sandclaw_memory-0.1.0/sandclaw_memory/summary.py +209 -0
  15. sandclaw_memory-0.1.0/sandclaw_memory/types.py +93 -0
  16. sandclaw_memory-0.1.0/sandclaw_memory/utils.py +185 -0
  17. sandclaw_memory-0.1.0/sandclaw_memory.egg-info/PKG-INFO +308 -0
  18. sandclaw_memory-0.1.0/sandclaw_memory.egg-info/SOURCES.txt +29 -0
  19. sandclaw_memory-0.1.0/sandclaw_memory.egg-info/dependency_links.txt +1 -0
  20. sandclaw_memory-0.1.0/sandclaw_memory.egg-info/requires.txt +8 -0
  21. sandclaw_memory-0.1.0/sandclaw_memory.egg-info/top_level.txt +1 -0
  22. sandclaw_memory-0.1.0/setup.cfg +4 -0
  23. sandclaw_memory-0.1.0/tests/test_brain.py +252 -0
  24. sandclaw_memory-0.1.0/tests/test_dispatcher.py +57 -0
  25. sandclaw_memory-0.1.0/tests/test_exceptions.py +189 -0
  26. sandclaw_memory-0.1.0/tests/test_integration.py +489 -0
  27. sandclaw_memory-0.1.0/tests/test_loader.py +80 -0
  28. sandclaw_memory-0.1.0/tests/test_permanent.py +279 -0
  29. sandclaw_memory-0.1.0/tests/test_real_world.py +489 -0
  30. sandclaw_memory-0.1.0/tests/test_session.py +224 -0
  31. sandclaw_memory-0.1.0/tests/test_summary.py +85 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kokogo100
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.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: sandclaw-memory
3
+ Version: 0.1.0
4
+ Summary: Self-growing tag-dictionary RAG that runs on a $200 laptop.
5
+ Author-email: kokogo100 <kokogo100@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/kokogo100/sandclaw-memory
8
+ Project-URL: Documentation, https://github.com/kokogo100/sandclaw-memory#readme
9
+ Project-URL: Repository, https://github.com/kokogo100/sandclaw-memory
10
+ Project-URL: Issues, https://github.com/kokogo100/sandclaw-memory/issues
11
+ Project-URL: Changelog, https://github.com/kokogo100/sandclaw-memory/blob/main/CHANGELOG.md
12
+ Keywords: rag,memory,llm,ai-agent,tag-based-rag,temporal-rag,self-growing,gpu-free,lightweight,local-first,privacy,sqlite,zero-dependencies
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.8.0; extra == "dev"
32
+ Requires-Dist: pyright>=1.1.350; extra == "dev"
33
+ Requires-Dist: build>=1.0; extra == "dev"
34
+ Requires-Dist: twine>=5.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ <p align="center">
38
+ <h1 align="center">sandclaw-memory</h1>
39
+ <p align="center"><strong>Self-Growing Tag-Dictionary RAG for Any Device</strong></p>
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/sandclaw-memory/"><img src="https://img.shields.io/pypi/v/sandclaw-memory?color=blue" alt="PyPI"></a>
44
+ <a href="https://pypi.org/project/sandclaw-memory/"><img src="https://img.shields.io/pypi/pyversions/sandclaw-memory" alt="Python"></a>
45
+ <a href="https://github.com/kokogo100/sandclaw-memory/blob/main/LICENSE"><img src="https://img.shields.io/github/license/kokogo100/sandclaw-memory" alt="License"></a>
46
+ <a href="https://github.com/kokogo100/sandclaw-memory/actions"><img src="https://img.shields.io/github/actions/workflow/status/kokogo100/sandclaw-memory/ci.yml?label=tests" alt="Tests"></a>
47
+ </p>
48
+
49
+ ---
50
+
51
+ Give your AI **long-term memory that grows smarter over time.**
52
+
53
+ No GPU. No vector database. No external dependencies.
54
+ Just `pip install` and go.
55
+
56
+ ```python
57
+ from sandclaw_memory import BrainMemory
58
+
59
+ brain = BrainMemory(tag_extractor=my_ai_func)
60
+ brain.save("User loves Python and React")
61
+ context = brain.recall("what does the user like?")
62
+ # -> Returns relevant memories as Markdown, ready for LLM injection
63
+ ```
64
+
65
+ ## Why sandclaw-memory?
66
+
67
+ | Feature | sandclaw-memory | Vector DB (Pinecone, Weaviate) | mem0 |
68
+ |---|---|---|---|
69
+ | **Setup** | `pip install` (done) | Docker + API keys + config | `pip install` + API key |
70
+ | **GPU required** | No | Often yes | No |
71
+ | **Cost over time** | Decreases (self-growing) | Constant | Constant |
72
+ | **Search** | FTS5 + BM25 (proven) | Cosine similarity | Embedding-based |
73
+ | **Privacy** | 100% local | Cloud required | Cloud optional |
74
+ | **Dependencies** | 0 (stdlib only) | Many | Several |
75
+ | **Size** | ~50KB | 100MB+ Docker | ~5MB |
76
+
77
+ ### The Self-Growing Advantage
78
+
79
+ Traditional RAG calls AI for **every** search. sandclaw-memory learns:
80
+
81
+ ```
82
+ Day 1: "React로 페이지 만듦" → AI extracts: [react, frontend, page]
83
+ keyword_map registers: "React" → react, "페이지" → page
84
+
85
+ Day 30: "React Native 시작" → keyword_map instant match! No AI needed.
86
+ Cost: $0.00
87
+
88
+ Day 90: 80% of saves match keyword_map → AI calls reduced by 80%
89
+ ```
90
+
91
+ **The more you use it, the cheaper it gets.**
92
+
93
+ ## Quick Start
94
+
95
+ ### Install
96
+
97
+ ```bash
98
+ pip install sandclaw-memory
99
+ ```
100
+
101
+ ### 3-Line Memory
102
+
103
+ ```python
104
+ from sandclaw_memory import BrainMemory
105
+
106
+ with BrainMemory(tag_extractor=my_tag_func) as brain:
107
+ brain.save("Important: migrate to FastAPI by Q2")
108
+ print(brain.recall("what's the migration plan?"))
109
+ ```
110
+
111
+ ### With OpenAI
112
+
113
+ ```python
114
+ import json, openai
115
+ from sandclaw_memory import BrainMemory
116
+
117
+ def tag_extractor(content: str) -> list[str]:
118
+ resp = openai.chat.completions.create(
119
+ model="gpt-4o-mini",
120
+ messages=[{
121
+ "role": "system",
122
+ "content": "Extract 3-7 keyword tags. Return JSON array only."
123
+ }, {"role": "user", "content": content}],
124
+ temperature=0,
125
+ )
126
+ return json.loads(resp.choices[0].message.content)
127
+
128
+ with BrainMemory(
129
+ db_path="./my_memory",
130
+ tag_extractor=tag_extractor,
131
+ ) as brain:
132
+ brain.start_polling() # Background tag extraction every 15s
133
+
134
+ brain.save("User prefers Python and TypeScript")
135
+ brain.save("Decided to use PostgreSQL", source="archive")
136
+
137
+ context = brain.recall("what tech stack?")
138
+ # Inject 'context' into your LLM's system prompt
139
+ ```
140
+
141
+ ### With Claude
142
+
143
+ ```python
144
+ import json, anthropic
145
+ from sandclaw_memory import BrainMemory
146
+
147
+ client = anthropic.Anthropic()
148
+
149
+ def tag_extractor(content: str) -> list[str]:
150
+ resp = client.messages.create(
151
+ model="claude-haiku-4-5-20251001",
152
+ max_tokens=200,
153
+ messages=[{"role": "user",
154
+ "content": f"Extract 3-7 tags as JSON array:\n{content}"}],
155
+ )
156
+ return json.loads(resp.content[0].text)
157
+
158
+ brain = BrainMemory(tag_extractor=tag_extractor)
159
+ ```
160
+
161
+ > See [`examples/`](examples/) for LangChain, custom callbacks, and scheduling patterns.
162
+
163
+ ## Architecture
164
+
165
+ ```
166
+ ┌─────────────────────────────────────────────────┐
167
+ │ BrainMemory │
168
+ │ save() → recall() → start_polling() │
169
+ ├─────────────────────────────────────────────────┤
170
+ │ │
171
+ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │
172
+ │ │ L1 │ │ L2 │ │ L3 │ │
173
+ │ │ Session │ │ Summary │ │ Archive │ │
174
+ │ │ │ │ │ │ │ │
175
+ │ │ 3-day │ │ 30-day │ │ Permanent │ │
176
+ │ │ rolling │ │ AI/text │ │ SQLite + FTS5 │ │
177
+ │ │ Markdown│ │ summary │ │ + self-growing │ │
178
+ │ │ logs │ │ │ │ tag dict │ │
179
+ │ └────┬────┘ └────┬─────┘ └───────┬────────┘ │
180
+ │ │ │ │ │
181
+ │ ┌────┴────────────┴────────────────┴────────┐ │
182
+ │ │ Intent Dispatcher │ │
183
+ │ │ CASUAL → L1 │ STANDARD → L1+L2 │ │
184
+ │ │ │ DEEP → L1+L2+L3 │ │
185
+ │ └───────────────┴───────────────────────────┘ │
186
+ │ │
187
+ │ ┌──────────────────────────────────────────┐ │
188
+ │ │ Self-Growing Tag Pipeline │ │
189
+ │ │ Stage 1: keyword_map (instant, free) │ │
190
+ │ │ Stage 2: AI callback (async, queue) │ │
191
+ │ │ → keyword_map grows → Stage 2 shrinks │ │
192
+ │ └──────────────────────────────────────────┘ │
193
+ └─────────────────────────────────────────────────┘
194
+ ```
195
+
196
+ ### Three Memory Layers
197
+
198
+ | Layer | What | Retention | Storage | Speed |
199
+ |---|---|---|---|---|
200
+ | **L1** Session | Conversation logs | 3 days (rolling) | Markdown files | Instant |
201
+ | **L2** Summary | Period summaries | 30 days | In-memory | Instant |
202
+ | **L3** Archive | Important memories | Forever | SQLite + FTS5 | ~1ms |
203
+
204
+ ### Intent-Based Depth
205
+
206
+ The dispatcher auto-detects how deep to search:
207
+
208
+ ```python
209
+ brain.recall("what time is it?") # → CASUAL (L1 only)
210
+ brain.recall("summarize this month") # → STANDARD (L1 + L2)
211
+ brain.recall("why did we pick React?") # → DEEP (L1 + L2 + L3)
212
+
213
+ # Or override manually:
214
+ brain.recall("anything", depth="deep")
215
+ ```
216
+
217
+ ## API Reference
218
+
219
+ ### BrainMemory
220
+
221
+ ```python
222
+ BrainMemory(
223
+ db_path="./memory", # Where to store files
224
+ tag_extractor=my_func, # REQUIRED: (str) -> list[str]
225
+ promote_checker=None, # Optional: (str) -> bool
226
+ depth_detector=None, # Optional: (str) -> str
227
+ duplicate_checker=None, # Optional: (str, str) -> bool
228
+ conflict_resolver=None, # Optional: (str, str) -> str
229
+ polling_interval=15, # Seconds between maintenance cycles
230
+ rolling_days=3, # L1 retention period
231
+ summary_days=30, # L2 summary period
232
+ max_context_chars=15_000, # Context budget for LLM injection
233
+ encryption_key=None, # Optional SQLCipher encryption
234
+ )
235
+ ```
236
+
237
+ ### Core Methods
238
+
239
+ | Method | Description |
240
+ |---|---|
241
+ | `save(content, source="chat", tags=None)` | Save to memory. `source="archive"` forces L3. |
242
+ | `recall(query, depth=None)` | Recall relevant memories as Markdown. |
243
+ | `search(query, limit=20)` | Search L3 archive, returns `list[MemoryEntry]`. |
244
+ | `promote(content, tags=None)` | Manually save to L3. Returns memory ID. |
245
+ | `summarize(llm_callback=None)` | Generate a 30-day summary. |
246
+ | `start_polling()` | Start background maintenance loop. |
247
+ | `stop_polling()` | Stop background maintenance loop. |
248
+ | `run_maintenance()` | Run one maintenance cycle manually. |
249
+ | `get_stats()` | Get stats from all layers. |
250
+ | `get_tag_stats()` | Get tag usage counts. |
251
+ | `export_json(path=None)` | Export all data as JSON. |
252
+ | `on(event, callback)` | Register an event hook. |
253
+ | `close()` | Stop polling and close DB. |
254
+
255
+ ### The 5 AI Callbacks
256
+
257
+ | Callback | Signature | Default |
258
+ |---|---|---|
259
+ | `tag_extractor` | `(str) -> list[str]` | **REQUIRED** |
260
+ | `promote_checker` | `(str) -> bool` | `len(content) > 200` |
261
+ | `depth_detector` | `(str) -> str` | Keyword matching |
262
+ | `duplicate_checker` | `(str, str) -> bool` | SequenceMatcher > 0.85 |
263
+ | `conflict_resolver` | `(str, str) -> str` | Keep newer text |
264
+
265
+ ### Event Hooks
266
+
267
+ ```python
268
+ brain.on("before_save", lambda content, source: ...)
269
+ brain.on("after_save", lambda content, source: ...)
270
+ brain.on("before_promote", lambda content: ...)
271
+ brain.on("after_promote", lambda content, mem_id: ...)
272
+ brain.on("after_recall", lambda query, depth, result: ...)
273
+ brain.on("after_cycle", lambda stats: ...)
274
+ ```
275
+
276
+ ## Examples
277
+
278
+ | File | Description |
279
+ |---|---|
280
+ | [`basic_usage.py`](examples/basic_usage.py) | Simplest setup with OpenAI |
281
+ | [`with_openai.py`](examples/with_openai.py) | All 5 callbacks with OpenAI |
282
+ | [`with_anthropic.py`](examples/with_anthropic.py) | Claude API integration |
283
+ | [`with_langchain.py`](examples/with_langchain.py) | LangChain agent + memory |
284
+ | [`custom_callbacks.py`](examples/custom_callbacks.py) | Custom callbacks + hooks |
285
+ | [`scheduling.py`](examples/scheduling.py) | Polling, FastAPI, Celery |
286
+
287
+ ## Compatibility
288
+
289
+ - **Python**: 3.9, 3.10, 3.11, 3.12, 3.13
290
+ - **OS**: Windows, macOS, Linux
291
+ - **Dependencies**: None (Python stdlib only)
292
+ - **SQLite**: Uses built-in `sqlite3` module (FTS5 optional, graceful fallback)
293
+
294
+ ## Roadmap
295
+
296
+ | Version | Codename | Status |
297
+ |---|---|---|
298
+ | v0.1.0 | "Remembering AI" | Current |
299
+ | v0.2.0 | "Growing AI" | Planned -- tag trees + built-in AI (Gemma 4) |
300
+ | v0.3.0 | "Thinking AI" | Planned -- contradiction detection (CKN) |
301
+
302
+ ## Contributing
303
+
304
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
305
+
306
+ ## License
307
+
308
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,272 @@
1
+ <p align="center">
2
+ <h1 align="center">sandclaw-memory</h1>
3
+ <p align="center"><strong>Self-Growing Tag-Dictionary RAG for Any Device</strong></p>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://pypi.org/project/sandclaw-memory/"><img src="https://img.shields.io/pypi/v/sandclaw-memory?color=blue" alt="PyPI"></a>
8
+ <a href="https://pypi.org/project/sandclaw-memory/"><img src="https://img.shields.io/pypi/pyversions/sandclaw-memory" alt="Python"></a>
9
+ <a href="https://github.com/kokogo100/sandclaw-memory/blob/main/LICENSE"><img src="https://img.shields.io/github/license/kokogo100/sandclaw-memory" alt="License"></a>
10
+ <a href="https://github.com/kokogo100/sandclaw-memory/actions"><img src="https://img.shields.io/github/actions/workflow/status/kokogo100/sandclaw-memory/ci.yml?label=tests" alt="Tests"></a>
11
+ </p>
12
+
13
+ ---
14
+
15
+ Give your AI **long-term memory that grows smarter over time.**
16
+
17
+ No GPU. No vector database. No external dependencies.
18
+ Just `pip install` and go.
19
+
20
+ ```python
21
+ from sandclaw_memory import BrainMemory
22
+
23
+ brain = BrainMemory(tag_extractor=my_ai_func)
24
+ brain.save("User loves Python and React")
25
+ context = brain.recall("what does the user like?")
26
+ # -> Returns relevant memories as Markdown, ready for LLM injection
27
+ ```
28
+
29
+ ## Why sandclaw-memory?
30
+
31
+ | Feature | sandclaw-memory | Vector DB (Pinecone, Weaviate) | mem0 |
32
+ |---|---|---|---|
33
+ | **Setup** | `pip install` (done) | Docker + API keys + config | `pip install` + API key |
34
+ | **GPU required** | No | Often yes | No |
35
+ | **Cost over time** | Decreases (self-growing) | Constant | Constant |
36
+ | **Search** | FTS5 + BM25 (proven) | Cosine similarity | Embedding-based |
37
+ | **Privacy** | 100% local | Cloud required | Cloud optional |
38
+ | **Dependencies** | 0 (stdlib only) | Many | Several |
39
+ | **Size** | ~50KB | 100MB+ Docker | ~5MB |
40
+
41
+ ### The Self-Growing Advantage
42
+
43
+ Traditional RAG calls AI for **every** search. sandclaw-memory learns:
44
+
45
+ ```
46
+ Day 1: "React로 페이지 만듦" → AI extracts: [react, frontend, page]
47
+ keyword_map registers: "React" → react, "페이지" → page
48
+
49
+ Day 30: "React Native 시작" → keyword_map instant match! No AI needed.
50
+ Cost: $0.00
51
+
52
+ Day 90: 80% of saves match keyword_map → AI calls reduced by 80%
53
+ ```
54
+
55
+ **The more you use it, the cheaper it gets.**
56
+
57
+ ## Quick Start
58
+
59
+ ### Install
60
+
61
+ ```bash
62
+ pip install sandclaw-memory
63
+ ```
64
+
65
+ ### 3-Line Memory
66
+
67
+ ```python
68
+ from sandclaw_memory import BrainMemory
69
+
70
+ with BrainMemory(tag_extractor=my_tag_func) as brain:
71
+ brain.save("Important: migrate to FastAPI by Q2")
72
+ print(brain.recall("what's the migration plan?"))
73
+ ```
74
+
75
+ ### With OpenAI
76
+
77
+ ```python
78
+ import json, openai
79
+ from sandclaw_memory import BrainMemory
80
+
81
+ def tag_extractor(content: str) -> list[str]:
82
+ resp = openai.chat.completions.create(
83
+ model="gpt-4o-mini",
84
+ messages=[{
85
+ "role": "system",
86
+ "content": "Extract 3-7 keyword tags. Return JSON array only."
87
+ }, {"role": "user", "content": content}],
88
+ temperature=0,
89
+ )
90
+ return json.loads(resp.choices[0].message.content)
91
+
92
+ with BrainMemory(
93
+ db_path="./my_memory",
94
+ tag_extractor=tag_extractor,
95
+ ) as brain:
96
+ brain.start_polling() # Background tag extraction every 15s
97
+
98
+ brain.save("User prefers Python and TypeScript")
99
+ brain.save("Decided to use PostgreSQL", source="archive")
100
+
101
+ context = brain.recall("what tech stack?")
102
+ # Inject 'context' into your LLM's system prompt
103
+ ```
104
+
105
+ ### With Claude
106
+
107
+ ```python
108
+ import json, anthropic
109
+ from sandclaw_memory import BrainMemory
110
+
111
+ client = anthropic.Anthropic()
112
+
113
+ def tag_extractor(content: str) -> list[str]:
114
+ resp = client.messages.create(
115
+ model="claude-haiku-4-5-20251001",
116
+ max_tokens=200,
117
+ messages=[{"role": "user",
118
+ "content": f"Extract 3-7 tags as JSON array:\n{content}"}],
119
+ )
120
+ return json.loads(resp.content[0].text)
121
+
122
+ brain = BrainMemory(tag_extractor=tag_extractor)
123
+ ```
124
+
125
+ > See [`examples/`](examples/) for LangChain, custom callbacks, and scheduling patterns.
126
+
127
+ ## Architecture
128
+
129
+ ```
130
+ ┌─────────────────────────────────────────────────┐
131
+ │ BrainMemory │
132
+ │ save() → recall() → start_polling() │
133
+ ├─────────────────────────────────────────────────┤
134
+ │ │
135
+ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │
136
+ │ │ L1 │ │ L2 │ │ L3 │ │
137
+ │ │ Session │ │ Summary │ │ Archive │ │
138
+ │ │ │ │ │ │ │ │
139
+ │ │ 3-day │ │ 30-day │ │ Permanent │ │
140
+ │ │ rolling │ │ AI/text │ │ SQLite + FTS5 │ │
141
+ │ │ Markdown│ │ summary │ │ + self-growing │ │
142
+ │ │ logs │ │ │ │ tag dict │ │
143
+ │ └────┬────┘ └────┬─────┘ └───────┬────────┘ │
144
+ │ │ │ │ │
145
+ │ ┌────┴────────────┴────────────────┴────────┐ │
146
+ │ │ Intent Dispatcher │ │
147
+ │ │ CASUAL → L1 │ STANDARD → L1+L2 │ │
148
+ │ │ │ DEEP → L1+L2+L3 │ │
149
+ │ └───────────────┴───────────────────────────┘ │
150
+ │ │
151
+ │ ┌──────────────────────────────────────────┐ │
152
+ │ │ Self-Growing Tag Pipeline │ │
153
+ │ │ Stage 1: keyword_map (instant, free) │ │
154
+ │ │ Stage 2: AI callback (async, queue) │ │
155
+ │ │ → keyword_map grows → Stage 2 shrinks │ │
156
+ │ └──────────────────────────────────────────┘ │
157
+ └─────────────────────────────────────────────────┘
158
+ ```
159
+
160
+ ### Three Memory Layers
161
+
162
+ | Layer | What | Retention | Storage | Speed |
163
+ |---|---|---|---|---|
164
+ | **L1** Session | Conversation logs | 3 days (rolling) | Markdown files | Instant |
165
+ | **L2** Summary | Period summaries | 30 days | In-memory | Instant |
166
+ | **L3** Archive | Important memories | Forever | SQLite + FTS5 | ~1ms |
167
+
168
+ ### Intent-Based Depth
169
+
170
+ The dispatcher auto-detects how deep to search:
171
+
172
+ ```python
173
+ brain.recall("what time is it?") # → CASUAL (L1 only)
174
+ brain.recall("summarize this month") # → STANDARD (L1 + L2)
175
+ brain.recall("why did we pick React?") # → DEEP (L1 + L2 + L3)
176
+
177
+ # Or override manually:
178
+ brain.recall("anything", depth="deep")
179
+ ```
180
+
181
+ ## API Reference
182
+
183
+ ### BrainMemory
184
+
185
+ ```python
186
+ BrainMemory(
187
+ db_path="./memory", # Where to store files
188
+ tag_extractor=my_func, # REQUIRED: (str) -> list[str]
189
+ promote_checker=None, # Optional: (str) -> bool
190
+ depth_detector=None, # Optional: (str) -> str
191
+ duplicate_checker=None, # Optional: (str, str) -> bool
192
+ conflict_resolver=None, # Optional: (str, str) -> str
193
+ polling_interval=15, # Seconds between maintenance cycles
194
+ rolling_days=3, # L1 retention period
195
+ summary_days=30, # L2 summary period
196
+ max_context_chars=15_000, # Context budget for LLM injection
197
+ encryption_key=None, # Optional SQLCipher encryption
198
+ )
199
+ ```
200
+
201
+ ### Core Methods
202
+
203
+ | Method | Description |
204
+ |---|---|
205
+ | `save(content, source="chat", tags=None)` | Save to memory. `source="archive"` forces L3. |
206
+ | `recall(query, depth=None)` | Recall relevant memories as Markdown. |
207
+ | `search(query, limit=20)` | Search L3 archive, returns `list[MemoryEntry]`. |
208
+ | `promote(content, tags=None)` | Manually save to L3. Returns memory ID. |
209
+ | `summarize(llm_callback=None)` | Generate a 30-day summary. |
210
+ | `start_polling()` | Start background maintenance loop. |
211
+ | `stop_polling()` | Stop background maintenance loop. |
212
+ | `run_maintenance()` | Run one maintenance cycle manually. |
213
+ | `get_stats()` | Get stats from all layers. |
214
+ | `get_tag_stats()` | Get tag usage counts. |
215
+ | `export_json(path=None)` | Export all data as JSON. |
216
+ | `on(event, callback)` | Register an event hook. |
217
+ | `close()` | Stop polling and close DB. |
218
+
219
+ ### The 5 AI Callbacks
220
+
221
+ | Callback | Signature | Default |
222
+ |---|---|---|
223
+ | `tag_extractor` | `(str) -> list[str]` | **REQUIRED** |
224
+ | `promote_checker` | `(str) -> bool` | `len(content) > 200` |
225
+ | `depth_detector` | `(str) -> str` | Keyword matching |
226
+ | `duplicate_checker` | `(str, str) -> bool` | SequenceMatcher > 0.85 |
227
+ | `conflict_resolver` | `(str, str) -> str` | Keep newer text |
228
+
229
+ ### Event Hooks
230
+
231
+ ```python
232
+ brain.on("before_save", lambda content, source: ...)
233
+ brain.on("after_save", lambda content, source: ...)
234
+ brain.on("before_promote", lambda content: ...)
235
+ brain.on("after_promote", lambda content, mem_id: ...)
236
+ brain.on("after_recall", lambda query, depth, result: ...)
237
+ brain.on("after_cycle", lambda stats: ...)
238
+ ```
239
+
240
+ ## Examples
241
+
242
+ | File | Description |
243
+ |---|---|
244
+ | [`basic_usage.py`](examples/basic_usage.py) | Simplest setup with OpenAI |
245
+ | [`with_openai.py`](examples/with_openai.py) | All 5 callbacks with OpenAI |
246
+ | [`with_anthropic.py`](examples/with_anthropic.py) | Claude API integration |
247
+ | [`with_langchain.py`](examples/with_langchain.py) | LangChain agent + memory |
248
+ | [`custom_callbacks.py`](examples/custom_callbacks.py) | Custom callbacks + hooks |
249
+ | [`scheduling.py`](examples/scheduling.py) | Polling, FastAPI, Celery |
250
+
251
+ ## Compatibility
252
+
253
+ - **Python**: 3.9, 3.10, 3.11, 3.12, 3.13
254
+ - **OS**: Windows, macOS, Linux
255
+ - **Dependencies**: None (Python stdlib only)
256
+ - **SQLite**: Uses built-in `sqlite3` module (FTS5 optional, graceful fallback)
257
+
258
+ ## Roadmap
259
+
260
+ | Version | Codename | Status |
261
+ |---|---|---|
262
+ | v0.1.0 | "Remembering AI" | Current |
263
+ | v0.2.0 | "Growing AI" | Planned -- tag trees + built-in AI (Gemma 4) |
264
+ | v0.3.0 | "Thinking AI" | Planned -- contradiction detection (CKN) |
265
+
266
+ ## Contributing
267
+
268
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
269
+
270
+ ## License
271
+
272
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,88 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sandclaw-memory"
7
+ version = "0.1.0"
8
+ description = "Self-growing tag-dictionary RAG that runs on a $200 laptop."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "kokogo100", email = "kokogo100@users.noreply.github.com" },
14
+ ]
15
+ keywords = [
16
+ "rag",
17
+ "memory",
18
+ "llm",
19
+ "ai-agent",
20
+ "tag-based-rag",
21
+ "temporal-rag",
22
+ "self-growing",
23
+ "gpu-free",
24
+ "lightweight",
25
+ "local-first",
26
+ "privacy",
27
+ "sqlite",
28
+ "zero-dependencies",
29
+ ]
30
+ classifiers = [
31
+ "Development Status :: 3 - Alpha",
32
+ "Intended Audience :: Developers",
33
+ "Operating System :: OS Independent",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.9",
36
+ "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Programming Language :: Python :: 3.13",
40
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
41
+ "Topic :: Software Development :: Libraries :: Python Modules",
42
+ "Typing :: Typed",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/kokogo100/sandclaw-memory"
47
+ Documentation = "https://github.com/kokogo100/sandclaw-memory#readme"
48
+ Repository = "https://github.com/kokogo100/sandclaw-memory"
49
+ Issues = "https://github.com/kokogo100/sandclaw-memory/issues"
50
+ Changelog = "https://github.com/kokogo100/sandclaw-memory/blob/main/CHANGELOG.md"
51
+
52
+ [project.optional-dependencies]
53
+ dev = [
54
+ "pytest>=7.0",
55
+ "pytest-cov>=4.0",
56
+ "ruff>=0.8.0",
57
+ "pyright>=1.1.350",
58
+ "build>=1.0",
59
+ "twine>=5.0",
60
+ ]
61
+
62
+ # ═══════════════════════════════════════════════════════════
63
+ # Ruff — fast Python linter & formatter
64
+ # ═══════════════════════════════════════════════════════════
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py39"
68
+
69
+ [tool.ruff.format]
70
+ quote-style = "double"
71
+
72
+ [tool.ruff.lint]
73
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
74
+
75
+ # ═══════════════════════════════════════════════════════════
76
+ # Pytest
77
+ # ═══════════════════════════════════════════════════════════
78
+ [tool.pytest.ini_options]
79
+ testpaths = ["tests"]
80
+ pythonpath = ["."]
81
+
82
+ # ═══════════════════════════════════════════════════════════
83
+ # Pyright — type checking
84
+ # ═══════════════════════════════════════════════════════════
85
+ [tool.pyright]
86
+ pythonVersion = "3.9"
87
+ typeCheckingMode = "basic"
88
+ include = ["sandclaw_memory"]