Skip to main content

Semantic memory search for markdown knowledge bases

Project description

ย  memsearch

OpenClaw's memory, everywhere.

PyPI Python License Docs Stars Milvus Discord X (Twitter)

https://github.com/user-attachments/assets/31de76cc-81a8-4462-a47d-bd9c394d33e3

๐Ÿ’ก memsearch extracts 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 (local Milvus Lite โ†’ Milvus Server โ†’ Zilliz Cloud). See it in action with the included Claude Code plugin.

โœจ Why memsearch?

  • ๐Ÿฆž 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
  • โšก Smart dedup โ€” SHA-256 content hashing means unchanged content is never re-embedded
  • ๐Ÿ”„ Live sync โ€” File watcher auto-indexes on changes, deletes stale chunks when files are removed
  • ๐Ÿงน Memory compact โ€” LLM-powered summarization compresses old memories, just like OpenClaw's compact cycle
  • ๐Ÿงฉ Claude Code plugin included โ€” A real-world example: ccplugin/ gives Claude persistent memory across sessions with zero config

๐Ÿ” How It Works

Markdown is the source of truth โ€” the vector store is just a derived index, rebuildable anytime.

  โ”Œโ”€โ”€โ”€ Search โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚                                                                    โ”‚
  โ”‚  "how to configure Redis?"                                         โ”‚
  โ”‚        โ”‚                                                           โ”‚
  โ”‚        โ–ผ                                                           โ”‚
  โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
  โ”‚   โ”‚  Embed   โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Cosine similarityโ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Top-K results    โ”‚   โ”‚
  โ”‚   โ”‚  query   โ”‚     โ”‚ (Milvus)        โ”‚     โ”‚ with source info โ”‚   โ”‚
  โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
  โ”‚                                                                    โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

  โ”Œโ”€โ”€โ”€ Ingest โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚                                                                    โ”‚
  โ”‚  MEMORY.md                                                         โ”‚
  โ”‚  memory/2026-02-09.md     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
  โ”‚  memory/2026-02-08.md โ”€โ”€โ”€โ–ถโ”‚ Chunker  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Dedup          โ”‚     โ”‚
  โ”‚                           โ”‚(heading, โ”‚     โ”‚(chunk_hash PK) โ”‚     โ”‚
  โ”‚                           โ”‚paragraph)โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
  โ”‚                           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜             โ”‚              โ”‚
  โ”‚                                             new chunks only       โ”‚
  โ”‚                                                    โ–ผ              โ”‚
  โ”‚                                            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
  โ”‚                                            โ”‚  Embed &     โ”‚       โ”‚
  โ”‚                                            โ”‚  Milvus upsertโ”‚      โ”‚
  โ”‚                                            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
  โ”‚                                                                    โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

  โ”Œโ”€โ”€โ”€ Watch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  File watcher (1500ms debounce) โ”€โ”€โ–ถ auto re-index / delete stale  โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

  โ”Œโ”€โ”€โ”€ Compact โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  Retrieve chunks โ”€โ”€โ–ถ LLM summarize โ”€โ”€โ–ถ write memory/YYYY-MM-DD.md โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”’ 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.

๐Ÿงฉ Claude Code Plugin

memsearch ships with a Claude Code plugin โ€” 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.

# 1. Install the memsearch CLI
pip install memsearch

# 2. In Claude Code, add the marketplace and install the plugin
/plugin marketplace add zilliztech/memsearch
/plugin install memsearch
๐Ÿ”ง Development mode โ€” install from local clone
git clone https://github.com/zilliztech/memsearch.git
pip install memsearch
claude --plugin-dir ./memsearch/ccplugin
  Session start โ”€โ”€โ–ถ start memsearch watch (singleton) โ”€โ”€โ–ถ inject recent memories
                           โ”‚
  User prompt โ”€โ”€โ–ถ memsearch search โ”€โ”€โ–ถ inject relevant memories
                           โ”‚
  Claude stops โ”€โ”€โ–ถ haiku summary โ”€โ”€โ–ถ write .memsearch/memory/YYYY-MM-DD.md
                           โ”‚                                โ”‚
  Session end โ”€โ”€โ–ถ stop watch              watch auto-indexes โ—€โ”˜

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 for the full architecture, hook details, progressive disclosure model, and comparison with claude-mem.

๐Ÿ“ฆ Installation

pip install memsearch

Additional embedding providers

pip install "memsearch[google]"      # Google Gemini
pip install "memsearch[voyage]"      # Voyage AI
pip install "memsearch[ollama]"      # Ollama (local)
pip install "memsearch[local]"       # sentence-transformers (local, no API key)
pip install "memsearch[all]"         # Everything

๐Ÿ Python API โ€” Build an Agent with Memory

The example below shows a complete agent loop with memory: save knowledge to markdown, index it, and recall it later via semantic search.

import asyncio
from datetime import date
from pathlib import Path
from openai import OpenAI
from memsearch import MemSearch

MEMORY_DIR = "./memory"
llm = OpenAI()                                        # your LLM client
ms = MemSearch(paths=[MEMORY_DIR])                    # memsearch handles the rest

