Long-term memory for OpenClaw AI agents. Named after the Noldor — Tolkien's elves renowned for deep knowledge and craft.
NoldoMem provides per-agent long-term storage, hybrid retrieval and evidence-aware memory through OpenClaw and Hermes adapters. Recall depends on capture coverage, the configured embedding model and the host's injection path.
Choose a memory authority for your workload. OpenClaw's stable native memory is
not keyword-only: it includes hybrid search, recency weighting, consolidation,
provenance and conditional multimodal indexing. Hermes offers bounded, persistent
MEMORY.md / USER.md context plus separate session and procedural tools.
| Capability | Host native memory | NoldoMem |
|---|---|---|
| Small durable preference set | Direct startup context; no retrieval round trip needed | Selective API recall, with an extra service call |
| Larger episodic history | Host-specific session search and indexing | Per-agent SQLite, FTS and vector search |
| Updates and history | Host-specific file edits, transcript/provenance tools | Explicit revision IDs, validity intervals and historical recall |
| Media | Depends on host/model and available extraction | Existing text derivatives with provenance; no built-in OCR/ASR |
| Isolation | Requires appropriate host/profile and session permissions | Separate agent DBs, trusted adapter scope and scoped API credentials |
| Forgetting | Host-specific scope; external copies need separate handling | Deletes the revision family and indexes; known source sessions are blocked from replay until explicit relearning, with legacy limits |
The dated platform comparison and synthetic measurements explain the tested versions, native advantages, limitations and integration status. Start with the current capability and evidence guide for observed behavior and the exact boundaries of each test. The guide links the historical runs without treating an earlier failed attempt as the current result.
The tested integrations run on released Hermes/OpenClaw hosts without the proposed upstream metadata changes. Those proposals improve audio-clip origin and outbound delivery correlation; they are not installation prerequisites. NoldoMem can retain content while its exact clip origin or delivery status is unknown. It does not infer missing metadata or claim an unsent draft was delivered.
Python consumers of the Turkish morphology helpers should read the 2.0.0 helper migration. The HTTP search path no longer requires Zeyrek/NLTK; lexical normalization is not lemmatization. Neither system has proven universal superiority. Avoid two independent writers for the same durable fact unless update and deletion propagation are implemented.
NoldoMem supports both OpenClaw and Hermes Agent:
- OpenClaw: Continue with the OpenClaw Quick Start below.
- Hermes Agent: Start with the Hermes integration guide for architecture and runtime guidance, then use the Hermes adapter README for adapter installation and configuration.
Requirements: Python 3.10+, ~200MB RAM for the API server (embedding server needs more — see Step 2).
git clone https://github.com/dorukardahan/noldo-memory.git
cd noldo-memory
python3 -m venv .venv
source .venv/bin/activate
python -m pip install .
# Smoke check: verify the command and package work outside the checkout
python -m pip show noldo-memory
command -v agent-memory
(cd /tmp && python -c "import agent_memory; print(agent_memory.__file__)")
# Create .env from the template without overwriting an existing file
test -e .env || cp .env.example .env
# Edit .env: set AGENT_MEMORY_API_KEY (pick any strong secret)NoldoMem needs an embedding API (OpenAI-compatible /v1/embeddings format).
Fastest path — use a cloud API (add these settings to .env):
# In .env:
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_API_KEY=your-key
AGENT_MEMORY_MODEL=openai/text-embedding-3-large
AGENT_MEMORY_DIMENSIONS=3072Works with OpenRouter, OpenAI, or any OpenAI-compatible embedding API.
Or run locally (recommended for privacy/cost):
./scripts/detect-hardware.sh --apply # auto-detect best model for your hardware| Profile | Model | Download Size | RAM | Best For |
|---|---|---|---|---|
| minimal | EmbeddingGemma 300M | ~300MB | 1-2GB | Raspberry Pi, $5 VPS |
| light | Qwen3-Embedding-0.6B | ~600MB | 2-4GB | Small VPS |
| standard | Qwen3-Embedding-4B | ~4GB | 4-8GB | Mid-range server |
| heavy | Qwen3-Embedding-8B | ~8GB | 12GB+ | Dedicated server |
# Download model (example: light/default profile)
huggingface-cli download Qwen/Qwen3-Embedding-0.6B-GGUF Qwen3-Embedding-0.6B-Q8_0.gguf --local-dir models/
# Start embedding server
llama-server --model models/Qwen3-Embedding-0.6B-Q8_0.gguf \
--embedding --pooling last --host 127.0.0.1 --port 8090
# In .env:
# OPENROUTER_BASE_URL=http://127.0.0.1:8090/v1
# AGENT_MEMORY_DIMENSIONS=1024Optional hosted reranker for better top-k ordering without loading a local cross-encoder model:
# In .env:
AGENT_MEMORY_RERANKER_API_ENABLED=true
AGENT_MEMORY_RERANKER_API_MODEL=cohere/rerank-4-pro
AGENT_MEMORY_RERANKER_API_KEY_FILE=$HOME/.openrouter_key
# Optional. Disabled by default because local fallback can be too slow for
# interactive recall on CPU-only hosts.
AGENT_MEMORY_RERANKER_API_LOCAL_FALLBACK=falseWhen the hosted reranker is available, NoldoMem skips local reranker prewarm and
two-pass background reranking. If the key or endpoint is missing, it tries the
optional local cross-encoder path at startup when sentence-transformers is
installed. If the hosted call fails at runtime, NoldoMem uses fast lexical fallback unless
AGENT_MEMORY_RERANKER_API_LOCAL_FALLBACK=true is set. Cross-agent
agent=all recall reranks once after merging agent results instead of calling
the hosted reranker once per agent database.
set -a; source .env; set +a
python -m agent_memory
# API starts on http://127.0.0.1:8787
# Data stored in: ~/.agent-memory/ (or ~/.noldomem/ or legacy ~/.asuman/)
# Each agent gets its own SQLite file: memory.sqlite, memory-agent1.sqlite, etc.4a. Select the memory authority for an isolated profile.
For NoldoMem-owned durable facts, disable the competing native long-term writer,
promotion and automatic injection paths using the settings supported by your
OpenClaw version. A legacy memorySearch.enabled: false setting alone is not a
complete authority switch on current stable hosts. Keep session/transcript tools
and procedural skills when needed, with same-agent visibility. See the
stable-host checklist.
The native-only option needs no NoldoMem plugin. For a NoldoMem profile with an explicit tool allowlist, enable its tools as shown below. Do not remove unrelated session tools just because their names contain “memory”.
{
"tools": {
"alsoAllow": [
"noldomem_recall",
"noldomem_store",
"noldomem_pin",
"noldomem_forget",
"noldomem_relearn_source"
]
}
}4b. Enable hooks:
{
"hooks": {
"internal": {
"enabled": true
}
}
}4c. Install hooks:
openclaw hooks install -l "$(pwd)/hooks"This keeps the repo as the source of truth via hooks.internal.load.extraDirs
and avoids the old copied-handler drift inside ~/.openclaw/workspace/hooks/.
The hook pack now ships installable sanitized handler.js files, while keeping
mirrored handler.js.example references for manual workflows.
Fallback manual mode is still possible through hooks/README.md.
4d. Install the native OpenClaw plugin:
openclaw plugins install -l "$(pwd)/plugin"The plugin gives agents explicit noldomem_recall, noldomem_store,
noldomem_pin, noldomem_forget, and noldomem_relearn_source tools. Select either its typed hooks or
the legacy hook pack for each automatic capture/injection event. The package declares its runtime entrypoint for
the OpenClaw 2026.5.2+ plugin installer path. See
plugin/README.md.
Operational capture ignores NoldoMem's own explicit tools, so memory reads and
writes do not recursively create extra memory writes.
For OpenClaw 2026.5.20+, keep compaction capture at the host's 30 second default so NoldoMem's bounded capture request has room to finish without stalling the gateway. Shorter optional lifecycle hooks can still use tighter timeouts:
{
"plugins": {
"entries": {
"noldomem": {
"hooks": {
"allowConversationAccess": true,
"allowPromptInjection": false,
"timeoutMs": 5000,
"timeouts": {
"after_tool_call": 3000,
"before_compaction": 30000,
"subagent_ended": 3000
}
}
}
}
}
}On OpenClaw 2026.9.3, conversation hooks require the explicit grant above.
For typed automatic recall, set both enableAutoRecall: true and
hooks.allowPromptInjection: true in the selected profile. See the plugin guide
for choosing one automatic capture/injection owner.
4e. Set the API key for hooks and plugin:
mkdir -p ~/.noldomem
echo "your-api-key-here" > ~/.noldomem/memory-api-key
chmod 600 ~/.noldomem/memory-api-keyOptional multi-workspace session discovery for maintenance/ingest utilities:
# In .env
AGENT_MEMORY_SESSIONS_ROOT="$HOME/.openclaw/agents"4f. Restart OpenClaw to load the hooks and plugin.
Optional per-workspace policy file:
{
"crossWorkspaceRecall": false,
"sharedNamespaces": [],
"dailyNotesEnabled": true
}Path: workspace/.openclaw/noldo-memory.json
curl -s localhost:8787/v1/health
# {"status":"ok","checks":{"storage":true,"embedding":true}}
curl -X POST localhost:8787/v1/store \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"text": "Test memory from setup", "agent": "main"}'
curl -X POST localhost:8787/v1/recall \
-H "Content-Type: application/json" -H "X-API-Key: YOUR_KEY" \
-d '{"query": "test", "agent": "main", "limit": 5}'Put this in your agent's TOOLS.md or system prompt so the agent knows how to use NoldoMem:
NoldoMem can also back non-OpenClaw agents through a small adapter that calls
the public HTTP API. Hermes Agent has a native MemoryProvider adapter in
adapters/hermes/noldomem; general external
runtime guidance lives in
docs/external-runtime-adapters.md.
For Hermes v2026.5.28+, verify that the effective toolsets still expose
noldomem_recall, noldomem_store, noldomem_pin, noldomem_forget, and noldomem_relearn_source; external
MemoryProvider tools are gated by the memory toolset when explicit toolsets
are configured.
## Memory API (NoldoMem)
You have access to a persistent memory system at localhost:8787.
All requests need headers:
- Content-Type: application/json
- X-API-Key: <key> (read from ~/.noldomem/memory-api-key)
### Store a memory
POST /v1/store {"text": "...", "agent": "YOUR_AGENT_ID", "session_id": "OPTIONAL_SESSION_ID"}
- Auto-classified as: fact, preference, rule, conversation, or lesson
- Optional `session_id` is stored as provenance (`source_session`)
### Recall memories
POST /v1/recall {"query": "...", "agent": "YOUR_AGENT_ID", "limit": 5}
- Returns relevant memories ranked by relevance + recency + importance
- Filter by type: {"memory_type": "lesson"} for lessons only
### Store a rule (max importance)
POST /v1/rule {"text": "Always run tests before commit", "agent": "YOUR_AGENT_ID"}
### What happens automatically (no action needed)
- Old memories fade over time (Ebbinghaus decay) — use them or lose them
- Lessons decay 3x slower than facts
- Repeated mistakes (3+) become permanent rules
- Your session starts with relevant memories pre-loaded (bootstrap hook)
- Feedback you give ("wrong", "don't do that") is captured as lessons
### Error responses
- 401: Invalid API key. Read key from ~/.noldomem/memory-api-key
- 404: Unknown endpoint. Check URL
- 422: Invalid request body. Check required fields (text, agent)
- 500: Server error (usually embedding server down). Retry in 5 seconds, max 2 retriesNoldoMem offers a typed OpenClaw plugin and a legacy lifecycle hook pack. Select one automatic capture/injection path per event; enabling both can duplicate work:
- The native plugin exposes agent tools:
noldomem_recall,noldomem_store,noldomem_pin,noldomem_forget,noldomem_relearn_source. - The hook pack handles lifecycle capture, bootstrap recall, compaction snapshots, and session transitions.
| Hook | When | What It Does |
|---|---|---|
| bootstrap-context | Session start | Recalls relevant memories + lessons, injects into agent context |
| realtime-capture | During chat | Detects feedback/corrections, stores as lessons |
| session-end-capture | Session end | Detects unverified suggestions, auto-generates lessons |
| after-tool-call | After tool use | Captures command outputs (allowlist-filtered) |
| before-compaction | Before compaction | Captures high-signal context before it is summarized away |
| pre-session-save | Before save | Tags session with memory metadata |
| post-compaction-restore | After compaction | Re-injects critical memories lost in context compaction |
| subagent-complete | Sub-agent done | Captures sub-agent results |
| claim-scanner | After replies | Logs unverified feature/config claims for audit |
| message-recall | User message | Optional mid-conversation recall hook |
Each hook has a HOOK.md in hooks/ explaining its behavior and configuration.
NoldoMem uses two complementary classification fields:
| Field | Purpose | Values | Set by |
|---|---|---|---|
category |
Who wrote it / where it came from | user, assistant, qa_pair, decision, lesson, rule, other |
Hook at capture time |
memory_type |
Canonical semantic type | fact, preference, rule, conversation, lesson, other |
API auto-classifier |
category reflects the message origin or operational label — a user message, an assistant response, a paired Q&A exchange, or a hook-provided label such as decision.
memory_type stays intentionally small so API validation, DB filters, and search ranking never drift apart. Operational concepts such as incidents, deployments, config changes, and decisions should remain in category, source, namespace, or the memory text itself, not in memory_type.
Both fields are used independently. memory_type drives type-specific bonuses in hybrid search (e.g., lessons get +0.35/60 RRF boost). category is available for reporting and operational labeling but does not expand the public memory_type enum.
graph TD
A[OpenClaw Agent] <-->|lifecycle events| B(Hooks)
B -->|store/recall HTTP| C{NoldoMem API :8787}
C -->|read/write| D[(SQLite per agent)]
C -->|embed text| E{Embedding Server :8090}
B -->|bootstrap-context| A
B -->|realtime-capture| C
B -->|session-end-capture| C
Agent Session
|
+-- bootstrap-context hook -----> NoldoMem /v1/recall --> inject memories into context
|
+-- [conversation happens] -----> realtime-capture hook --> /v1/store (lessons)
|
+-- [tool calls] ---------------> after-tool-call hook --> /v1/store (outputs)
|
+-- session-end-capture hook ---> /v1/store (unverified suggestions as lessons)
|
+-- pre-session-save hook ------> tag session metadata
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/v1/health |
GET | No | Health check |
/v1/health/deep |
GET | Yes | DB integrity, embedding, disk |
/v1/store |
POST | Yes | Store a memory |
/v1/recall |
POST | Yes | Hybrid search |
/v1/capture |
POST | Yes | Batch ingest (max 200 messages) |
/v1/rule |
POST | Yes | Store rule (importance=1.0) |
/v1/forget |
DELETE | Yes | Delete revision family and block identified source replay |
/v1/relearn-source |
POST | Yes | Explicitly unblock one source for future ingestion |
/v1/pin |
POST | Yes | Pin (protect from decay) |
/v1/unpin |
POST | Yes | Unpin |
/v1/decay |
POST | Yes | Run Ebbinghaus decay |
/v1/consolidate |
POST | Yes | Deduplicate + archive |
/v1/compress |
POST | Yes | Summarize old memories |
/v1/gc |
POST | Yes | Purge soft-deleted |
/v1/amnesia-check |
POST | Yes | Check memory coverage |
/v1/stats |
GET | Yes | DB statistics |
/v1/agents |
GET | Yes | List agent DBs |
/v1/metrics |
GET | Yes | Operational metrics |
/v1/metrics/lessons |
GET | Yes | Lesson effectiveness |
/v1/export |
GET | Yes | Export as JSON |
/v1/import |
POST | Yes | Import (max 500) |
/v1/admin/rotate-key |
POST | Admin | Rotate API key |
All endpoints accept ?agent=<id> for per-agent routing.
POST /v1/capture and POST /v1/store also support namespace in JSON body (recommended for session-scoped memory isolation).
Query -> Semantic (0.50) -> sqlite-vec L2 KNN (legacy score scale)
-> Keyword (0.25) -> FTS5 BM25
-> Recency (0.10) -> exp(-0.01 * days)
-> Strength (0.07) -> Ebbinghaus retention
-> Importance(0.08) -> write-time score
|
RRF fusion (k=60) -> Primary reranker (top-10) -> Background reranker (top-3)
Reranking has two modes:
- Local cross-encoder: optional, uses
sentence-transformersmodels. Install it withpip install ".[reranker]"for source installs orpip install "sentence-transformers>=3.0.0"for manual environments. - API reranker: set
AGENT_MEMORY_RERANKER_API_ENABLED=trueand provideAGENT_MEMORY_RERANKER_API_KEYorAGENT_MEMORY_RERANKER_API_KEY_FILE. API reranking handles the primary pass and can fall back to the local cross-encoder at runtime if the hosted call fails.
sudo cp noldo-memory.service.example /etc/systemd/system/noldo-memory.service
# Edit: paths, User, EnvironmentFile
sudo systemctl enable --now noldo-memorySee crontab.example. Key jobs: daily decay, weekly consolidation + GC, 6-hourly embedding backfill, daily SQLite backup.
-
AGENT_MEMORY_HOST=127.0.0.1(never 0.0.0.0) - API key file: permissions 600
- Data directory: permissions 700
-
memorySearch.enabled: falsein openclaw.json -
hooks.internal.enabled: truein openclaw.json - Embedding server localhost only
- Backup cron active
cp .env.example .env && mkdir -p models
# Download embedding model into models/
docker compose up -dAll config via environment variables. See .env.example for full list.
pip install -r requirements-dev.txt
python -m pytest tests/ -v
ruff check agent_memory/Run a read-only SQLite audit without printing memory text:
python scripts/audit_memory_quality.py --db ~/.agent-memory/memory.sqlite
python scripts/audit_memory_quality.py --jsonThe audit reports aggregate counts for vectorless rows, invalid memory types, duplicate text groups, very long/short rows, namespace distribution, and secret-like patterns. It prints hashed row identifiers only, never memory content.
NoldoMem should remember where credentials live and what they are used for, not the raw credential value. This keeps recall useful while reducing the chance of a token leaking into chat, logs, exports, or backups.
Audit first:
python scripts/audit_memory_quality.py --json
python scripts/redact_memory_secrets.py --db ~/.agent-memory/memory.sqlite --jsonApply redaction only after reviewing the aggregate dry-run counts:
python scripts/redact_memory_secrets.py --db ~/.agent-memory/memory.sqlite --apply
python scripts/backfill_vectors.py --agent all --batch-size 2 --max-sub-batch 1The redaction script creates a SQLite backup before applying changes. It
replaces secret-like values with placeholders, redacts original_text when
present, invalidates vectors for changed searchable text, and never prints
memory content or secret values.
Can I use NoldoMem without OpenClaw? Yes. NoldoMem is a standalone REST API. Any application that can make HTTP requests can store and recall memories. The hooks are OpenClaw-specific, but the API works with anything.
How much disk space do memories use? Roughly 1-2 KB per memory (text + metadata + embedding vector). 10,000 memories take about 15-20 MB. SQLite with WAL mode handles concurrent access well.
What happens if the embedding server goes down?
NoldoMem continues to work in degraded mode — keyword search (BM25) still works, but semantic search returns no results. The /v1/health endpoint reports "embedding": false. Memories stored without embeddings get auto-embedded when the server comes back (via the backfill worker).
Can multiple agents share memories?
Each agent has its own isolated SQLite database by design. Use ?agent=<id> to route requests. Shared reads are explicit opt-in: agent=all enables cross-agent recall/search style operations, while writes still stay scoped to one agent or namespace for safety.
How do I force a memory type?
Pass "memory_type": "rule" (or fact/preference/lesson/conversation) in your /v1/store request to override auto-classification.
| Problem | Cause & Fix |
|---|---|
curl returns "Unauthorized" |
Wrong API key. Check ~/.noldomem/memory-api-key matches AGENT_MEMORY_API_KEY in .env |
/v1/health shows "embedding": false |
Embedding server not running or wrong URL. Check OPENROUTER_BASE_URL in .env |
| Agent doesn't remember anything | Hooks not loading. Verify hooks.internal.enabled: true in openclaw.json and restart OpenClaw |
vector dimension mismatch error |
Changed embedding model without reindexing. Run .venv/bin/python scripts/reindex_embeddings.py |
| Port 8787 already in use | Set AGENT_MEMORY_PORT=8788 (or any free port) in .env |
| Memories disappearing too fast | Decay is too aggressive. Adjust decay cron frequency in crontab, or pin critical memories via /v1/pin |
sqlite3.OperationalError: database is locked |
Concurrent writes. NoldoMem handles this with WAL mode, but check for external tools accessing the DB |
cd /path/to/noldo-memory
git pull origin main
.venv/bin/python -m pip install .
# Compare .env.example with your .env for new variables
# Database migrations run automatically on startup
sudo systemctl restart noldo-memorySee CONTRIBUTING.md.
MIT
noldomem_forget accepts a recalled memory_id for an explicit user forgetting
request. It deletes that assertion and its revision family in the current agent
scope; original transcripts and other stores remain separate.
Identified source sessions are blocked after forgetting until an explicit user
request authorizes noldomem_relearn_source. A new independent session is not
blocked by text similarity. See source identity, relearning receipts and legacy
limits.