Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt
Use this file to discover all available pages before exploring further.
Memory Tools
NullClaw provides four memory tools that enable agents to store and retrieve long-term facts:
memory_store — Persist facts to long-term memory
memory_recall — Search memory with hybrid retrieval
memory_list — List all stored memories
memory_forget — Delete memory entries
Memory tools support:
- Multiple backends — SQLite, Markdown, None
- Hybrid search — BM25 + vector similarity with RRF merge
- Temporal decay — Recent memories weighted higher
- MMR diversification — Avoid redundant results
- Vector sync — Automatic embedding sync to vector store
Memory Architecture
Memory Backend
Memory tools operate on a pluggable Memory backend:
pub const Memory = struct {
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
store: *const fn (ptr: *anyopaque, key: []const u8, content: []const u8, category: MemoryCategory, metadata: ?[]const u8) anyerror!void,
recall: *const fn (ptr: *anyopaque, allocator: Allocator, query: []const u8, limit: usize, category: ?MemoryCategory) anyerror![]MemoryEntry,
list: *const fn (ptr: *anyopaque, allocator: Allocator, category: ?MemoryCategory) anyerror![]MemoryEntry,
forget: *const fn (ptr: *anyopaque, key: []const u8) anyerror!bool,
};
};
Memory Categories
Memories are organized into three categories:
pub const MemoryCategory = enum {
core, // Stable facts and preferences
daily, // Session notes and temporary context
conversation, // Important conversation snippets
};
Usage guidelines:
core — User preferences, stable facts (e.g., “User prefers Zig”)
daily — Temporary session notes (e.g., “Working on feature X today”)
conversation — Key conversation excerpts (use sparingly)
Do not store routine greetings or every chat message in memory. Only store important facts and decisions.
Hybrid Retrieval Pipeline
When a MemoryRuntime is available, memory_recall uses a sophisticated retrieval pipeline:
- BM25 full-text search — Exact keyword matching
- Vector similarity search — Semantic similarity via embeddings
- RRF merge — Reciprocal Rank Fusion combines rankings
- Temporal decay — Recent memories boosted
- MMR diversification — Remove redundant results
Without MemoryRuntime:
Falls back to raw mem.recall() (BM25 only)
memory_store
Persist facts to long-term memory with automatic vector sync.
Parameters
Unique key for this memory (used for updates and deletion)
The information to remember
Memory category: core, daily, or conversation
Configuration
const mst = try allocator.create(memory_store.MemoryStoreTool);
mst.* = .{
.memory = memory_backend, // Memory vtable
.mem_rt = &memory_runtime, // Optional MemoryRuntime for vector sync
};
Usage
Store a core fact:
{
"tool": "memory_store",
"key": "user_language",
"content": "User prefers Zig programming language",
"category": "core"
}
Response:
Stored memory: user_language (core)
Store a daily note:
{
"tool": "memory_store",
"key": "daily_2026_03_01",
"content": "Working on NullClaw documentation today. Focus on tools reference.",
"category": "daily"
}
Response:
Stored memory: daily_2026_03_01 (daily)
Vector Sync
When mem_rt is configured, stored memories are automatically:
- Embedded via configured embedding provider
- Upserted to vector store (e.g., Qdrant, Chroma)
- Available for semantic search in
memory_recall
Sync is best-effort and does not block on failure.
Duplicate Keys
Storing with an existing key overwrites the previous content:
{
"tool": "memory_store",
"key": "status",
"content": "Starting project"
}
Later:
{
"tool": "memory_store",
"key": "status",
"content": "Project completed"
}
memory_recall now returns “Project completed” for key “status”.
Source
src/tools/memory_store.zig:12-61
memory_recall
Search long-term memory for relevant facts using hybrid retrieval.
Parameters
Keywords or phrase to search for in memory
Maximum results to return (1-100)
Configuration
const mrt = try allocator.create(memory_recall.MemoryRecallTool);
mrt.* = .{
.memory = memory_backend, // Memory vtable
.mem_rt = &memory_runtime, // Optional MemoryRuntime for hybrid search
};
Usage
Search for facts about Zig:
{
"tool": "memory_recall",
"query": "Zig programming",
"limit": 3
}
Response:
Found 2 memories:
1. [user_language] (core): User prefers Zig programming language
2. [daily_2026_03_01] (daily): Working on NullClaw documentation today. Focus on tools reference.
No results:
{
"tool": "memory_recall",
"query": "Python"
}
Response:
No memories found matching: Python
Hybrid Search Results
When mem_rt is configured, results include hybrid scores:
Found 3 memories:
1. [user_language] (bm25 0.85): User prefers Zig programming language
2. [project_notes] (vector 0.72): Working on a Zig-based AI agent
3. [learning_log] (bm25 0.68): Started learning Zig last week
Scores indicate:
bm25 — Exact keyword match
vector — Semantic similarity
- Higher score = better match
Internal Memory Filtering
Memories with keys starting with __bootstrap. are filtered from results:
// Stored internally during bootstrap:
"__bootstrap.prompt.SOUL.md" = "internal system prompt"
// User memory:
"user_pref" = "loves Zig"
memory_recall("Zig") returns only user_pref, not bootstrap internals.
Source
src/tools/memory_recall.zig:14-163
memory_list
List all stored memories (optionally filtered by category).
Parameters
Filter by category: core, daily, or conversation
Configuration
const mlt = try allocator.create(memory_list.MemoryListTool);
mlt.* = .{
.memory = memory_backend,
};
Usage
List all memories:
{
"tool": "memory_list"
}
Response:
Found 5 memories:
1. [user_language] (core): User prefers Zig programming language
2. [daily_2026_03_01] (daily): Working on NullClaw documentation today
3. [status] (core): Project completed
4. [meeting_notes] (conversation): Discussed feature roadmap
5. [timezone] (core): User timezone is UTC-8
List core memories only:
{
"tool": "memory_list",
"category": "core"
}
Response:
Found 3 memories:
1. [user_language] (core): User prefers Zig programming language
2. [status] (core): Project completed
3. [timezone] (core): User timezone is UTC-8
Source
src/tools/memory_list.zig
memory_forget
Delete a memory by key with automatic vector store cleanup.
Parameters
The key of the memory to forget
Configuration
const mft = try allocator.create(memory_forget.MemoryForgetTool);
mft.* = .{
.memory = memory_backend,
.mem_rt = &memory_runtime, // Optional MemoryRuntime for vector cleanup
};
Usage
Delete a memory:
{
"tool": "memory_forget",
"key": "outdated_fact"
}
Response:
Forgot memory: outdated_fact
Key not found:
{
"tool": "memory_forget",
"key": "nonexistent"
}
Response:
No memory found with key: nonexistent
Vector Store Cleanup
When mem_rt is configured, memory_forget automatically:
- Deletes the memory from the primary backend (SQLite/Markdown)
- Deletes the corresponding vector embedding from the vector store
Cleanup is best-effort and does not block on failure.
Source
src/tools/memory_forget.zig:11-57
Memory Backends
SQLite Backend
var sqlite_mem = try mem_root.SqliteMemory.init(allocator, "memory.db");
defer sqlite_mem.deinit();
const mem = sqlite_mem.memory();
Features:
- BM25 full-text search via SQLite FTS5
- Category filtering
- Metadata support
- Persistent across restarts
Markdown Backend
var md_mem = try mem_root.MarkdownMemory.init(allocator, "memory.md");
defer md_mem.deinit();
const mem = md_mem.memory();
Features:
- Human-readable markdown file
- Simple key-value structure
- Substring search (not BM25)
- Great for debugging
None Backend (No-Op)
var none_mem = mem_root.NoneMemory.init();
defer none_mem.deinit();
const mem = none_mem.memory();
Features:
- All operations are no-ops
- Useful for testing without persistence
- Store/recall return empty results
Memory Runtime
For hybrid search with vector embeddings, configure a MemoryRuntime:
const mem_rt = try mem_root.MemoryRuntime.init(allocator, .{
.memory = memory_backend,
.embedding_provider = embedding_provider,
.vector_store = vector_store,
.k = 5, // Top-K results per method
.bm25_weight = 0.6,
.vector_weight = 0.4,
.temporal_decay_days = 30,
.mmr_lambda = 0.7,
});
defer mem_rt.deinit();
Bind to tools:
root.bindMemoryRuntime(tools, &mem_rt);
Retrieval Parameters
k — Number of candidates from each method (BM25, vector)
bm25_weight — Weight for BM25 scores in RRF merge (default: 0.6)
vector_weight — Weight for vector scores in RRF merge (default: 0.4)
temporal_decay_days — Days after which memory score decays by 50% (default: 30)
mmr_lambda — MMR diversification parameter (0 = max diversity, 1 = max relevance)
Use Cases
User Preferences
{
"tool": "memory_store",
"key": "user_timezone",
"content": "User is in UTC-8 (Pacific Time)",
"category": "core"
}
Project Context
{
"tool": "memory_store",
"key": "project_status",
"content": "Building NullClaw tools documentation. Completed file operations and shell pages.",
"category": "daily"
}
Conversation Highlights
{
"tool": "memory_store",
"key": "feature_request_2026_03_01",
"content": "User requested async tool execution with callback support",
"category": "conversation"
}
Recall During Task
{
"tool": "memory_recall",
"query": "user timezone"
}
Response:
Found 1 memory:
1. [user_timezone] (core): User is in UTC-8 (Pacific Time)
Testing
Run memory tool tests:
zig build test --summary all
Tests cover:
- Store/recall/list/forget operations
- Category filtering
- Hybrid search with MemoryRuntime
- Internal memory filtering
- No backend (NoneMemory) behavior