Skip to main content

NeatMem

Lightweight local memory for agents, with cleaner deduplication, less memory pollution, and more relevant recall.

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.

Status: v0.1-preview. NeatMem is usable for local development and mem0-compatible integrations, but APIs, packaging, and integrations may still change.

Benchmark: 90.80% accuracy on LOCOMO, fully reproducible locally (3-run mean; MiniMax-M3 answer + judge, SiliconFlow bge-m3 embedding). See the evaluation guide for benchmark reproduction steps.

Why NeatMem?

Agent memory is easy to start but hard to keep clean.

Common problems include:

  • duplicate memories accumulating over time
  • assistant suggestions being stored as user facts
  • vague memories losing their original context
  • semantically related memories not being merged
  • irrelevant memories being recalled because of weak vector matches
  • local agent tools needing a simple self-hosted memory backend

NeatMem focuses on one narrow goal:

Local agent memory that stays clean, inspectable, and easy to tune.

It is not a full Memory OS and not an enterprise multi-tenant memory system.

Features

  • LLM-assisted memory decisions

    • Classifies each new memory as add, none, or update (listwise, single LLM call).
    • DEDUP_MODE controls behavior: skip (keep both), replace (overwrite), rewrite (LLM merge), edit (LLM patch).
  • Sequential memory updates

    • Processes new memories one by one so each merge sees the latest stored version.
    • Helps avoid overwrite conflicts when several new facts update the same old memory.
  • Less memory pollution

    • Avoids saving AI suggestions, guesses, or tool noise as user facts.
    • Tracks whether each memory came from the user, assistant, or tool output.
  • Memories with enough context

    • Adds missing context from the same message batch when needed.
    • Example: “during development” can become “while developing a mem0-based memory module”.
  • More relevant recall

    • Multi-signal retrieval: dense vector search + BM25 sparse matching + entity boosting.
    • LLM listwise rerank filters and reorders candidates before injection into agent context.
  • Lightweight local storage

    • Runs with local Qdrant (embedded or server mode) by default.
    • Does not require Redis, a hosted memory service, or a full database stack.
  • Modular signal architecture

    • Message store, BM25, and entity modules are decoupled under neatmem/storage/ and neatmem/signals/.
    • Each signal can be toggled via environment variables (ENABLE_BM25, ENABLE_ENTITY, ENABLE_GRAPH).
  • Optional graph memory (opt-in)

    • Entity-relation storage via KuzuDB, toggled by ENABLE_GRAPH.
    • Off by default; graph relations injection into answer prompt is experimental (GRAPH_INJECT_RELATIONS, known harmful on LOCOMO).
  • OpenClaw and mem0-style integration

    • Implements the core mem0-style memory endpoints needed for local agent workflows.
    • Designed to support OpenClaw platform-mode memory integration.

Compatibility

NeatMem implements a mem0-compatible API subset for local agent memory workflows:

  • add memory
  • search memory
  • list memories
  • update memory
  • delete memory
  • health check

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.

A remote client is provided for programmatic access:

from neatmem import MemoryClient

client = MemoryClient(host="http://localhost:8790")
client.add("My name is Alex", user_id="default_user")
results = client.search("What is my name?", filters={"user_id": "default_user"})

Quick start

1. Install

pip install -r requirements.txt
pip install -e .

The second command registers the neatmem CLI.

Optional:

  • BM25 keyword search (enabled by default) needs spaCy:
    pip install -e ".[nlp]" && python -m spacy download en_core_web_sm
    
  • Local reranker model (alternative to LLM rerank):
    pip install -e ".[local-reranker]"
    

2. Configure environment variables

cp .env.example .env

Edit .env and configure your LLM and embedding provider.

Minimum configuration for OpenAI-compatible LLM providers:

OPENAI_API_KEY=your-api-key
OPENAI_BASE_URL=https://your-openai-compatible-endpoint/v1
LLM_MODEL=qwen-max-latest

EMBEDDING_PROVIDER=siliconflow
SILICONFLOW_API_KEY=your-siliconflow-api-key

3. Start the server

neatmem serve

The server listens on:

http://localhost:8790

To use a different port:

neatmem serve --port 9000

View all options:

neatmem serve --help

CLI flags override .env environment variables; see the Configuration table below for the full list.

Alternatively, start directly with Python:

python -m neatmem.main

Check health:

curl http://localhost:8790/health

Expected response:

{"status":"healthy","timestamp":"..."}

Configuration

NeatMem reads configuration from .env.