def save_memory(content: str):
    """Append a note to today's memory log (OpenClaw-style daily markdown)."""
    p = Path(MEMORY_DIR) / f"{date.today()}.md"
    p.parent.mkdir(parents=True, exist_ok=True)
    with open(p, "a") as f:
        f.write(f"\n{content}\n")

async def agent_chat(user_input: str) -> str:
    # 1. Recall โ€” search past memories for relevant context
    memories = await ms.search(user_input, top_k=3)
    context = "\n".join(f"- {m['content'][:200]}" for m in memories)

    # 2. Think โ€” call LLM with memory context
    resp = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": f"You have these memories:\n{context}"},
            {"role": "user", "content": user_input},
        ],
    )
    answer = resp.choices[0].message.content

    # 3. Remember โ€” save this exchange and index it
    save_memory(f"## {user_input}\n{answer}")
    await ms.index()

    return answer

async def main():
    # Seed some knowledge
    save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
    save_memory("## Decision\nWe chose Redis for caching over Memcached.")
    await ms.index()

    # Agent can now recall those memories
    print(await agent_chat("Who is our frontend lead?"))
    print(await agent_chat("What caching solution did we pick?"))

asyncio.run(main())
๐Ÿ’œ Anthropic Claude example โ€” click to expand
pip install memsearch anthropic
import asyncio
from datetime import date
from pathlib import Path
from anthropic import Anthropic
from memsearch import MemSearch

MEMORY_DIR = "./memory"
llm = Anthropic()
ms = MemSearch(paths=[MEMORY_DIR])

def save_memory(content: str):
    p = Path(MEMORY_DIR) / f"{date.today()}.md"
    p.parent.mkdir(parents=True, exist_ok=True)
    with open(p, "a") as f:
        f.write(f"\n{content}\n")

async def agent_chat(user_input: str) -> str:
    # 1. Recall
    memories = await ms.search(user_input, top_k=3)
    context = "\n".join(f"- {m['content'][:200]}" for m in memories)

    # 2. Think โ€” call Claude with memory context
    resp = llm.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=1024,
        system=f"You have these memories:\n{context}",
        messages=[{"role": "user", "content": user_input}],
    )
    answer = resp.content[0].text

    # 3. Remember
    save_memory(f"## {user_input}\n{answer}")
    await ms.index()
    return answer

async def main():
    save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
    await ms.index()
    print(await agent_chat("Who is our frontend lead?"))

asyncio.run(main())
๐Ÿฆ™ Ollama (fully local, no API key) โ€” click to expand
pip install "memsearch[ollama]"
ollama pull nomic-embed-text          # embedding model
ollama pull llama3.2                  # chat model
import asyncio
from datetime import date
from pathlib import Path
from ollama import chat
from memsearch import MemSearch

MEMORY_DIR = "./memory"
ms = MemSearch(paths=[MEMORY_DIR], embedding_provider="ollama")

def save_memory(content: str):
    p = Path(MEMORY_DIR) / f"{date.today()}.md"
    p.parent.mkdir(parents=True, exist_ok=True)
    with open(p, "a") as f:
        f.write(f"\n{content}\n")

async def agent_chat(user_input: str) -> str:
    # 1. Recall
    memories = await ms.search(user_input, top_k=3)
    context = "\n".join(f"- {m['content'][:200]}" for m in memories)

    # 2. Think โ€” call Ollama locally
    resp = chat(
        model="llama3.2",
        messages=[
            {"role": "system", "content": f"You have these memories:\n{context}"},
            {"role": "user", "content": user_input},
        ],
    )
    answer = resp.message.content

    # 3. Remember
    save_memory(f"## {user_input}\n{answer}")
    await ms.index()
    return answer

async def main():
    save_memory("## Team\n- Alice: frontend lead\n- Bob: backend lead")
    await ms.index()
    print(await agent_chat("Who is our frontend lead?"))

asyncio.run(main())

๐Ÿ—„๏ธ Milvus Backend Configuration

memsearch supports three Milvus deployment modes โ€” just change milvus_uri and milvus_token:

1. Milvus Lite (default โ€” zero config, local file)

ms = MemSearch(
    paths=["./docs/"],
    milvus_uri="~/.memsearch/milvus.db",    # local file, no server needed
)

No server to install. Data is stored in a single .db file. Perfect for personal use, single-agent setups, and development.

2. Milvus Server (self-hosted)

ms = MemSearch(
    paths=["./docs/"],
    milvus_uri="http://localhost:19530",     # your Milvus server
    milvus_token="root:Milvus",              # default credentials, change in production
)

Deploy via Docker (docker compose) or Kubernetes. Ideal for multi-agent workloads and team environments where you need a shared, always-on vector store.

3. Zilliz Cloud (fully managed)

ms = MemSearch(
    paths=["./docs/"],
    milvus_uri="https://in03-xxx.api.gcp-us-west1.zillizcloud.com",
    milvus_token="your-api-key",
)

Zero-ops, auto-scaling managed service. Get your free cluster at cloud.zilliz.com. Great for production deployments and when you don't want to manage infrastructure.

