openclaw-factmesh
v0.1.1
Published
OpenClaw memory plugin backed by a local Lichen fact-mesh — honest recall with confidence tiers and coverage, write-through capture, fully offline, zero API cost.
Maintainers
Readme
factmesh — OpenClaw memory plugin backed by Lichen
Memory for OpenClaw agents, served by a Lichen fact-mesh running on your own
machine. Lichen is a grown-not-trained knowledge store: facts reinforce on use, decay on disuse, carry
confidence tiers (verified / observed / inferred / stale), and every recall reports structured coverage
({score, level, gaps}) — so the memory says "I have little on this" as data, instead of letting the agent
hallucinate.
Works offline. Zero API cost. Honest recall. Retrieval is pure mesh math (hybrid dense + BM25 fused by
RRF) — no LLM is called to remember, no token leaves the machine, and Lichen binds 127.0.0.1 only.
What it does
| Hook / surface | Behavior |
| --- | --- |
| before_prompt_build | POSTs the current user turn to Lichen /recall and injects the top-k facts via prependContext, truncated to a configurable token budget. Low coverage adds an explicit "do not treat as authoritative" note. |
| agent_end, before_compaction | Write-through capture: deterministic, pattern-based extraction of user-stated durable facts (remember: ..., I prefer ..., my X is ...) POSTed to /learn. No LLM extraction. Assistant text is never captured. Disable with capture: false. |
| Tool memory_search | Query the mesh (/recall); returns facts with confidence/stability/availability + coverage. |
| Tool memory_add | Teach a fact (/learn). Exact dupes reinforce; Correction: ... can supersede. |
| Tool memory_forget (optional, allowlisted) | Honest surface: Lichen v1 has no hard delete. This records a bad outcome (/outcome {good:false}) so the fact decays up to ~2× faster. To correct, use memory_add with a correction. True deletion is a Lichen-side TODO. |
| CLI openclaw factmesh status | GET /health — version, fact/edge counts. |
| CLI openclaw factmesh search <query> | POST /recall, prints facts with confidence tiers + coverage. |
A down or slow Lichen never breaks a turn: prompt-recall fails silent (debug log), capture fails silent, every HTTP call has a hard timeout (default 4s).
Install
openclaw plugins install openclaw-factmesh
# or point config at a local checkout directlyOpenClaw 2026.7+ requires two packaging files, both shipped here: openclaw.extensions in package.json
(pointing at ./index.js) and the openclaw.plugin.json manifest in the plugin root. Because the manifest
declares kind: "memory", the installer switches plugins.slots.memory to factmesh for you.
~/.openclaw/config.json:
{
"plugins": {
"entries": {
"factmesh": {
"enabled": true,
"source": "/path/to/factmesh-openclaw/index.js",
"config": {
"lichenUrl": "http://127.0.0.1:4174",
"tokenBudget": 600,
"capture": true,
"recallK": 6,
"timeoutMs": 4000
},
"hooks": {
"allowPromptInjection": true,
"allowConversationAccess": true
}
}
},
"slots": { "memory": "factmesh" }
}
}The hooks opt-ins are required for non-bundled plugins in 2026.7+: allowPromptInjection lets the
recall hook mutate the prompt, allowConversationAccess lets agent_end read user messages for capture.
Without them the hooks are blocked (warn-level diagnostics). plugins.slots.memory makes factmesh the
exclusive memory backend (verified real in 2026.7.1-2). To downweight memories via chat, allowlist the
optional tool: "agents": { "list": [{ "id": "main", "tools": { "allow": ["memory_forget"] } }] }.
Config
| Key | Default | Meaning |
| --- | --- | --- |
| lichenUrl | http://127.0.0.1:4174 | Where the Lichen brain listens. |
| tokenBudget | 600 | Max tokens of recalled facts injected per prompt (chars/4 estimate — approximate, leave headroom). |
| capture | true | Write-through capture on agent_end / before_compaction. |
| recallK | 6 | Facts recalled per turn (capped at Lichen's 24-candidate pool). |
| timeoutMs | 4000 | Hard HTTP timeout; a hung Lichen can never stall a turn. |
Layout
index.js plugin definition: hooks (typed api.on), tools, CLI, config schema
openclaw.plugin.json native plugin manifest (id, inline configSchema, kind: "memory", contracts.tools)
lib/client.js zero-dep Lichen HTTP client (fetch + hard timeout)
lib/extract.js deterministic user-fact extraction (patterns only, no LLM)
lib/budget.js chars/4 token estimation + greedy budget fitting
test/run.js npm test — 22 tests (see "Tested" below)Tested / not tested (honest)
Tested (npm test, Node 24, 22/22 green):
- extraction patterns (explicit/stated, assistant text never captured, dedupe, malformed input never throws)
- token-budget fitting
- plugin
register()wiring against a mock OpenClaw api (hooks/tools/CLI registered; dead Lichen fails silent) /recallend-to-end against a temp copy of the Lichen server on a scratch port with its own tiny mesh (relevant facts + confidence/stability/availability metadata + coverage; retrieval-only — no LLM fields, ~80ms; empty query 400;khonored; clientlearnText → recall → outcomeround-trip)GET /healthagainst the live Lichen (read-only —/learnis never posted to the live brain)
Tested against a real OpenClaw 2026.7.1-2 gateway (2026-08-13, scratch Lichen on 127.0.0.1:4199, Ollama phi4-mini, no channels):
- Install from a local path: requires
openclaw.extensionsin package.json andopenclaw.plugin.jsonin the plugin root (manifest withid+ inlineconfigSchema;kind: "memory"makes the installer switchplugins.slots.memoryto factmesh automatically — the slot key is real in this version). - All 3 tools register and are served to the model without TypeBox — but the schema field must be named
parameters(plain JSON Schema object),executeis called as(toolCallId, params), and results must be{ content: [{ type: "text", text }], details? }. The pre-2026.7 shape (input, single-arg execute,{ result, details }) is rejected: "plugin tool is malformed: missing parameters object". - Hooks must use the typed API
api.on(event, handler)— the legacyapi.registerHookfeeds the internal-hook system the agent harness never consults (hooks "register" but never fire).api.registerHookis kept as a fallback for older hosts. - Non-bundled plugins need explicit hook opt-ins in the entry config:
"hooks": { "allowPromptInjection": true, "allowConversationAccess": true }(recall injection is a prompt-mutating hook;agent_endreads conversation content). - Verified live: recall injection into the prompt (
LICHEN MEMORYblock, +203 prompt chars, model used the fact), write-through capture onagent_end("remember: ..." →/learn,src: openclaw:agent_end), assistant text never captured,openclaw factmesh status/openclaw factmesh search <q>both work. - CLI registration requires explicit command metadata:
api.registerCli(registrar, { descriptors: [...] }).
Not tested:
- Model-driven tool calls end-to-end — phi4-mini 3.8b emitted tool-call JSON as plain text instead of a
structured invocation ("Assistant reply looks like a tool call, but no structured tool invocation was
emitted"). The tool definitions reach the model (it quoted
memory_add+parametersunprompted); whether a tool-capable model completes the call is unverified. before_compactionfiring (no compaction occurred during the test);memory_forgetend-to-end (optional tool, not allowlisted in the test).
Notes
- Requires Lichen ≥ the 2026-08-13
/recallroute (restart Lichen after pulling it). On older Lichen the recall hook andmemory_searchfail silent / return a structured error; capture andmemory_forgetstill work. - Package name is unscoped
openclaw-factmesh(scoped@factmesh/…would require owning the npm org; OpenClaw's own docs use the unscopedopenclaw-plugin-*convention). Published as 0.1.x on npm. - Plain CommonJS JavaScript, zero runtime dependencies. Node ≥ 18 (OpenClaw itself requires 22).
Ecosystem
- lichen-core — the brain this plugin remembers through (free, MIT).
- The Local AI Field Manual — the field guide to running this whole stack on ordinary hardware. Pay-what-you-want; the software stays free.
If factmesh earns it, the storefront above is where a tip lives — USDT or ETH on Base, no accounts.