Variable Required Default Description
NEATMEM_HOST no 0.0.0.0 Server bind host
NEATMEM_PORT no 8790 Server port
NEATMEM_URL no http://localhost:8790 Base URL used by MemoryClient
NEATMEM_API_KEY no - API key sent as Authorization: Token header by MemoryClient (server ignores it)
OPENAI_API_KEY yes - API key for OpenAI-compatible LLM provider
OPENAI_BASE_URL yes - OpenAI-compatible API base URL
LLM_MODEL no qwen-max-latest LLM model name
EMBEDDING_PROVIDER no siliconflow siliconflow or xinference
SILICONFLOW_API_KEY conditional - Required when EMBEDDING_PROVIDER=siliconflow
EMBEDDING_MODEL no BAAI/bge-m3 Embedding model name
EMBEDDING_BASE_URL no https://api.siliconflow.cn/v1 Embedding API base URL
EMBEDDING_DIMS no auto-detect Embedding dimensions. When unset, auto-detected from a startup probe; set explicitly to enforce a dimension check at boot
XINFERENCE_SERVER_URL conditional http://localhost:9997 Required when using Xinference
XINFERENCE_MODEL_UID conditional bge-m3 Xinference embedding model UID
QDRANT_PATH no qdrant_db Local Qdrant storage path (embedded mode)
QDRANT_HOST no - Qdrant server host (sets server mode; overrides QDRANT_PATH)
QDRANT_PORT no 6333 Qdrant server port
DEDUP_MODE no skip Dedup behavior: off, skip, replace, rewrite, edit
ENABLE_BM25 no true Enable BM25 sparse search signal
ENABLE_ENTITY no false Enable entity extraction and boosting
ENABLE_GRAPH no false Enable graph memory (KuzuDB entity-relation storage). Graph hooks are no-op when disabled
KUZU_DB_PATH conditional - KuzuDB database file path. Required when ENABLE_GRAPH=true
GRAPH_THRESHOLD no 0.7 Entity match threshold for graph retrieval
GRAPH_SEARCH_TOP_K no 5 Max relations returned per speaker from graph search
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
GRAPH_EMBEDDING_MODEL no BAAI/bge-m3 Embedding model for graph entities (defaults to main embedding model)
GRAPH_EMBEDDING_DIMS no 1024 Embedding dimensions for graph entities
GRAPH_EMBEDDING_BASE_URL no https://api.siliconflow.cn/v1 Embedding API base URL for graph entities
GRAPH_EMBEDDING_API_KEY no - Embedding API key for graph entities. Defaults to SILICONFLOW_API_KEY
LLM_RERANK no true Enable LLM listwise rerank for recall
RERANK_MODE no llm_listwise Rerank strategy
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
RERANK_MAX_CONCURRENT no 4 Max concurrent LLM rerank calls (protects against API rate limits)
MERGE_STRATEGY no off Deprecated; use DEDUP_MODE instead
DEDUP_THINKING no false Enable LLM thinking for dedup
EDIT_THINKING no false Enable LLM thinking for edit mode (DEDUP_MODE=edit)
HISTORY_DB_PATH no {QDRANT_PATH}/history.db SQLite message history database path
EXTRACT_LAST_K_MESSAGES no 10 Number of recent messages fed to extraction as context
MESSAGE_STORE_BACKEND no sqlite Message store backend: sqlite or none
ENTITY_EXTRACTOR_BACKEND no ner Entity extractor: ner or llm
ENTITY_STORE_BACKEND no qdrant Entity store backend
RERANKER_MODEL_PATH no - Optional local Sentence-Transformers reranker
RERANKER_DEVICE no cpu Reranker device
RERANKER_BATCH_SIZE no 32 Reranker batch size
RERANKER_TOP_K no 5 Reranker top-k
HF_ENDPOINT no https://hf-mirror.com HuggingFace mirror endpoint

Custom prompts

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.

Prompt Env var / CLI flag Built-in ids Used when
Fact extraction EXTRACTION_PROMPT / --extraction-prompt - always (write path)
Dedup decision DEDUP_PROMPT / --dedup-prompt zh (default), en DEDUP_MODE=skip/replace/rewrite/edit
Merge rewrite REWRITE_PROMPT / --rewrite-prompt - DEDUP_MODE=rewrite
Patch edit EDIT_PROMPT / --edit-prompt - DEDUP_MODE=edit
Rerank RERANK_PROMPT / --rerank-prompt - LLM listwise rerank

Switch to the English dedup prompt (validated on LOCOMO, 2026-07-24):

neatmem serve --dedup-prompt en

Use your own prompt:

