Last week we
analyzed Hermes' memory architecture
and proposed its MemoryProvider ABC as the basis for a community
agent-memory-protocol standard. This week we applied the same analysis to
OpenClaw — a TypeScript
multi-channel AI gateway with 17,000+ commits, active daily development, and sponsors
including OpenAI, GitHub, and NVIDIA.
The results are revealing. OpenClaw and Hermes both solve the pluggable memory problem — but with different philosophies, different languages, and meaningfully different tradeoffs. And one key prediction from the Hermes post turned out to be wrong in an instructive way.
OpenClaw is a personal AI gateway. Where Hermes is a self-improving agent primarily accessed through a terminal, OpenClaw is a multi-channel daemon: it runs as a background service and routes messages between Claude and every messaging platform you already use — WhatsApp, Telegram, Slack, Discord, Signal, iMessage, Matrix, Zalo, WeChat, QQ, and more. You talk to your AI from your phone; OpenClaw handles the protocol translation, tool dispatch, memory, and delivery.
The version tag as of this writing is 2026.4.20. Its git history has
over 17,000 commits. The last 12 commits are all from the past few days: CI
performance tuning, test refactoring, shared fixture helpers. This is a codebase
under continuous professional maintenance, not a research prototype.
The relevance for memory integration: OpenClaw must work reliably across sessions measured in weeks or months, across multiple messaging platforms, often for multiple users simultaneously via its gateway mode. Memory isn't optional — it's load-bearing infrastructure.
Where Hermes defines memory via an ABC that providers inherit, OpenClaw uses a capability registration pattern. A memory plugin registers a bundle of callbacks rather than subclassing an interface:
// src/plugins/memory-state.ts
export type MemoryPluginCapability = {
promptBuilder?: MemoryPromptSectionBuilder;
flushPlanResolver?: MemoryFlushPlanResolver;
runtime?: MemoryPluginRuntime;
publicArtifacts?: MemoryPluginPublicArtifactsProvider;
};
export function registerMemoryCapability(
pluginId: string,
capability: MemoryPluginCapability,
): void {
memoryPluginState.capability = { pluginId, capability: { ...capability } };
}
Each slot is independently replaceable:
promptBuilder — returns the system-prompt block that tells the
model when and how to use memory. Default guidance: "before answering anything about
prior work, decisions, or user preferences, run memory_search first."
flushPlanResolver — called before context compression to
decide which thoughts should be durably written to MEMORY.md. This is
OpenClaw's equivalent of Hermes' on_pre_compress() hook, but declarative:
the resolver returns a plan, and OpenClaw executes it.
runtime — the actual search/index engine. This is where
the MemorySearchManager interface lives.
publicArtifacts — lists memory artifacts that can be
surfaced externally (e.g., in the gateway dashboard). No Hermes equivalent.
The runtime slot expects a MemorySearchManager, defined in
the SDK. The three existing backends all implement it:
// extensions/memory-core/src/memory/manager.ts
export class MemoryIndexManager
extends MemoryManagerEmbeddingOps
implements MemorySearchManager
{
async search(
query: string,
opts?: {
maxResults?: number;
minScore?: number;
sessionKey?: string;
onDebug?: (debug: MemorySearchRuntimeDebug) => void;
},
): Promise<MemorySearchResult[]>
async warmSession(sessionKey?: string): Promise<void>
status(): MemoryProviderStatus
async close(): Promise<void>
}
Search results are a typed union:
// src/plugins/memory-state.ts
type MemoryCorpusSearchResult = {
corpus: string;
path: string;
title?: string;
kind?: string;
score: number;
snippet: string;
lines?: { start: number; end: number };
};
| Backend | Storage | Search | When to use |
|---|---|---|---|
| builtin | SQLite per agent~/.openclaw/state/memory/{agentId}.sqlite |
FTS5 + optional vector | Default. Works offline, zero config. |
| qmd | Quantum Memory Daemon (local sidecar) | Vector + reranking + query expansion | Higher recall quality, local-first. |
| honcho | Honcho cloud service | AI-native, cross-session user modeling | Multi-user, cross-device memory. |
Five integration points, each corresponding to a different phase of a conversation turn:
promptBuilder() injects recall
guidance. The model is told to call memory_search before answering
questions about prior context.
memory_search and
memory_get are registered from
extensions/memory-core/src/tools.ts and appear in the agent's tool
catalog.
warmSession(sessionKey) is called at
session start. Implementations use this to trigger background index sync.
memory_search, the runtime can optionally kick off an async re-sync
for subsequent turns.
flushPlanResolver decides what to durably write. A silent agent turn
runs the plan and appends to MEMORY.md, append-only, guardian-protected.
Unlike Hermes — where MCP is one of several tool integration paths — OpenClaw treats
MCP as a primary tool registration mechanism. The configuration type
(src/config/types.mcp.ts) supports both stdio and HTTP transports. MCP
tools are materialized into OpenClaw's native tool catalog at startup via
pi-bundle-mcp-materialize.ts, subject to the same tool policy and
allow/deny logic as built-in tools.
// src/config/types.mcp.ts
export type McpServerConfig = {
command?: string; // stdio transport
args?: string[];
url?: string; // HTTP transport
transport?: "sse" | "streamable-http";
headers?: Record<string, string | number | boolean>;
connectionTimeoutMs?: number;
};
Time to working: under two minutes. No code, no compilation, no plugin installation — just four lines of YAML and a daemon restart.
# Install once
cargo install mentisdb
# Start the daemon (MCP endpoint on port 9471)
mentisdbd &
# Verify
curl -s http://localhost:9471/health
# → {"status":"ok"}
mcp:
servers:
mentisdb:
url: "http://localhost:9471"
transport: "streamable-http"
connectionTimeoutMs: 5000
Save and restart OpenClaw:
openclaw gateway restart
openclaw tools list | grep mentisdb
# mcp_mentisdb_mentisdb_search
# mcp_mentisdb_mentisdb_append_thought
# mcp_mentisdb_mentisdb_recent_context
# mcp_mentisdb_mentisdb_bootstrap
# … all MentisDB MCP tools
MentisDB tools are now available in every agent conversation, just like any other built-in tool. The model can call them directly:
# From any channel (Telegram, WhatsApp, Slack, …)
You: Search my memory for anything about the API rate limiting fix from last month.
# OpenClaw routes to: mcp_mentisdb_mentisdb_search({"query": "API rate limiting fix"})
# Returns ranked results from MentisDB's hybrid retrieval pipeline
mcp:
servers:
mentisdb:
url: "http://localhost:9471"
transport: "streamable-http"
tools:
- mentisdb_search
- mentisdb_append_thought
- mentisdb_recent_context
Add to your agent's system prompt or MEMORY.md:
At the start of each conversation, call mentisdb_bootstrap to load context
from the chain before answering any questions.
mcp:
servers:
mentisdb:
url: "https://my.mentisdb.com:9473"
transport: "streamable-http"
For full lifecycle integration — automatic turn sync, session warmup, flush plan participation — you can implement MentisDB as a native OpenClaw memory backend. This is a TypeScript extension plugin, not a Python ABC subclass.
Create the plugin directory:
~/.openclaw/plugins/
└── memory-mentisdb/
├── package.json
├── openclaw.plugin.json
└── src/
├── index.ts ← entry point, registers the capability
└── manager.ts ← MemorySearchManager implementation
openclaw.plugin.json
{
"id": "memory-mentisdb",
"name": "MentisDB Memory",
"description": "Durable, hash-chained semantic memory via MentisDB",
"version": "1.0.0",
"entrypoint": "./src/index.ts",
"capabilities": ["memory"]
}
src/manager.ts — the search backend:
import type { MemorySearchManager, MemorySearchResult, MemoryProviderStatus }
from "@openclaw/sdk";
export class MentisDBManager implements MemorySearchManager {
private baseUrl: string;
private chainKey: string;
private abortController: AbortController | null = null;
constructor(opts: { url: string; chainKey: string }) {
this.baseUrl = opts.url;
this.chainKey = opts.chainKey;
}
async search(
query: string,
opts?: { maxResults?: number; minScore?: number; sessionKey?: string },
): Promise<MemorySearchResult[]> {
const resp = await fetch(`${this.baseUrl}/v1/search`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query,
limit: opts?.maxResults ?? 10,
chain_key: this.chainKey,
}),
});
if (!resp.ok) return [];
const { thoughts } = await resp.json();
return (thoughts ?? []).map((t: any) => ({
corpus: "mentisdb",
path: `mentisdb://${t.id}`,
title: t.thought_type,
kind: t.thought_type?.toLowerCase(),
score: t.score ?? 0.5,
snippet: t.content,
}));
}
async warmSession(sessionKey?: string): Promise<void> {
// Optional: trigger background sync, verify daemon reachability
await fetch(`${this.baseUrl}/health`).catch(() => {});
}
status(): MemoryProviderStatus {
return { available: true, label: "MentisDB" };
}
async close(): Promise<void> {
this.abortController?.abort();
}
}
/** Append a thought to MentisDB (called from flush plan + sync_turn). */
export async function appendThought(opts: {
baseUrl: string;
chainKey: string;
content: string;
thoughtType: string;
tags?: string[];
}): Promise<void> {
await fetch(`${opts.baseUrl}/v1/thoughts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: opts.content,
thought_type: opts.thoughtType,
chain_key: opts.chainKey,
tags: opts.tags ?? [],
}),
}).catch(console.error);
}
src/index.ts — register the capability:
import type { OpenClawPluginAPI } from "@openclaw/sdk";
import { MentisDBManager, appendThought } from "./manager.js";
export function register(api: OpenClawPluginAPI): void {
const config = api.getConfig();
const baseUrl = config.env?.MENTISDB_URL ?? "http://localhost:9471";
const chainKey = config.env?.MENTISDB_CHAIN ?? "openclaw";
const manager = new MentisDBManager({ url: baseUrl, chainKey });
api.registerMemoryCapability("memory-mentisdb", {
// ── Prompt block ──────────────────────────────────────────────────────
promptBuilder: (_tools) =>
"Your long-term memory is stored in MentisDB. Before answering questions\n"
+ "about past decisions, prior work, or user preferences, call memory_search.",
// ── Flush plan: what to persist before context compression ────────────
flushPlanResolver: async (messages) => {
const userMsgs = messages
.filter(m => m.role === "user" && m.content)
.slice(-5);
return userMsgs.map(m => ({
content: String(m.content).slice(0, 2000),
write: async () =>
appendThought({
baseUrl, chainKey,
content: String(m.content).slice(0, 2000),
thoughtType: "Observation",
tags: ["openclaw", "pre-compress"],
}),
}));
},
// ── Runtime: the actual search engine ────────────────────────────────
runtime: {
manager,
// sync_turn: called after every completed turn
onTurnComplete: async (userContent, assistantContent) => {
await appendThought({
baseUrl, chainKey,
content: `User: ${userContent}\nAssistant: ${assistantContent}`.slice(0, 3000),
thoughtType: "Observation",
tags: ["openclaw", "turn"],
});
},
},
// ── Public artifacts: list memories in gateway dashboard ──────────────
publicArtifacts: {
listArtifacts: async () => [{
id: "mentisdb-chain",
label: `MentisDB chain: ${chainKey}`,
kind: "memory-index",
}],
},
});
}
Activate via config:
memory:
provider: memory-mentisdb
env:
MENTISDB_URL: "http://localhost:9471"
MENTISDB_CHAIN: "openclaw"
| Feature | Hermes MemoryProvider | OpenClaw MemoryPlugin | Proposed Standard |
|---|---|---|---|
| Language | Python | TypeScript | Language-agnostic spec |
| Pattern | ABC inheritance | Capability registration | Either (composable preferred) |
| Recall / persist | ✓ prefetch + sync_turn | ✓ search + onTurnComplete | ✓ required |
| Prompt injection | ✓ system_prompt_block() | ✓ promptBuilder() | ✓ required |
| Tool exposure to model | ✓ get_tool_schemas() | ✓ via memory_search / memory_get built-ins | ✓ required |
| Async prefetch / caching | ✓ queue_prefetch() | ✓ warmSession() + async sync | ✓ required |
| Pre-compression hook | ✓ on_pre_compress() | ✓ flushPlanResolver() | ✓ required |
| Built-in write bridging | ✓ on_memory_write() | ~ guardian-protected MEMORY.md append | ✓ recommended |
| Delegation observability | ✓ on_delegation() | — not exposed | ✓ recommended |
| Session boundary hook | ✓ on_session_end() | ~ implicit via close() | ✓ required |
| Public artifact listing | — not in Hermes | ✓ publicArtifacts | ~ nice to have |
| Multi-tenant scoping | ✓ user_id, agent_identity in initialize() | ✓ sessionKey in search() / warmSession() | ✓ required |
| MCP as first class | ~ one integration path among several | ✓ primary tool registration mechanism | ✓ required |
| Failure isolation | ✓ catches & logs, agent continues | ✓ same pattern | ✓ required |
| Config / setup wizard | ✓ get_config_schema() | ~ via plugin.json + env keys | ✓ recommended |
Both frameworks cover the core protocol. OpenClaw adds publicArtifacts
(no Hermes equivalent) and makes MCP a first-class transport. Hermes adds
on_delegation() (no OpenClaw equivalent) and a richer setup wizard
abstraction. Neither is strictly a superset of the other.
In the Hermes post we proposed six design principles for an agent-memory-protocol standard. OpenClaw is an independent implementation built by a different team — it's an honest test of whether those principles hold.
Frozen snapshot injection. ✓ Both frameworks freeze the memory snapshot
into the system prompt at session start. OpenClaw's MEMORY.md is loaded
once; Hermes' MEMORY.md is loaded once. Mid-session writes go to disk,
not the active prompt. This isn't a coincidence — it's the only approach that preserves
the LLM's prefix cache. Any standard must mandate it.
Pre-compression extraction. ✓ Both frameworks hook before context is
compressed. Hermes calls on_pre_compress(). OpenClaw calls
flushPlanResolver(). The mechanism differs — Hermes is imperative (extract
now), OpenClaw is declarative (return a plan, system executes it) — but the intent is
identical. Any standard must define this hook.
Async prefetch at turn boundary. ✓ Both frameworks warm the cache for the next turn while the current one is processing. Hermes uses Python threads; OpenClaw uses async warmSession calls. Same pattern, different runtime.
Failure isolation. ✓ Both frameworks catch exceptions in memory provider calls and continue. This is non-negotiable for production agents.
ABC inheritance vs. capability registration. The Hermes post assumed that a standard would look like an abstract base class — a single interface you subclass. OpenClaw proves otherwise. Its capability registration pattern is equally expressive and arguably more composable: you register only the callbacks you implement, and each can be replaced independently without touching the others. A language-agnostic standard should define the protocol (which events fire, in what order, with what arguments) rather than mandating an implementation pattern. ABC or capability registration are both conforming implementations.
MCP must be part of the standard, not optional. The Hermes post treated MCP as one integration path. OpenClaw treats it as the primary tool registration mechanism. Given that MCP is rapidly becoming the universal tool protocol — backed by Anthropic, adopted by GitHub Copilot, Cursor, Claude Desktop, and now OpenClaw — any memory protocol that doesn't specify how MCP interacts with memory tools will be incomplete within twelve months. The standard should define: how a memory backend exposes tools via MCP, and how an agent framework routes those MCP tools through the memory lifecycle hooks.
Delegation observability is real, but niche.
Hermes' on_delegation() hook — called when a subagent completes — has
no OpenClaw equivalent. OpenClaw runs in a gateway context where multi-agent
delegation isn't the primary model. This suggests on_delegation() should
be optional in the standard, not required. It matters for orchestration
frameworks; it's irrelevant for single-user gateways.
"One provider" is a Hermes constraint, not a universal principle.
Hermes enforces one external provider at a time. OpenClaw does the same implicitly
(via the single capability slot). But this is an implementation choice
that both frameworks made for practical reasons, not a protocol requirement. A
standard should not mandate single-provider — some use cases legitimately need
two backends (e.g., local fast recall + remote long-term storage with different
latency profiles).
Based on both implementations, here is what a conforming memory protocol must define:
Optional (implement for richer integrations):
These eleven required points and four optional extensions are where both Hermes and OpenClaw converge — independently, built by different teams, in different languages, for different deployment models. That convergence is the evidence that they belong in a standard.
The native TypeScript backend in Part 3 is a working starting point.
We plan to publish it as a proper @mentisdb/openclaw-plugin npm package
and submit it as a pull request to the OpenClaw plugin registry alongside the
Python MemoryProvider plugin for Hermes.
For the protocol itself: we'll open a GitHub discussion to collect feedback from both the Hermes and OpenClaw communities before drafting a formal spec. If you're building an agent framework and want to participate: join the discussion.