Skip to main content

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:
  1. BM25 full-text search — Exact keyword matching
  2. Vector similarity search — Semantic similarity via embeddings
  3. RRF merge — Reciprocal Rank Fusion combines rankings
  4. Temporal decay — Recent memories boosted
  5. 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

key
string
required
Unique key for this memory (used for updates and deletion)
content
string
required
The information to remember
category
string
default:"core"
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:
  1. Embedded via configured embedding provider
  2. Upserted to vector store (e.g., Qdrant, Chroma)
  3. 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

query
string
required
Keywords or phrase to search for in memory
limit
integer
default:5
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

category
string
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

key
string
required
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:
  1. Deletes the memory from the primary backend (SQLite/Markdown)
  2. 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