How It Works¶
What Happens Automatically¶
| Event | What memsearch does |
|---|---|
| Agent starts | Recent memories from the 2 most recent daily logs are injected as context (before_agent_start) |
| Each turn ends | Conversation is summarized by the OpenClaw agent and appended to daily .md (agent_end hook) |
| LLM needs history | Calls memory_search, memory_get, or memory_transcript tools progressively |
TypeScript Plugin Architecture¶
The plugin source is index.ts, built to index.js for OpenClaw 2026.5+ runtime loading. It registers tools, hooks, and a CLI subcommand using OpenClaw's plugin API:
graph TB
subgraph "Plugin Registration (index.ts -> index.js)"
TOOLS["registerTool (factory pattern)<br/>memory_search · memory_get · memory_transcript"]
HOOKS["Event hooks<br/>before_agent_start · agent_end · session_start"]
CLI["registerCli<br/>openclaw memsearch search/index/status"]
end
subgraph "Capture Pipeline"
AGENT_END["agent_end event"] --> EXTRACT["Extract last user+assistant turn<br/>from event.messages"]
EXTRACT --> SUMMARIZE["summarizeWithLLM()<br/>openclaw agent --local"]
SUMMARIZE --> WRITE["Append to YYYY-MM-DD.md<br/>with session anchors"]
WRITE --> INDEX["memsearch index<br/>(background, non-blocking)"]
end
subgraph "Recall"
SEARCH["memory_search"] --> MILVUS[(Milvus)]
EXPAND["memory_get"] --> MILVUS
TRANSCRIPT["memory_transcript"] --> JSONL["OpenClaw JSONL"]
end
style TOOLS fill:#1a2744,stroke:#6ba3d6,color:#a8b2c1
style HOOKS fill:#1a2744,stroke:#7bc67e,color:#a8b2c1
style MILVUS fill:#1a2744,stroke:#e0976b,color:#a8b2c1
Tool Registration: Factory Pattern¶
The tools use OpenClaw's registerTool factory pattern -- each tool receives the current ctx (context) on every invocation. This is how per-agent isolation works:
api.registerTool(
(ctx) => {
updateAgentContext(ctx); // update agentId, projectDir, collectionName
return {
name: "memory_search",
// ... tool definition with current collectionName
};
},
{ name: "memory_search" }
);
When the tool is called, ctx.agentId tells the plugin which agent is running. The plugin updates its internal state (projectDir, memoryDir, collectionName) accordingly, so memory operations always target the correct agent's data.
Capture¶
The capture pipeline hooks into OpenClaw's agent_end event, which fires after every conversational turn in TUI mode. The event provides event.messages — a complete snapshot of the message history up to that point.
sequenceDiagram
participant User
participant Agent as OpenClaw Agent
participant Plugin as memsearch plugin
participant File as Daily .md
User->>Agent: Ask question
Agent->>Agent: LLM calls, tool use, etc.
Agent->>Plugin: agent_end (event.messages)
Plugin->>Plugin: Extract last user+assistant turn
Plugin->>Plugin: summarizeWithLLM()
Plugin->>File: Append summary with anchors
Plugin->>Plugin: memsearch index (background)
The plugin extracts the last user question and assistant response from event.messages, summarizes them via LLM, and appends the summary to the daily markdown file. Set plugins.openclaw.summarize.model to override only this capture model; empty or unset keeps the default OpenClaw agent model, with no fallback to llm.model. No debounce or noise filtering is needed — agent_end provides a clean, complete message history for each turn.
OpenClaw 2026.5+ requires third-party plugins to opt in before reading raw conversation content from agent_end. The installer sets plugins.entries.memsearch.hooks.allowConversationAccess = true for capture and plugins.entries.memsearch.hooks.allowPromptInjection = true for recent-memory injection.
Known limitations
Summarization¶
Summaries are generated via openclaw agent --local with a third-person note-taker system prompt:
You are a third-person note-taker. Record what happened as factual
third-person notes. Output 2-6 bullet points, each starting with '- '.
Write in third person: 'User asked...', 'OpenClaw replied...'
If openclaw agent fails (e.g., no model configured), the plugin falls back to raw truncated text.
Cold-Start Context¶
On agent start (before_agent_start hook), the plugin reads recent daily memory files and returns them as prependContext:
api.on("before_agent_start", async () => {
const context = getRecentMemories(memoryDir);
if (context) {
return { prependContext: context };
}
});
The getRecentMemories function reads the last 15 lines from the 2 most recent daily .md files, extracting only bullet-point lines (- ...) to keep the context concise. This gives the LLM awareness of recent sessions so it can decide when to use the memory tools.
If no memory files exist yet, a hint is injected instead: "You have N past memory file(s). Use the memory_search tool when the user's question could benefit from historical context."
Multi-Agent Isolation¶
OpenClaw supports multiple agents (e.g., main, work). The plugin provides automatic isolation based on workspace directories:
| Agent | Memory Directory | Collection |
|---|---|---|
main |
~/.openclaw/workspace/.memsearch/memory/ |
ms_workspace_<hash> |
work |
~/.openclaw/workspace-work/.memsearch/memory/ |
ms_workspace_work_<hash> |
| custom | <workspace-dir>/.memsearch/memory/ |
ms_<basename>_<hash> |
Collection names are derived from the workspace path using the same algorithm as Claude Code, Codex, and OpenCode. This means when an agent's workspace points to a project directory used by other platforms, memories are automatically shared across platforms.
Memory Files¶
Memory files live alongside other workspace files in the agent's workspace directory:
Example Memory File¶
# 2026-03-25
## Session 14:47
### 14:47
<!-- session:a1b2c3d4 transcript:~/.openclaw/agents/main/sessions/a1b2c3d4.jsonl -->
- User asked about memsearch architecture and how chunks are deduplicated
- Agent explained the SHA-256 content hashing mechanism in chunker.py
- Agent showed the composite chunk ID format: hash(source:startLine:endLine:contentHash:model)
### 15:12
<!-- session:a1b2c3d4 transcript:~/.openclaw/agents/main/sessions/a1b2c3d4.jsonl -->
- User asked to add a new embedding provider for Cohere
- Agent created src/memsearch/embeddings/cohere.py with CohereProvider class
- Agent registered the provider in embeddings/__init__.py factory
- Added cohere to optional deps in pyproject.toml
## Session 17:30
### 17:30
<!-- session:e5f6g7h8 transcript:~/.openclaw/agents/main/sessions/e5f6g7h8.jsonl -->
- User reported search returning stale results after re-indexing
- Agent identified the issue: Milvus Server stats lag after upsert
- Explained that stats update after segment flush/compaction, but search works immediately
The <!-- session:... transcript:... --> anchors enable L3 drill-down: the memory_transcript tool parses these to locate and read the original JSONL conversation.
Plugin Files¶
plugins/openclaw/
├── package.json # npm package with openclaw peer dependency
├── openclaw.plugin.json # Plugin config schema (kind: memory)
├── index.ts # Main plugin source: tools, hooks, helpers
├── index.js # Built runtime entry loaded by OpenClaw
├── install.sh # Installation script
├── skills/
│ └── memory-recall/
│ └── SKILL.md # Decision guide for memory tools
└── scripts/
├── derive-collection.sh # Per-agent collection name derivation
└── parse-transcript.sh # OpenClaw JSONL transcript parser
| File | Purpose |
|---|---|
package.json |
npm package with openclaw >=2026.3.11 as peer dependency |
openclaw.plugin.json |
Plugin configuration schema: provider, autoCapture, autoRecall settings |
index.ts |
Main plugin source. Registers 3 tools (factory pattern), 3 event hooks, and CLI subcommand |
index.js |
Built runtime entry required by OpenClaw 2026.5+ plugin installs |
install.sh |
Installation script: checks memsearch availability, registers plugin |
SKILL.md |
Memory recall skill guide -- helps the LLM decide when and how to use the memory tools |
derive-collection.sh |
Generates deterministic per-agent Milvus collection names |
parse-transcript.sh |
Parses OpenClaw JSONL transcripts into readable [Human]/[Assistant] format |