# 1. Export the example templates (they are the exact built-in defaults)
#    From a git clone:
mkdir -p my_prompts && cp neatmem/prompts/examples/*.txt my_prompts/
#    From a pip install:
python - <<'EOF'
from importlib.resources import files
import shutil, os
os.makedirs("my_prompts", exist_ok=True)
for f in files("neatmem.prompts").joinpath("examples").iterdir():
    shutil.copy(f, "my_prompts/")
EOF

# 2. Edit the one you want (keep every {placeholder} intact,
#    including the {{ }} escaping in JSON examples)

# 3. Point the server at it
neatmem serve --dedup-prompt /absolute/path/to/my_prompts/dedup_zh.example.txt

Notes:

  • Prompts are loaded once at startup; restart the server after editing a file.
  • 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.
  • Prefer absolute paths; relative paths resolve against the server's working directory.

OpenClaw integration

NeatMem includes an OpenClaw plugin under openclaw/. Build it and install it as a linked local plugin during development:

cd /path/to/NeatMem/openclaw
npm install
npm run build

cd /path/to/NeatMem
openclaw plugins install ./openclaw --link

After changing plugin TypeScript source, rebuild before reinstalling or restarting OpenClaw.

The plugin id is openclaw-neatmem. It talks to NeatMem through the local mem0-compatible HTTP API.

Example OpenClaw configuration:

{
  "plugins": {
    "slots": {
      "memory": "openclaw-neatmem"
    },
    "entries": {
      "openclaw-neatmem": {
        "enabled": true,
        "config": {
          "mode": "platform",
          "apiKey": "neatmem-local",
          "userId": "default_user",
          "baseUrl": "http://localhost:8790"
        }
      }
    }
  }
}

Then check:

openclaw mem0 status

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.

Hermes integration

NeatMem includes a Hermes Agent memory provider under hermes/. With the NeatMem server running at http://localhost:8790:

hermes plugins install kanhaoning/NeatMem/hermes --enable
hermes config set memory.provider neatmem

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:

{
  "base_url": "http://localhost:8790",
  "user_id": "myname",
  "rerank": true
}

Verify: tell Hermes "remember that I prefer dark themes", then ask about it in a new session. See hermes/README.md for the full configuration reference and troubleshooting.

API examples

Health check

curl http://localhost:8790/health

Add memory

curl -X POST http://localhost:8790/v1/memories/ \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role": "user", "content": "My name is Alex and I work on agent memory systems."},
      {"role": "assistant", "content": "Nice to meet you, Alex."}
    ],
    "user_id": "default_user",
    "infer": true
  }'

Search memory

curl -X POST http://localhost:8790/v2/memories/search/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What is Alex working on?",
    "filters": {"user_id": "default_user"},
    "top_k": 10,
    "threshold": 0.1
  }'

List memories

curl -X POST http://localhost:8790/v2/memories/ \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {"user_id": "default_user"},
    "page": 1,
    "page_size": 100
  }'

Get memory

curl http://localhost:8790/v1/memories/{memory_id}/

Update memory

curl -X PUT http://localhost:8790/v1/memories/{memory_id}/ \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Alex works on local-first agent memory systems.",
    "metadata": {"source": "manual_update"}
  }'

Delete memory

curl -X DELETE http://localhost:8790/v1/memories/{memory_id}/

How it works

Add flow

messages
  ↓
retrieve last-k messages as extraction context
  ↓
LLM memory extraction (with last-k context)
  ↓
context completion and source tracking
  ↓
sequential LLM-assisted memory decisions
  ├─ add    -> store as new memory
  ├─ none   -> skip (duplicate)
  └─ update -> merge per DEDUP_MODE (skip/replace/rewrite/edit)
  ↓
write to vector store + BM25 index + entity store

Search flow

query
  ↓
dense vector search + BM25 sparse search + entity boosting
  ↓
LLM listwise rerank
  ↓
threshold filtering
  ↓
results

Development probes

Memory quality iteration is done through probe/, which contains OpenClaw end-to-end probes and extraction simulation scripts. It is not a benchmark suite.

Design notes

NeatMem is designed around a few constraints:

  • keep the plugin layer thin
  • keep the backend self-hosted and debuggable
  • do not require Redis or a background scheduler
  • prefer memory quality over feature breadth
  • preserve compatibility with mem0-style APIs where possible

Limitations

NeatMem is in active development. Current limitations:

  • APIs and packaging may still change.
  • No dashboard or GUI.
  • No multi-tenant permission system.
  • OpenClaw is the primary tested integration path.
  • Prompt behavior is still being iterated and may vary across models.
  • BM25 lemmatization is basic; bilingual (Chinese/English) tokenization needs improvement.

Roadmap

  • Bilingual multi-signal support (improved Chinese/English BM25 and entity extraction)
  • PyPI package publication
  • Memory inspection and export/import tools
  • Richer recall diagnostics

License

MIT License.

Acknowledgements

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.

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.

Download files

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

Source Distribution

neatmem-0.1.0.tar.gz (776.5 kB view details)

Uploaded Source

Built Distribution

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

neatmem-0.1.0-py3-none-any.whl (799.0 kB view details)

Uploaded Python 3

File details

Details for the file neatmem-0.1.0.tar.gz.

File metadata

  • Download URL: neatmem-0.1.0.tar.gz
  • Upload date:
  • Size: 776.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for neatmem-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ed9fb73ce27a11c6c87fdae8420d40e3be00b0884d4205a1db1b5279d1d94f54
MD5 e2457b68f695514792080eb9904f1e3b
BLAKE2b-256 af9f34edbf9fe35fd748e902630834a2914010a5b7d2816fcbe44d645e527715

See more details on using hashes here.

File details

Details for the file neatmem-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: neatmem-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 799.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for neatmem-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 65d30e813f562eb1c1aed89f9623436240ba80af0274e476cfa3d08bfd653487
MD5 a7cdd36cdc152f8ad046cff96b7db167
BLAKE2b-256 bf3136569dd0accf2b3ca956bad74ec62217110ecbc5cf5a4da73a157bd0f0e3

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.0

2 files

0.1.1

2 files

This release

0.1.0 This release

2 files

Supported by

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