๐Ÿ–ฅ๏ธ CLI Usage

Index markdown files

# Index one or more directories / files
memsearch index ./docs/ ./notes/

# Use a different embedding provider
memsearch index ./docs/ --provider google

# Force re-index everything
memsearch index ./docs/ --force

# Use a remote Milvus server
memsearch index ./docs/ --milvus-uri http://localhost:19530 --milvus-token root:Milvus

Search

memsearch search "how to configure Redis caching"

# Return more results
memsearch search "authentication flow" --top-k 10

# JSON output (for piping to other tools)
memsearch search "error handling" --json-output

Watch for changes

# Auto-index on file changes (Ctrl+C to stop)
memsearch watch ./docs/ ./notes/

# Custom debounce interval
memsearch watch ./docs/ --debounce-ms 3000

Compact (compress memories)

Summarize indexed chunks into a condensed memory using an LLM:

memsearch compact

# Use a specific LLM
memsearch compact --llm-provider anthropic
memsearch compact --llm-provider gemini

# Only compact chunks from a specific source
memsearch compact --source ./docs/old-notes.md

Configuration management

memsearch config init               # Interactive wizard
memsearch config set milvus.uri http://localhost:19530
memsearch config get milvus.uri
memsearch config list --resolved    # Show merged config from all sources
memsearch config list --global      # Show ~/.memsearch/config.toml only
memsearch config list --project     # Show .memsearch.toml only

Manage

memsearch stats    # Show index statistics
memsearch reset    # Drop all indexed data (with confirmation)

โš™๏ธ Configuration

memsearch uses a layered configuration system. Settings are resolved in priority order (lowest โ†’ highest):

  1. Built-in defaults
  2. Global config โ€” ~/.memsearch/config.toml
  3. Project config โ€” .memsearch.toml (in your working directory)
  4. Environment variables โ€” MEMSEARCH_SECTION_FIELD (e.g. MEMSEARCH_MILVUS_URI)
  5. CLI flags โ€” --milvus-uri, --provider, etc.

API keys

API keys for embedding and LLM providers are read from standard environment variables:

# Embedding providers (set the one you use)
export OPENAI_API_KEY="sk-..."
export OPENAI_BASE_URL="https://..."   # optional, for proxies / Azure
export GOOGLE_API_KEY="..."
export VOYAGE_API_KEY="..."

# LLM for compact/summarization (set the one you use)
export ANTHROPIC_API_KEY="..."         # for compact with Anthropic

๐Ÿ”Œ Embedding Providers

Provider Install Env Var Default Model
OpenAI memsearch (included) OPENAI_API_KEY text-embedding-3-small
Google memsearch[google] GOOGLE_API_KEY gemini-embedding-001
Voyage memsearch[voyage] VOYAGE_API_KEY voyage-3-lite
Ollama memsearch[ollama] OLLAMA_HOST (optional) nomic-embed-text
Local memsearch[local] โ€” all-MiniLM-L6-v2

๐Ÿพ OpenClaw Compatibility

memsearch is designed to be a drop-in memory backend for projects following OpenClaw's memory architecture:

Feature OpenClaw memsearch
Memory layout MEMORY.md + memory/YYYY-MM-DD.md โœ… Same
Chunk ID format hash(source:startLine:endLine:contentHash:model) โœ… Same
Dedup strategy Content-hash primary key โœ… Same
Compact target Append to daily markdown log โœ… Same
Source of truth Markdown files (vector DB is derived) โœ… Same
File watch debounce 1500ms โœ… Same default
Vector backend Built-in Milvus (Lite / Server / Zilliz Cloud)
Embedding providers Built-in Pluggable (OpenAI, Google, Voyage, Ollama, local)
Packaging Part of OpenClaw monorepo Standalone pip install

If you're already using OpenClaw's memory directory layout, just point memsearch at it โ€” no migration needed.

๐Ÿ“„ License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

memsearch-0.1.4.tar.gz (2.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

memsearch-0.1.4-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

Details for the file memsearch-0.1.4.tar.gz.

File metadata

  • Download URL: memsearch-0.1.4.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for memsearch-0.1.4.tar.gz
Algorithm Hash digest
SHA256 64f7800899df60e21bbc987f50804318e2a5a8d03f19987f8b49c471336a2b14
MD5 011dde1d0d5e573231d2f24e5ca40efe
BLAKE2b-256 1d1e1a0575c8bbdc36067daa4aecaa057051d61467e95323bdb3e6c6d096a709

See more details on using hashes here.

Provenance

The following attestation bundles were made for memsearch-0.1.4.tar.gz:

Publisher: release.yml on zilliztech/memsearch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file memsearch-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: memsearch-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 35.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for memsearch-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 30fb1a2caf75df10bf738b79a3ff472ad0e0a2ae8598898e9ee1c4da9f10f14d
MD5 3c62f3ac3b95ee870d78f2ca6f71e523
BLAKE2b-256 13f39768de22ea8595c70ded81a5e17091423cf0381aa3c4592977df41cb61f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for memsearch-0.1.4-py3-none-any.whl:

Publisher: release.yml on zilliztech/memsearch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page