memsearch 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.
- memsearch-0.1.0/.github/workflows/docs.yml +27 -0
- memsearch-0.1.0/.github/workflows/release.yml +40 -0
- memsearch-0.1.0/.gitignore +11 -0
- memsearch-0.1.0/LICENSE +21 -0
- memsearch-0.1.0/PKG-INFO +466 -0
- memsearch-0.1.0/README.md +435 -0
- memsearch-0.1.0/assets/logo-banner-alt.jpg +0 -0
- memsearch-0.1.0/assets/logo-banner.jpg +0 -0
- memsearch-0.1.0/assets/logo-icon.jpg +0 -0
- memsearch-0.1.0/ccplugin/.claude-plugin/plugin.json +5 -0
- memsearch-0.1.0/ccplugin/README.md +257 -0
- memsearch-0.1.0/ccplugin/hooks/common.sh +85 -0
- memsearch-0.1.0/ccplugin/hooks/hooks.json +50 -0
- memsearch-0.1.0/ccplugin/hooks/parse-transcript.sh +111 -0
- memsearch-0.1.0/ccplugin/hooks/session-end.sh +9 -0
- memsearch-0.1.0/ccplugin/hooks/session-start.sh +68 -0
- memsearch-0.1.0/ccplugin/hooks/stop.sh +97 -0
- memsearch-0.1.0/ccplugin/hooks/user-prompt-submit.sh +48 -0
- memsearch-0.1.0/docs/architecture.md +374 -0
- memsearch-0.1.0/docs/assets/logo-icon.jpg +0 -0
- memsearch-0.1.0/docs/claude-plugin.md +390 -0
- memsearch-0.1.0/docs/cli.md +742 -0
- memsearch-0.1.0/docs/getting-started.md +530 -0
- memsearch-0.1.0/docs/index.md +172 -0
- memsearch-0.1.0/docs/stylesheets/terminal.css +189 -0
- memsearch-0.1.0/examples/sample-memory/MEMORY.md +35 -0
- memsearch-0.1.0/examples/sample-memory/memory/2026-02-05.md +27 -0
- memsearch-0.1.0/examples/sample-memory/memory/2026-02-06.md +37 -0
- memsearch-0.1.0/examples/sample-memory/memory/2026-02-07.md +31 -0
- memsearch-0.1.0/examples/sample-memory/memory/2026-02-08.md +4 -0
- memsearch-0.1.0/examples/test_e2e.py +26 -0
- memsearch-0.1.0/mkdocs.yml +54 -0
- memsearch-0.1.0/pyproject.toml +45 -0
- memsearch-0.1.0/src/memsearch/__init__.py +5 -0
- memsearch-0.1.0/src/memsearch/__main__.py +5 -0
- memsearch-0.1.0/src/memsearch/chunker.py +155 -0
- memsearch-0.1.0/src/memsearch/cli.py +628 -0
- memsearch-0.1.0/src/memsearch/config.py +230 -0
- memsearch-0.1.0/src/memsearch/core.py +339 -0
- memsearch-0.1.0/src/memsearch/embeddings/__init__.py +78 -0
- memsearch-0.1.0/src/memsearch/embeddings/google.py +37 -0
- memsearch-0.1.0/src/memsearch/embeddings/local.py +37 -0
- memsearch-0.1.0/src/memsearch/embeddings/ollama.py +31 -0
- memsearch-0.1.0/src/memsearch/embeddings/openai.py +48 -0
- memsearch-0.1.0/src/memsearch/embeddings/voyage.py +40 -0
- memsearch-0.1.0/src/memsearch/flush.py +107 -0
- memsearch-0.1.0/src/memsearch/scanner.py +65 -0
- memsearch-0.1.0/src/memsearch/store.py +212 -0
- memsearch-0.1.0/src/memsearch/transcript.py +224 -0
- memsearch-0.1.0/src/memsearch/watcher.py +120 -0
- memsearch-0.1.0/tests/__init__.py +0 -0
- memsearch-0.1.0/tests/test_chunker.py +63 -0
- memsearch-0.1.0/tests/test_config.py +172 -0
- memsearch-0.1.0/tests/test_core.py +68 -0
- memsearch-0.1.0/tests/test_embeddings_openai.py +54 -0
- memsearch-0.1.0/tests/test_scanner.py +52 -0
- memsearch-0.1.0/tests/test_store.py +150 -0
- memsearch-0.1.0/uv.lock +3330 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Deploy docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'docs/**'
|
|
8
|
+
- 'mkdocs.yml'
|
|
9
|
+
- '.github/workflows/docs.yml'
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
|
|
25
|
+
- run: pip install mkdocs-material mkdocs-terminal pymdown-extensions
|
|
26
|
+
|
|
27
|
+
- run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Publish to PyPI when a version tag is pushed
|
|
2
|
+
#
|
|
3
|
+
# Usage:
|
|
4
|
+
# git tag v0.1.0 # Must match the version in pyproject.toml
|
|
5
|
+
# git push --tags
|
|
6
|
+
|
|
7
|
+
name: Publish Python Package to PyPI
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*"
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
name: Publish to PyPI
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
environment: pypi
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
id-token: write
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout code
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.10"
|
|
32
|
+
|
|
33
|
+
- name: Install build tools
|
|
34
|
+
run: python -m pip install build
|
|
35
|
+
|
|
36
|
+
- name: Build package
|
|
37
|
+
run: python -m build
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
memsearch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 zhangchen
|
|
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.
|
memsearch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memsearch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Semantic memory search for markdown knowledge bases
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: click>=8.1
|
|
9
|
+
Requires-Dist: openai>=1.0
|
|
10
|
+
Requires-Dist: pymilvus[milvus-lite]>=2.5.0
|
|
11
|
+
Requires-Dist: setuptools<75
|
|
12
|
+
Requires-Dist: tomli-w>=1.0
|
|
13
|
+
Requires-Dist: watchdog>=4.0
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Requires-Dist: anthropic>=0.40; extra == 'all'
|
|
16
|
+
Requires-Dist: google-genai>=1.0; extra == 'all'
|
|
17
|
+
Requires-Dist: ollama>=0.4; extra == 'all'
|
|
18
|
+
Requires-Dist: sentence-transformers>=3.0; extra == 'all'
|
|
19
|
+
Requires-Dist: voyageai>=0.3; extra == 'all'
|
|
20
|
+
Provides-Extra: anthropic
|
|
21
|
+
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
|
|
22
|
+
Provides-Extra: google
|
|
23
|
+
Requires-Dist: google-genai>=1.0; extra == 'google'
|
|
24
|
+
Provides-Extra: local
|
|
25
|
+
Requires-Dist: sentence-transformers>=3.0; extra == 'local'
|
|
26
|
+
Provides-Extra: ollama
|
|
27
|
+
Requires-Dist: ollama>=0.4; extra == 'ollama'
|
|
28
|
+
Provides-Extra: voyage
|
|
29
|
+
Requires-Dist: voyageai>=0.3; extra == 'voyage'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<img src="assets/logo-icon.jpg" alt="memsearch" width="120">
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<h1 align="center">memsearch</h1>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<strong><a href="https://github.com/openclaw/openclaw">OpenClaw</a>'s memory, everywhere.</strong>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
> 💡 **memsearch extracts [OpenClaw](https://github.com/openclaw/openclaw)'s memory system into a standalone library** — same markdown-first architecture, same chunking, same chunk ID format. Pluggable into *any* agent framework, backed by [Milvus](https://milvus.io/) (local Milvus Lite → Milvus Server → Zilliz Cloud). See it in action with the included **[Claude Code plugin](ccplugin/README.md)**.
|
|
43
|
+
|
|
44
|
+
### ✨ Why memsearch?
|
|
45
|
+
|
|
46
|
+
- 🦞 **OpenClaw's memory, everywhere** — OpenClaw has one of the best memory designs in open-source AI: **markdown as the single source of truth** — simple, human-readable, `git`-friendly, zero vendor lock-in
|
|
47
|
+
- ⚡ **Smart dedup** — SHA-256 content hashing means unchanged content is never re-embedded
|
|
48
|
+
- 🔄 **Live sync** — File watcher auto-indexes on changes, deletes stale chunks when files are removed
|
|
49
|
+
- 🧹 **Memory flush** — LLM-powered summarization compresses old memories, just like OpenClaw's flush cycle
|
|
50
|
+
- 🧩 **Claude Code plugin included** — A real-world example: **[ccplugin/](ccplugin/README.md)** gives Claude persistent memory across sessions with zero config
|
|
51
|
+
|
|
52
|
+
## 🔍 How It Works
|
|
53
|
+
|
|
54
|
+
**Markdown is the source of truth** — the vector store is just a derived index, rebuildable anytime.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
┌─── Search ─────────────────────────────────────────────────────────┐
|
|
58
|
+
│ │
|
|
59
|
+
│ "how to configure Redis?" │
|
|
60
|
+
│ │ │
|
|
61
|
+
│ ▼ │
|
|
62
|
+
│ ┌──────────┐ ┌─────────────────┐ ┌──────────────────┐ │
|
|
63
|
+
│ │ Embed │────▶│ Cosine similarity│────▶│ Top-K results │ │
|
|
64
|
+
│ │ query │ │ (Milvus) │ │ with source info │ │
|
|
65
|
+
│ └──────────┘ └─────────────────┘ └──────────────────┘ │
|
|
66
|
+
│ │
|
|
67
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
68
|
+
|
|
69
|
+
┌─── Ingest ─────────────────────────────────────────────────────────┐
|
|
70
|
+
│ │
|
|
71
|
+
│ MEMORY.md │
|
|
72
|
+
│ memory/2026-02-09.md ┌──────────┐ ┌────────────────┐ │
|
|
73
|
+
│ memory/2026-02-08.md ───▶│ Chunker │────▶│ Dedup │ │
|
|
74
|
+
│ │(heading, │ │(chunk_hash PK) │ │
|
|
75
|
+
│ │paragraph)│ └───────┬────────┘ │
|
|
76
|
+
│ └──────────┘ │ │
|
|
77
|
+
│ new chunks only │
|
|
78
|
+
│ ▼ │
|
|
79
|
+
│ ┌──────────────┐ │
|
|
80
|
+
│ │ Embed & │ │
|
|
81
|
+
│ │ Milvus upsert│ │
|
|
82
|
+
│ └──────────────┘ │
|
|
83
|
+
│ │
|
|
84
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
85
|
+
|
|
86
|
+
┌─── Watch ──────────────────────────────────────────────────────────┐
|
|
87
|
+
│ File watcher (1500ms debounce) ──▶ auto re-index / delete stale │
|
|
88
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
89
|
+
|
|
90
|
+
┌─── Flush ──────────────────────────────────────────────────────────┐
|
|
91
|
+
│ Retrieve chunks ──▶ LLM summarize ──▶ write memory/YYYY-MM-DD.md │
|
|
92
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
🔒 The entire pipeline runs locally by default — your data never leaves your machine unless you choose a remote Milvus backend or a cloud embedding provider.
|
|
96
|
+
|
|
97
|
+
## 🧩 Claude Code Plugin
|
|
98
|
+
|
|
99
|
+
memsearch ships with a **[Claude Code plugin](ccplugin/README.md)** — a real-world example of OpenClaw's memory running outside OpenClaw. It gives Claude **automatic persistent memory** across sessions: every session is summarized to markdown, every prompt triggers a semantic search, and a background watcher keeps the index in sync. No commands to learn, no manual saving — just install and go.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Install memsearch, then launch Claude with the plugin
|
|
103
|
+
pip install memsearch
|
|
104
|
+
claude --plugin-dir ./ccplugin
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Session start ──▶ start memsearch watch (singleton) ──▶ inject recent memories
|
|
109
|
+
│
|
|
110
|
+
User prompt ──▶ memsearch search ──▶ inject relevant memories
|
|
111
|
+
│
|
|
112
|
+
Claude stops ──▶ haiku summary ──▶ write .memsearch/memory/YYYY-MM-DD.md
|
|
113
|
+
│ │
|
|
114
|
+
Session end ──▶ stop watch watch auto-indexes ◀┘
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Under the hood: 4 shell hooks + 1 watch process, all calling the `memsearch` CLI. Memories are transparent `.md` files — human-readable, git-friendly, rebuildable. See **[ccplugin/README.md](ccplugin/README.md)** for the full architecture, hook details, progressive disclosure model, and comparison with claude-mem.
|
|
118
|
+
|
|
119
|
+
## 📦 Installation
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip install memsearch
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Additional embedding providers
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pip install "memsearch[google]" # Google Gemini
|
|
129
|
+
pip install "memsearch[voyage]" # Voyage AI
|
|
130
|
+
pip install "memsearch[ollama]" # Ollama (local)
|
|
131
|
+
pip install "memsearch[local]" # sentence-transformers (local, no API key)
|
|
132
|
+
pip install "memsearch[all]" # Everything
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 🐍 Python API — Build an Agent with Memory
|
|
136
|
+
|
|
137
|
+
The example below shows a complete agent loop with memory: save knowledge to markdown, index it, and recall it later via semantic search.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import asyncio
|
|
141
|
+
from datetime import date
|
|
142
|
+
from pathlib import Path
|
|
143
|
+
from openai import OpenAI
|
|
144
|
+
from memsearch import MemSearch
|
|
145
|
+
|
|
146
|
+
MEMORY_DIR = "./memory"
|
|
147
|
+
llm = OpenAI() # your LLM client
|
|
148
|
+
ms = MemSearch(paths=[MEMORY_DIR]) # memsearch handles the rest
|
|
149
|
+
|
|
150
|
+
def save_memory(content: str):
|
|
151
|
+
"""Append a note to today's memory log (OpenClaw-style daily markdown)."""
|
|
152
|
+
p = Path(MEMORY_DIR) / f"{date.today()}.md"
|
|
153
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
with open(p, "a") as f:
|
|
155
|
+
f.write(f"\n{content}\n")
|
|
156
|
+
|
|
157
|
+
async def agent_chat(user_input: str) -> str:
|
|
158
|
+
# 1. Recall — search past memories for relevant context
|
|
159
|
+
memories = await ms.search(user_input, top_k=3)
|
|
160
|
+
context = "\n".join(f"- {m['content'][:200]}" for m in memories)
|
|
161
|
+
|
|
162
|
+
# 2. Think — call LLM with memory context
|
|
163
|
+
resp = llm.chat.completions.create(
|
|
164
|
+
model="gpt-4o-mini",
|
|
165
|
+
messages=[
|
|
166
|
+
{"role": "system", "content": f"You have these memories:\n{context}"},
|
|
167
|
+
{"role": "user", "content": user_input},
|
|
168
|
+
],
|
|
169
|
+
)
|
|
170
|
+
answer = resp.choices[0].message.content
|
|
171
|
+
|
|
172
|
+
# 3. Remember — save this exchange and index it
|
|
173
|
+
save_memory(f"## {user_input}\n{answer}")
|
|
174
|
+
await ms.index()
|
|
175
|
+
|
|
176
|
+
return answer
|
|
177
|
+
|
|
178
|
+
async def main():
|
|
179
|
+
# Seed some knowledge
|
|
180
|
+
save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
|
|
181
|
+
save_memory("## Decision\nWe chose Redis for caching over Memcached.")
|
|
182
|
+
await ms.index()
|
|
183
|
+
|
|
184
|
+
# Agent can now recall those memories
|
|
185
|
+
print(await agent_chat("Who is our frontend lead?"))
|
|
186
|
+
print(await agent_chat("What caching solution did we pick?"))
|
|
187
|
+
|
|
188
|
+
asyncio.run(main())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
<details>
|
|
192
|
+
<summary>💜 <b>Anthropic Claude example</b> — click to expand</summary>
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pip install memsearch anthropic
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
import asyncio
|
|
200
|
+
from datetime import date
|
|
201
|
+
from pathlib import Path
|
|
202
|
+
from anthropic import Anthropic
|
|
203
|
+
from memsearch import MemSearch
|
|
204
|
+
|
|
205
|
+
MEMORY_DIR = "./memory"
|
|
206
|
+
llm = Anthropic()
|
|
207
|
+
ms = MemSearch(paths=[MEMORY_DIR])
|
|
208
|
+
|
|
209
|
+
def save_memory(content: str):
|
|
210
|
+
p = Path(MEMORY_DIR) / f"{date.today()}.md"
|
|
211
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
212
|
+
with open(p, "a") as f:
|
|
213
|
+
f.write(f"\n{content}\n")
|
|
214
|
+
|
|
215
|
+
async def agent_chat(user_input: str) -> str:
|
|
216
|
+
# 1. Recall
|
|
217
|
+
memories = await ms.search(user_input, top_k=3)
|
|
218
|
+
context = "\n".join(f"- {m['content'][:200]}" for m in memories)
|
|
219
|
+
|
|
220
|
+
# 2. Think — call Claude with memory context
|
|
221
|
+
resp = llm.messages.create(
|
|
222
|
+
model="claude-sonnet-4-5-20250929",
|
|
223
|
+
max_tokens=1024,
|
|
224
|
+
system=f"You have these memories:\n{context}",
|
|
225
|
+
messages=[{"role": "user", "content": user_input}],
|
|
226
|
+
)
|
|
227
|
+
answer = resp.content[0].text
|
|
228
|
+
|
|
229
|
+
# 3. Remember
|
|
230
|
+
save_memory(f"## {user_input}\n{answer}")
|
|
231
|
+
await ms.index()
|
|
232
|
+
return answer
|
|
233
|
+
|
|
234
|
+
async def main():
|
|
235
|
+
save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
|
|
236
|
+
await ms.index()
|
|
237
|
+
print(await agent_chat("Who is our frontend lead?"))
|
|
238
|
+
|
|
239
|
+
asyncio.run(main())
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
<details>
|
|
245
|
+
<summary>🦙 <b>Ollama (fully local, no API key)</b> — click to expand</summary>
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
pip install "memsearch[ollama]"
|
|
249
|
+
ollama pull nomic-embed-text # embedding model
|
|
250
|
+
ollama pull llama3.2 # chat model
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
import asyncio
|
|
255
|
+
from datetime import date
|
|
256
|
+
from pathlib import Path
|
|
257
|
+
from ollama import chat
|
|
258
|
+
from memsearch import MemSearch
|
|
259
|
+
|
|
260
|
+
MEMORY_DIR = "./memory"
|
|
261
|
+
ms = MemSearch(paths=[MEMORY_DIR], embedding_provider="ollama")
|
|
262
|
+
|
|
263
|
+
def save_memory(content: str):
|
|
264
|
+
p = Path(MEMORY_DIR) / f"{date.today()}.md"
|
|
265
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
266
|
+
with open(p, "a") as f:
|
|
267
|
+
f.write(f"\n{content}\n")
|
|
268
|
+
|
|
269
|
+
async def agent_chat(user_input: str) -> str:
|
|
270
|
+
# 1. Recall
|
|
271
|
+
memories = await ms.search(user_input, top_k=3)
|
|
272
|
+
context = "\n".join(f"- {m['content'][:200]}" for m in memories)
|
|
273
|
+
|
|
274
|
+
# 2. Think — call Ollama locally
|
|
275
|
+
resp = chat(
|
|
276
|
+
model="llama3.2",
|
|
277
|
+
messages=[
|
|
278
|
+
{"role": "system", "content": f"You have these memories:\n{context}"},
|
|
279
|
+
{"role": "user", "content": user_input},
|
|
280
|
+
],
|
|
281
|
+
)
|
|
282
|
+
answer = resp.message.content
|
|
283
|
+
|
|
284
|
+
# 3. Remember
|
|
285
|
+
save_memory(f"## {user_input}\n{answer}")
|
|
286
|
+
await ms.index()
|
|
287
|
+
return answer
|
|
288
|
+
|
|
289
|
+
async def main():
|
|
290
|
+
save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
|
|
291
|
+
await ms.index()
|
|
292
|
+
print(await agent_chat("Who is our frontend lead?"))
|
|
293
|
+
|
|
294
|
+
asyncio.run(main())
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
</details>
|
|
298
|
+
|
|
299
|
+
### 🗄️ Milvus Backend Configuration
|
|
300
|
+
|
|
301
|
+
memsearch supports three Milvus deployment modes — just change `milvus_uri` and `milvus_token`:
|
|
302
|
+
|
|
303
|
+
#### 1. Milvus Lite (default — zero config, local file)
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
ms = MemSearch(
|
|
307
|
+
paths=["./docs/"],
|
|
308
|
+
milvus_uri="~/.memsearch/milvus.db", # local file, no server needed
|
|
309
|
+
)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
No server to install. Data is stored in a single `.db` file. Perfect for personal use, single-agent setups, and development.
|
|
313
|
+
|
|
314
|
+
#### 2. Milvus Server (self-hosted)
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
ms = MemSearch(
|
|
318
|
+
paths=["./docs/"],
|
|
319
|
+
milvus_uri="http://localhost:19530", # your Milvus server
|
|
320
|
+
milvus_token="root:Milvus", # default credentials, change in production
|
|
321
|
+
)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Deploy via Docker (`docker compose`) or Kubernetes. Ideal for multi-agent workloads and team environments where you need a shared, always-on vector store.
|
|
325
|
+
|
|
326
|
+
#### 3. Zilliz Cloud (fully managed)
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
ms = MemSearch(
|
|
330
|
+
paths=["./docs/"],
|
|
331
|
+
milvus_uri="https://in03-xxx.api.gcp-us-west1.zillizcloud.com",
|
|
332
|
+
milvus_token="your-api-key",
|
|
333
|
+
)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Zero-ops, auto-scaling managed service. Get your free cluster at [cloud.zilliz.com](https://cloud.zilliz.com). Great for production deployments and when you don't want to manage infrastructure.
|
|
337
|
+
|
|
338
|
+
## 🖥️ CLI Usage
|
|
339
|
+
|
|
340
|
+
### Index markdown files
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# Index one or more directories / files
|
|
344
|
+
memsearch index ./docs/ ./notes/
|
|
345
|
+
|
|
346
|
+
# Use a different embedding provider
|
|
347
|
+
memsearch index ./docs/ --provider google
|
|
348
|
+
|
|
349
|
+
# Force re-index everything
|
|
350
|
+
memsearch index ./docs/ --force
|
|
351
|
+
|
|
352
|
+
# Use a remote Milvus server
|
|
353
|
+
memsearch index ./docs/ --milvus-uri http://localhost:19530 --milvus-token root:Milvus
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Search
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
memsearch search "how to configure Redis caching"
|
|
360
|
+
|
|
361
|
+
# Return more results
|
|
362
|
+
memsearch search "authentication flow" --top-k 10
|
|
363
|
+
|
|
364
|
+
# JSON output (for piping to other tools)
|
|
365
|
+
memsearch search "error handling" --json-output
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Watch for changes
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# Auto-index on file changes (Ctrl+C to stop)
|
|
372
|
+
memsearch watch ./docs/ ./notes/
|
|
373
|
+
|
|
374
|
+
# Custom debounce interval
|
|
375
|
+
memsearch watch ./docs/ --debounce-ms 3000
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Flush (compress memories)
|
|
379
|
+
|
|
380
|
+
Summarize indexed chunks into a condensed memory using an LLM:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
memsearch flush
|
|
384
|
+
|
|
385
|
+
# Use a specific LLM
|
|
386
|
+
memsearch flush --llm-provider anthropic
|
|
387
|
+
memsearch flush --llm-provider gemini
|
|
388
|
+
|
|
389
|
+
# Only flush chunks from a specific source
|
|
390
|
+
memsearch flush --source ./docs/old-notes.md
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Configuration management
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
memsearch config init # Interactive wizard
|
|
397
|
+
memsearch config set milvus.uri http://localhost:19530
|
|
398
|
+
memsearch config get milvus.uri
|
|
399
|
+
memsearch config list --resolved # Show merged config from all sources
|
|
400
|
+
memsearch config list --global # Show ~/.memsearch/config.toml only
|
|
401
|
+
memsearch config list --project # Show .memsearch.toml only
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Manage
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
memsearch stats # Show index statistics
|
|
408
|
+
memsearch reset # Drop all indexed data (with confirmation)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## ⚙️ Configuration
|
|
412
|
+
|
|
413
|
+
memsearch uses a layered configuration system. Settings are resolved in priority order (lowest → highest):
|
|
414
|
+
|
|
415
|
+
1. **Built-in defaults**
|
|
416
|
+
2. **Global config** — `~/.memsearch/config.toml`
|
|
417
|
+
3. **Project config** — `.memsearch.toml` (in your working directory)
|
|
418
|
+
4. **Environment variables** — `MEMSEARCH_SECTION_FIELD` (e.g. `MEMSEARCH_MILVUS_URI`)
|
|
419
|
+
5. **CLI flags** — `--milvus-uri`, `--provider`, etc.
|
|
420
|
+
|
|
421
|
+
### API keys
|
|
422
|
+
|
|
423
|
+
API keys for embedding and LLM providers are read from standard environment variables:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Embedding providers (set the one you use)
|
|
427
|
+
export OPENAI_API_KEY="sk-..."
|
|
428
|
+
export OPENAI_BASE_URL="https://..." # optional, for proxies / Azure
|
|
429
|
+
export GOOGLE_API_KEY="..."
|
|
430
|
+
export VOYAGE_API_KEY="..."
|
|
431
|
+
|
|
432
|
+
# LLM for flush/summarization (set the one you use)
|
|
433
|
+
export ANTHROPIC_API_KEY="..." # for flush with Anthropic
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## 🔌 Embedding Providers
|
|
437
|
+
|
|
438
|
+
| Provider | Install | Env Var | Default Model |
|
|
439
|
+
|----------|---------|---------|---------------|
|
|
440
|
+
| OpenAI | `memsearch` (included) | `OPENAI_API_KEY` | `text-embedding-3-small` |
|
|
441
|
+
| Google | `memsearch[google]` | `GOOGLE_API_KEY` | `text-embedding-004` |
|
|
442
|
+
| Voyage | `memsearch[voyage]` | `VOYAGE_API_KEY` | `voyage-3-lite` |
|
|
443
|
+
| Ollama | `memsearch[ollama]` | `OLLAMA_HOST` (optional) | `nomic-embed-text` |
|
|
444
|
+
| Local | `memsearch[local]` | — | `all-MiniLM-L6-v2` |
|
|
445
|
+
|
|
446
|
+
## 🐾 OpenClaw Compatibility
|
|
447
|
+
|
|
448
|
+
memsearch is designed to be a drop-in memory backend for projects following [OpenClaw's memory architecture](https://github.com/openclaw/openclaw):
|
|
449
|
+
|
|
450
|
+
| Feature | OpenClaw | memsearch |
|
|
451
|
+
|---------|----------|-----------|
|
|
452
|
+
| Memory layout | `MEMORY.md` + `memory/YYYY-MM-DD.md` | ✅ Same |
|
|
453
|
+
| Chunk ID format | `hash(source:startLine:endLine:contentHash:model)` | ✅ Same |
|
|
454
|
+
| Dedup strategy | Content-hash primary key | ✅ Same |
|
|
455
|
+
| Flush target | Append to daily markdown log | ✅ Same |
|
|
456
|
+
| Source of truth | Markdown files (vector DB is derived) | ✅ Same |
|
|
457
|
+
| File watch debounce | 1500ms | ✅ Same default |
|
|
458
|
+
| Vector backend | Built-in | Milvus (Lite / Server / Zilliz Cloud) |
|
|
459
|
+
| Embedding providers | Built-in | Pluggable (OpenAI, Google, Voyage, Ollama, local) |
|
|
460
|
+
| Packaging | Part of OpenClaw monorepo | Standalone `pip install` |
|
|
461
|
+
|
|
462
|
+
If you're already using OpenClaw's memory directory layout, just point memsearch at it — no migration needed.
|
|
463
|
+
|
|
464
|
+
## 📄 License
|
|
465
|
+
|
|
466
|
+
MIT
|