For upgrade instructions (migration steps, opt-in features, verification commands), see docs/guides/upgrading.md. This file is the chronological feature record, newest first.
setup openclaw + --help short-circuit (issue #11)v0.10.4 fixes yoloshii/ClawMem#11. Two bugs reported by @elquercarlos:
clawmem setup openclaw ignored OPENCLAW_STATE_DIR and OpenClaw’s --profile flag. Pre-v0.10.4 hardcoded ~/.openclaw/extensions/clawmem and never consulted env vars or OpenClaw’s own destination-resolution logic. Users running OpenClaw with a non-default profile (e.g. ~/.openclaw-dev) got the plugin installed in the wrong directory, where their active profile couldn’t see it.clawmem setup openclaw --help ran setup instead of printing help. The handler had no argv short-circuit for --help / -h.Both bugs close on this release. Vault on disk is byte-identical to v0.10.3. No schema changes, no env-var changes for default-profile users, no retrieval-pipeline or hook changes. Pure bun update -g clawmem upgrade.
cmdSetupOpenClaw — three-path install (§28.1)The setup command now picks one of three paths at runtime:
PATH). Spawns openclaw plugins install <pluginDir> --force. OpenClaw owns destination resolution (which respects OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH, and the --profile flag), runs manifest validation + security scans, persists install records, applies slot selection, and refreshes the registry. The plugin is auto-enabled by the install — the post-install “Next steps” output no longer prints openclaw plugins enable clawmem. --force makes the install idempotent across re-runs (OpenClaw’s default install mode rejects existing targets).--link flag, OpenClaw CLI on PATH). Spawns openclaw plugins install <pluginDir> -l, which records the source in plugins.load.paths — a load-path entry, not a filesystem symlink. Discovery uses the recorded load-path entry directly, so the v2026.4.11 symlink-discovery skip does NOT apply here. ClawMem does manual stale-install cleanup before delegating because OpenClaw rejects --force with --link.cpSync (or filesystem symlink with --link) at a destination resolved by a faithful mirror of OpenClaw’s resolveConfigDir: OPENCLAW_STATE_DIR → OPENCLAW_CONFIG_PATH (config root = dirname(file)) → OPENCLAW_HOME/HOME/USERPROFILE/os.homedir()/cwd → ~/.openclaw. The user gets a warning surfacing reduced capability (no manifest validation, no security scan, no install records). Filesystem symlink in the fallback’s --link path is still subject to OpenClaw v2026.4.11+’s discovery skip — install OpenClaw to get the cleaner delegated behavior.The faithful-mirror resolver matches OpenClaw exactly, including the asymmetry where OPENCLAW_STATE_DIR / OPENCLAW_CONFIG_PATH apply only .trim() (so OPENCLAW_STATE_DIR="undefined" is a literal directory name) while home-resolution env vars filter the literal strings "undefined" / "null" (matching OpenClaw’s home-dir.ts:normalize). Diverging here would mean the delegated path and the fallback path install into different locations for the same env, which is exactly the bug class §28.1 set out to fix.
--remove — legacy-compatible uninstallclawmem setup openclaw --remove now tries openclaw plugins uninstall clawmem --force first (when the CLI is available) and falls back to manual cleanup at the resolved extensions path. The fallback runs in two cases:
extensions/clawmem path and removes any remaining symlink or directory — a “constrained stale cleanup” that handles the side-by-side case of a managed-link install plus a leftover unmanaged-copy directory from an earlier ClawMem version.--help / -h short-circuit (§28.2)cmdSetupOpenClaw short-circuits --help / -h at the top of the handler before any spawn or filesystem work and prints the full flag + env-var reference. Documents: --link (with separate behavior in delegated load-path mode vs filesystem-symlink fallback), --remove, env vars consulted (OPENCLAW_STATE_DIR, OPENCLAW_CONFIG_PATH, OPENCLAW_HOME, HOME, USERPROFILE), and example invocations including the headline OPENCLAW_STATE_DIR=~/.openclaw-dev clawmem setup openclaw.
The change set was validated against four turns of GPT-5.5 high-reasoning adversarial code review (cumulative ~292K tokens) under codex exec. All findings — three HIGH (delegated-install auto-enable messaging, idempotence-via---force, legacy-compatible --remove), two MEDIUM (resolver fidelity, real-stub-binary integration tests), two LOW (assertion tightening, main --help line) — were addressed before final clearance. The Turn 4 verdict was an explicit “zero remaining concerns, ready to ship v0.10.4.”
Test coverage: 8 unit tests (tests/unit/openclaw-paths.test.ts) on the resolver helpers, including precedence (OPENCLAW_STATE_DIR over OPENCLAW_CONFIG_PATH), tilde expansion, OPENCLAW_HOME priority, os.homedir() failure → cwd fallback, and the asymmetric "undefined" / "null" literal handling that mirrors OpenClaw exactly. 6 integration tests (tests/integration/setup-openclaw.integration.test.ts) exercise the real subprocess boundary via a per-command shell stub on a sandboxed PATH: copy mode passes --force and not -l; link mode passes -l and not --force; install failure aborts (no silent fallback to direct copy); --remove with a managed install runs CLI uninstall AND constrained stale cleanup; --remove with a legacy install falls back to manual cleanup with the user-visible warning; CLI absent honors OPENCLAW_STATE_DIR in direct-copy mode. Two further integration tests prove the dual next-steps messaging differs between paths and that --help short-circuits before the openclaw --version probe.
kind: memory), and Hermes plugin contract are all unchanged.src/openclaw/) are unchanged. The change is entirely in cmdSetupOpenClaw and a new src/openclaw-paths.ts helper module.tests/unit/openclaw-plugin.test.ts (84 source-text assertions) all still pass — the Path 3 fallback branch preserves the original next-steps output verbatim.BACKLOG.md Source 28 — full scope including the codex-validated implementation planopenclaw/src/cli/plugins-install-command.ts:669 (linked-path branch), openclaw/src/cli/plugins-install-persist.ts:182 (auto-enable + slot selection), openclaw/src/utils.ts:119 (resolveConfigDir)src/openclaw-paths.ts (mirrors OpenClaw’s path-resolution semantics for the fallback path)v0.10.3 is a small patch release. The retrieval pipeline, composite scoring, vault format, hook set, agent tool surface, OpenClaw plugin registration shape, and Hermes plugin contract are all unchanged from v0.10.2. The release hardens the A-MEM JSON parser against a class of noisy llama-server outputs that v0.10.2 mishandled (parser silently picked an example/schema literal over the real payload, link-generation batches got zeroed when the LLM under-delivered), and bundles four sitting-in-tree doc updates that were waiting for the next release.
Vaults from v0.10.2 are byte-identical at rest. No schema migration. Default behavior is byte-identical for users whose llama-server outputs were already parsing cleanly — the parser only changes what it returns on inputs that previously fell through to repair paths or to zeroed link batches. Pure bun add -g clawmem upgrade.
The pre-PR parser had two real-world failure modes when the llama-server LLM emitted prompt-shaped prose alongside the real payload:
Return empty array [] if no structured facts found. before the real fenced JSON answer, the parser locked onto the prose [] and returned it as the result. Conversation-synthesis runs that included the actual prompt wording in the assistant context exhibited this regularly. After the fix, the later real payload wins via a precedence order that walks all parseable balanced JSON candidates in source order, prefers payload-cued candidates (Actual:, Result:, Final answer:, Answer:), then avoids example-cued (example, e.g., schema) and inline-prose literals, then falls back to the first candidate. parseJsonCandidate now searches forward for a later line-start [/{ when the first balanced candidate sits behind an example cue or at a non-line-start position with no payload cue. extractJsonFromLLM tightens its precedence so that outside-of-fences JSON before a preferred json fence requires a payload cue rather than winning by virtue of position.generateMemoryLinks enforced an all-or-nothing completeness gate: if the LLM returned 4 valid links for a 5-neighbor prompt, or repeated a target_idx, or referenced an out-of-range index, the entire batch was discarded and 0 links were created. After the fix, partial-valid insertion semantics are restored: a 5-neighbor prompt with 4 valid returned links inserts 4 rows. Duplicate target_idx entries are logged (Skipping duplicate link target N) and skipped after the first valid link for that neighbor. Out-of-range entries are logged (Skipping out-of-range link target N) and skipped instead of aborting already-valid links. The commit message includes the directive “Do not reintroduce all-or-nothing link generation without corpus measurements” to lock the contract.Item-shape validation in parseLinkGenerationFromLLM (src/amem.ts:55,99) is unchanged and continues to reject malformed items strictly (missing fields, wrong types, non-finite confidence, bad relation type, non-positive/non-integer target_idx) before they reach the insert loop.
The PR went through three adversarial review rounds: Turn 1 surfaced 3 findings (1 HIGH on the prose-precedence behavior + 2 Medium on the under-delivery zeroing path and a sanitization edge), Turn 2 confirmed Findings 2-3 fixed and surfaced 1 HIGH on a regressed prose-fence ordering case + 1 MEDIUM on an un-flagged completeness gate that wasn’t parser-level. Turn 3 verified both Turn 2 findings fixed (cymkd ran an independent GPT-5.5 high-reasoning pass before pushing the Turn 2 follow-up commit) and surfaced 2 LOW findings deferred to a future release (see below).
Both LOWs were surfaced by the Turn 3 GPT-5.5 high-reasoning review pass and are explicit-acceptance candidates per the contributor’s own “intentionally broad for this repro; can be tightened later” framing:
json-fence precedence change. The new outsidePrecedesPreferredJsonFence gate causes raw line-start JSON before a non-example json fence to lose to the fence. Repro: [{"key":"real"}]\n followed by a json fence containing [] parses as []. Real behavior change from “first raw JSON wins,” but the affected pattern is narrow — well-formed raw payload immediately followed by a non-example json fence is uncommon in observed llama-server output, and the broader fix the gate enables (the prose-before-fence repro above) is the more frequent failure mode.schema cue breadth. hasExampleCueBefore recognizes schema alongside example and e.g. to fix the reported Schema: {...}\n[{...real...}] repro, but the substring match is broad enough to suppress real fenced payloads following phrases like Schema validation result:. A tighter schema: / json schema: boundary regex would close the false-positive without losing the original repro coverage.Both will be addressed in the next release with measurement-backed fixes — either tightening the heuristics with a corpus-measurement pass over real llama-server output, or accepting them with regression tests locking in the new contract. That decision belongs with the data, not this PR.
Four user-facing doc updates that accumulated in the working tree across the v0.10.2 → v0.10.3 window, all from in-session OpenClaw delta surveys:
src/openclaw/index.ts line-ref drift (cosmetic, no behavior change). OpenClaw advanced from v2026.4.21 to HEAD 1f724bc50b (2026-05-04, post-v2026.4.26 untagged) across two consecutive surveys. attempt.ts was reorganized under src/agents/pi-embedded-runner/run/ as part of a runner refactor; line refs in our doc-comment correctness contracts shifted twice: before_prompt_build await context attempt.ts:1873 → :2294 → :2610; agent_end fire-and-forget block attempt.ts:2470-2496 → :3023-3048 → :3379-3402. Both await/fire-and-forget contracts hold across both bumps; only line numbers shifted. 4 occurrences updated in the file header docstring and the before_prompt_build / agent_end handler comments.README.md, CLAUDE.md, AGENTS.md, SKILL.md — 30s agent_end void-hook timeout disclosure. OpenClaw v2026.4.26 (commit 4d4c7c8ab3) introduced DEFAULT_VOID_HOOK_TIMEOUT_MS_BY_HOOK = { agent_end: 30_000 } in src/plugins/hooks.ts. A timed-out handler is logged (“timed out after 30000ms”) and the runner continues, but the plugin’s underlying work is not cancelled. ClawMem’s agent_end runs decision-extractor + handoff-generator + feedback-loop; warm-cache postrun is well under 30s, but cold-start indexing or LLM stalls could approach this. The disclosure is operational, not behavioral — it doesn’t change what ClawMem does, only adds a log warning above the 30s threshold for users with cold-start scenarios that exceed it. Single-sentence insertion in each doc, adapted to surrounding tone, fail-open framing emphasized. CLAUDE.md and AGENTS.md remain byte-identical post-edit.Per the option-(a) standing direction these doc updates do not justify a release on their own; they ride PR #7 which is the next real-functionality release.
tests/unit/amem.test.ts + tests/unit/conversation-synthesis.test.ts + tests/integration/conversation-synthesis-two-pass.integration.test.ts (107 pass / 0 fail / 220 expect() calls, was 102 pre-PR; +5 new regression tests). Two Turn 3 LOW findings were explicitly deferred to the next release rather than chasing a fifth round, in line with cymkd’s own forward-looking framing on the schema cue breadth.PR #7 adds five new regression tests in tests/unit/amem.test.ts covering: prose [] before a fenced real payload, the actual conversation-synthesis prompt wording echoed before a fenced payload, prose schema object before a later real raw array, parseLinkGenerationFromLLM parsing the later real link array instead of the schema object, and generateMemoryLinks inserting a valid partial batch while skipping duplicate/out-of-range targets.
Targeted suite: 107 pass / 0 fail / 220 expect() calls across the three test files (was 102 pre-PR; +5 new tests).
v0.10.2 is a small patch release. The retrieval pipeline, composite scoring, vault format, hook set, agent tool surface, and OpenClaw plugin registration shape are all unchanged from v0.10.0/v0.10.1. The release adds three opt-in env vars for users running ClawMem against OpenAI-compatible remote LLM proxies, fixes a /v1 URL-doubling edge case in the remote LLM transport, and applies two small doc updates that were sitting in the working tree.
Vaults from v0.10.1 are byte-identical at rest. No schema migration. Default behavior is byte-identical for users with no new env vars set — same hard-coded qwen3 model, same /no_think suffix, same request body field order, same http://localhost:8089/v1/chat/completions URL. Pure git pull upgrade.
The remote LLM transport in src/llm.ts previously hard-coded model: "qwen3" and the /no_think prompt suffix in the /v1/chat/completions request body. That worked for the QMD native combo (qmd-query-expansion-1.7B on :8089 via llama-server) but blocked anyone wanting to point ClawMem at an OpenAI-compatible proxy with a different model name or at a non-Qwen endpoint that treats /no_think as literal prompt text. v0.10.2 adds three opt-in env vars to make all three knobs user-configurable while preserving the existing defaults exactly:
| Env var | Default | Effect |
|---|---|---|
CLAWMEM_LLM_MODEL |
qwen3 |
Model name sent in the request body. Override for OpenAI-compatible proxies (e.g. gpt-5.4-mini). |
CLAWMEM_LLM_REASONING_EFFORT |
(unset, field omitted) | Optional top-level reasoning_effort field for Chat Completions endpoints that support it. Validated against the enum none / minimal / low / medium / high / xhigh; unsupported values log a warning and are ignored. Leave unset for llama-server / vLLM unless your serving stack explicitly accepts the field. |
CLAWMEM_LLM_NO_THINK |
true |
Append /no_think to remote prompts. Set false for standard OpenAI models and other endpoints that reject or treat the Qwen-style suffix as literal prompt text. |
The settings are threaded through both plugin layers:
src/openclaw/openclaw.plugin.json exposes gpuLlmModel, gpuLlmReasoningEffort (with the same enum constraint), and gpuLlmNoThink config keys. src/openclaw/index.ts maps each to the corresponding CLAWMEM_LLM_* env var only when the user explicitly set it (so unset config falls through to the runtime default rather than overriding it with an empty string).src/hermes/__init__.py adds the three keys to its get_config_schema() (all secret: False) and to the env-passthrough tuple in initialize(). The module docstring lists them alongside the existing CLAWMEM_LLM_URL./v1 URL doubling fixThe pre-PR transport posted to ${remoteLlmUrl}/v1/chat/completions unconditionally. If a user set CLAWMEM_LLM_URL=https://api.example.com/v1 (a common shape for OpenAI-compatible proxies that document their base URL with /v1 already included), the request went to https://api.example.com/v1/v1/chat/completions and 404’d. v0.10.2 introduces buildRemoteChatCompletionsUrl() which strips trailing slashes, detects an existing /v1 suffix, and only appends /chat/completions in that case. All four shapes now resolve correctly: http://host, http://host/, http://host/v1, http://host/v1/.
normalizeRemoteLlmReasoningEffort() is now the single normalization point for the reasoning-effort value. Previously the env-bootstrap path did its own .trim().toLowerCase() + Set check and the constructor did nothing — so a direct caller passing LlamaCpp({remoteLlmReasoningEffort: " HIGH "}) would have posted "reasoning_effort":" HIGH " to the endpoint while the env path correctly normalized it to "high". The constructor now calls the helper for both paths, so env and direct config behave identically. CLAWMEM_LLM_MODEL is also trimmed at both surfaces (defense-in-depth — whitespace-padded values from .env-style configs no longer post "model":" gpt-5.4-mini ").
Two small doc changes that were sitting in the working tree from the v2026.4.18 / v2026.4.16-920 changelog surveys ride this release:
docs/guides/hermes-plugin.md — preventive warning added to the Install section: do NOT add clawmem to plugins.enabled in ~/.hermes/config.yaml. Hermes #11xxx onwards made all general plugins opt-in by default; plugins.enabled is the general-plugin opt-in roster, not the memory-provider activation channel. Memory providers are activated via memory.provider: clawmem, completely separate from the general plugin loader. Adding clawmem to plugins.enabled would cause the general loader to import it as a kind: standalone plugin and call register(ctx) against the general PluginContext — which doesn’t expose register_memory_provider, so the import errors and a warning gets logged. Harmless but noisy. The warning heads off the easy mistake for users reading the install path docs literally and conflating the two settings.src/openclaw/index.ts — cosmetic line-ref drift fix in the file-header docstring and correctness-contract comments. OpenClaw’s before_prompt_build is still awaited at attempt.ts:1873 (was :1642 at v2026.4.18 cutoff); agent_end is still fire-and-forget at attempt.ts:2470-2496 (was :2198-2224). No semantic change — the await/fire-and-forget contracts hold; only the line numbers in our reference comments shifted./v1 URL doubling fix, the validation centralization, and 5 new contract tests covering whitespace handling, URL normalization, env-vs-direct config consistency, and the byte-identical default-preservation contract. The PR went through two adversarial review rounds (gpt-5.4 high reasoning each side); the contributor independently identified and folded in fork-review extras (the /v1 fix being the standout) within the same PR scope. Cross-validated in their downstream DrJLabs/ClawMem fork before opening upstream — exactly the integration discipline the v0.7.x community-contributor track has been rewarding.PR #8 adds three new test files:
tests/unit/llm-remote-config.test.ts (new) — the contract tests for the new env vars and the /v1 URL buildertests/unit/hermes-plugin.test.ts (new) — covers the schema additionstests/unit/openclaw-plugin.test.ts extended — covers the env-mapping consistencyTargeted suite: 97 pass / 0 fail / 171 expect() calls across the three files (was 92 pre-PR; +5 new tests).
v0.10.1 is a small patch release. The retrieval pipeline, composite scoring, vault format, hook set, agent tool surface, and OpenClaw plugin registration shape are all unchanged from v0.10.0. The release covers two upstream changelog reviews against the OpenClaw and Hermes runtimes ClawMem integrates with — neither produced any breaking changes — plus one correctness fix in the Hermes plugin and a small set of doc updates that keep the public-facing surfaces aligned with the runtimes users are actually running.
Vaults from v0.10.0 are byte-identical at rest. No schema migration, no new env vars, no new dependencies. Pure git pull upgrade for Claude Code users; clawmem setup openclaw re-run is optional (no plugin code changes that affect runtime behavior); Hermes users on a non-primary agent_context (subagent, cron, flush) get a quiet correctness improvement.
agent_context isolation in src/hermes/__init__.pyHermes’s MemoryProvider ABC docstring is explicit: “Providers should skip writes for non-primary contexts (cron system prompts would corrupt user representations).” Hermes’s run_agent.py passes an agent_context kwarg to every MemoryProvider.initialize() call with one of "primary", "subagent", "cron", or "flush". Pre-v0.10.1 the ClawMem plugin absorbed the kwarg via **kwargs and ran the full lifecycle for every context — including writing transcript turns and running decision-extractor / handoff-generator / feedback-loop / precompact-extract on cron and subagent passes. The vault’s saveMemory dedup limited the blast radius, but the cleaner answer is the one the ABC asks for.
v0.10.1 honours the contract. The plugin now reads agent_context in initialize() and gates only the write-side surfaces; the read-side surfaces still run for every context so non-primary agents continue to benefit from retrieval:
| Surface | Direction | agent_context != "primary" |
|---|---|---|
session-bootstrap (in initialize) |
Read | Runs |
prefetch() / queue_prefetch() (context-surfacing) |
Read | Runs |
system_prompt_block() |
Read | Runs |
Agent tools (REST: clawmem_retrieve etc.) |
Read | Runs |
sync_turn() (transcript append) |
Write | Suppressed |
on_session_end() (extraction trio) |
Write | Suppressed |
on_pre_compress() (precompact-extract) |
Write | Suppressed |
When the active context is non-primary, initialize() logs a single info-level line for operator visibility:
clawmem: agent_context=cron — reads enabled, writes suppressed
Net effect: subagents and cron agents read the vault as before; they no longer write back into it. For installs that run only agent_context="primary" (the default for interactive CLI / Telegram / Discord platforms), behavior is unchanged.
$HERMES_HOME/plugins/clawmem/Hermes #10529 (in v2026.4.13+) added user-plugin discovery: plugins/memory/__init__.py now scans $HERMES_HOME/plugins/<name>/ in addition to the bundled hermes-agent/plugins/memory/<name>/. Both paths still work; the user-plugin path is preferred because it survives git pull of hermes-agent and avoids the dual-registration trap that previously caused duplicate tool names with strict providers. Discovery heuristic = grep __init__.py for register_memory_provider or MemoryProvider; both already present in src/hermes/__init__.py. README.md Install + Setup blocks, docs/guides/hermes-plugin.md, CLAUDE.md, AGENTS.md, and SKILL.md now lead with the user-plugin path and document the bundled path as a still-supported alternative.
A v2026.4.11 → v2026.4.18 changelog survey (1,794 commits) ran against ClawMem v0.10.0’s plugin. Verdict: no breaking changes. Three changes touch surfaces ClawMem consumes; all are documented in this release:
register() enforcement (2a283e87a7, in v2026.4.19-beta.1). OpenClaw now throws "plugin register must be synchronous" if a plugin’s register() returns a Promise. ClawMem’s register(api) in src/openclaw/index.ts is and always has been synchronous (no async, no top-level await, returns void) — every await lives inside per-event handlers, never in registration itself. Companion change: register failures now atomically roll back side effects, so any future throw inside register() will leave OpenClaw in a clean state. The constraint is now documented as a load-bearing invariant in CLAUDE.md / AGENTS.md.memory-core dreaming sidecar coexistence (5fde14b844, #65411, in v2026.4.18). When ClawMem owns the memory slot AND plugins.entries.memory-core.config.dreaming.enabled = true, OpenClaw now loads memory-core’s dreaming engine alongside ClawMem instead of unloading it entirely. Two valid configurations: ClawMem-only (default after openclaw plugins enable clawmem, with dreaming.enabled = false) or ClawMem + dreaming sidecar (opt-in, set dreaming.enabled = true). Documented in docs/guides/openclaw-plugin.md (new “Coexistence with memory-core dreaming sidecar” section), README.md, CLAUDE.md, AGENTS.md, SKILL.md.attempt.ts line drift. OpenClaw’s before_prompt_build is still awaited; agent_end is still fire-and-forget. Their line numbers shifted between v2026.4.11 and v2026.4.18 (:1661 → :1642 and :2226-2249 → :2198-2224). The four references in src/openclaw/index.ts (file docstring + correctness-contract comments) were updated accordingly. No semantic change.on_memory_write deliberately not opted intoHermes #10507 added an on_memory_write bridge to the sequential tool execution path; the commit message names ClawMem as a beneficiary. v0.10.1 deliberately stays opted out. ClawMem’s filesystem watcher already indexes Hermes’s MEMORY.md / USER.md if those files live under a configured collection — layering an on_memory_write shell-out on top duplicates filesystem watching and introduces a remove-semantics mismatch (a hook event saying “this entry was removed” cannot be cleanly translated into ClawMem’s content-addressed vault). The docs/guides/hermes-plugin.md lifecycle table now documents this rationale instead of the prior “future” framing.
### External credit subsection added to v0.8.4 in this file naming @saschabuehrle (Lemony.ai founder) for PR #6 (fix/issue-5). The PR was closed-as-superseded on 2026-04-11 because v0.8.4 ended up shipping the broader auto-install fix, but the gateway-restart-before-slot-assignment insight is preserved verbatim in v0.8.4’s printed next-steps and the code comment that documents the constraint. Matches the v0.10.0 ### External credit precedent set for @withx. The credit landed in the working tree before the v0.10.1 cut and rides this release.
No new tests in v0.10.1 — the agent_context guard’s behavior is observable only against a live Hermes runtime, and the rest of the release is documentation. Full v0.10.0 baseline holds: 1204 pass / 0 fail, 2339 expect() calls across 66 test files.
v0.10.0 is the OpenClaw pure-memory migration. ClawMem’s OpenClaw plugin is no longer a kind: context-engine plugin wrapping a ClawMemContextEngine class. It is a kind: memory plugin that wires every lifecycle event (before_prompt_build, agent_end, before_compaction, session_start) through OpenClaw’s plugin-hook bus directly. This matches the direction OpenClaw’s own context-engine slot has taken since v2026.4.x (narrowed to runtime compaction) and moves ClawMem to the slot it was always logically targeting: the exclusive memory slot, alongside memory-core and memory-lancedb (which it automatically displaces when enabled).
Over the last year, the OpenClaw and Hermes maintainers independently converged on the same architectural split: agent runtimes expose two distinct plugin surfaces, one for memory (persistent, cross-session, retrieval-first) and one for context engines (in-session, lossless or lossy compression, compaction-first). Hermes shipped its MemoryProvider ABC from the start, and ClawMem has always been plugged in there correctly — as a memory provider. OpenClaw’s plugin kind vocabulary took longer to stabilize, with context-engine originally broad enough to accommodate both roles, then narrowing through v2026.4.x to what it is today: runtime compaction / compression specifically.
Under the stabilized definition, ClawMem is a memory layer, not a context engine. A memory layer maintains a durable index of prior sessions, decisions, and knowledge and serves them back into new conversations via retrieval. A context engine reshapes the live session window: compressing old turns, summarizing transcripts, handing off between models. These are different jobs on different time axes. Calling ClawMem a “context engine” was accurate for the ClawMem-as-OpenClaw-plugin wiring pre-v0.10.0 only because no other slot existed yet. Post-v0.10.0 it is misleading in both directions: it mislabels what ClawMem does, and it blocks OpenClaw users from pairing ClawMem with a genuine context-engine plugin (e.g. an LCM-compression plugin like lossless-claw) because the two would fight over the same slot.
On v0.10.0, OpenClaw users get the same composability Hermes users have had all along:
clawmem — cross-session retrieval, knowledge graph, decision extraction, the 5 agent toolslossless-claw)Both can be enabled at the same time. They do not overlap. The memory slot is about what you knew yesterday; the context-engine slot is about what fits in today’s prompt. This is the end state the OpenClaw maintainers have been steering toward for several releases, and v0.10.0 is ClawMem moving to the seat that was prepared for it. For Hermes users nothing changes — ClawMem has always been correctly integrated as a memory provider there.
The retrieval pipeline, composite scoring, profiles, vault format, hook set, <vault-context> output, and the 5 registered agent tools are unchanged. No schema migration, no new env vars, no new dependencies. The vault on disk is byte-identical to v0.9.0. Claude Code users who do not run OpenClaw can upgrade with no action beyond git pull.
v0.10.0 also fixes a packaging bug that surfaced when OpenClaw shipped v2026.4.11. The new discovery path (readdirSync({ withFileTypes: true }) + dirent.isDirectory()) started silently skipping ClawMem’s symlinked plugin directory. Diagnosis, confirmation via an external user on yoloshii/ClawMem#5, and the complete fix (discovery manifest + copy-not-symlink install) are all in this release.
context-engine to memory. The adapter in src/openclaw/ registers as kind: memory. OpenClaw’s exclusive memory slot now holds clawmem (via openclaw plugins enable clawmem, which also disables any competing memory plugin in the same step). The older plugins.slots.contextEngine: "clawmem" pattern no longer applies — v0.10.0 does not occupy that slot.ClawMemContextEngine class removed. Every lifecycle surface the plugin needs — prompt injection, post-turn extraction, pre-compaction state capture, session bootstrap — is now a plain PluginHookName handler on the plugin-hook bus. before_prompt_build is the load-bearing path: it runs prompt-aware retrieval (context-surfacing every turn + cached bootstrap context on first turn) AND runs precompact-extract synchronously when token usage approaches the compaction threshold, so state is captured before the LLM call that could trigger compaction on this turn (no race with the compactor). agent_end runs decision-extractor + handoff-generator + feedback-loop in parallel (fire-and-forget at OpenClaw’s call site). before_compaction is a defense-in-depth fallback only — fire-and-forget, races the compactor, exists solely to catch the rare case where before_prompt_build’s proximity heuristic missed a sudden token-count jump; it forces the precompact regardless of proximity since by the time it runs compaction is already in motion. session_start registers the session and caches the bootstrap context for first-turn injection. This is a strict improvement over v0.3.0’s shape, where the pre-emptive extraction happened inside ContextEngine.compact() via delegateCompactionToRuntime() — the v0.10.0 wiring moves the extraction up the stack into before_prompt_build where it has a real pre-LLM hook to await on.<vault-context> block is byte-equivalent to v0.9.0 on the same vault with the same prompt. This is a packaging and registration change, not a behavioral one. The pure-memory shape is the architecturally correct home for what ClawMem has always done — the old context-engine shape was load-bearing on an OpenClaw version that narrowed out from under us.The packaging fix is co-resident with §14.3 because they are both in the same file and both required for v0.10.0 to run on the current OpenClaw release. The symptom was reported externally by @withx on a fresh install of OpenClaw v2026.4.11 + ClawMem — the plugin looked installed to every CLI command but never registered at runtime.
src/openclaw/package.json is the new discovery manifest. OpenClaw v2026.4.11’s discoverInDirectory reads package.json and checks for the openclaw.extensions: ["./index.ts"] field before descending into a candidate plugin directory. Pre-v0.10.0 ClawMem shipped openclaw.plugin.json as the only manifest. That file is still shipped and still parsed at runtime, but it is not enough on its own for discovery on v2026.4.11+ — the plugin directory is silently skipped. v0.10.0 adds package.json to the plugin source tree and clawmem setup openclaw now verifies it is present before copying.clawmem setup openclaw defaults to recursive copy, not symlink. OpenClaw v2026.4.11’s discoverer walks the extensions directory with readdirSync({ withFileTypes: true }) and uses dirent.isDirectory() to decide which entries to descend into. Symlinks to directories report isDirectory() === false on that API shape, so a symlinked plugin is silently skipped during discovery and never registers. Every ClawMem release since the OpenClaw plugin was introduced shipped a symlinked install — it worked on OpenClaw v2026.3.x but stopped working on v2026.4.11. cmdSetupOpenClaw now runs cpSync(..., { recursive: true, dereference: true }) to install the plugin as a real directory. A new --link opt-in flag preserves the old symlink behavior for local dev workflows and for older OpenClaw versions, with a warning that v2026.4.11+ discovery will skip the symlink.openclaw plugins enable clawmem. The setup command now prints openclaw plugins enable clawmem instead of openclaw config set plugins.slots.memory clawmem. The enable verb pre-validates that the plugin is in the discovered registry (so it only runs on a successful copy), switches the exclusive memory slot, and disables the previous occupant (memory-core, memory-lancedb) in a single command. The older config set pattern failed silently on v2026.4.11 because the slot validator rejected a plugin id that had not been discovered first.suspicious ownership (uid=X, expected uid=Y or root). This is a security feature (it prevents a gateway running as a privileged system user from loading code a less-privileged user dropped into its extensions directory). On single-user installs where the gateway runs as your own user account, the ownership check passes automatically. On deployments where the gateway runs as a dedicated system user (e.g. openclaw) different from the installer user (e.g. sciros), you must sudo chown -R <gateway-user>:<gateway-group> ~/.openclaw/extensions/clawmem after running setup. Documented in docs/guides/openclaw-plugin.md Install section and docs/troubleshooting.md OpenClaw section.+2 regression gates on top of the v0.9.0 test baseline, locking in the v2026.4.11 packaging fix:
tests/unit/openclaw-plugin.test.ts::cmdSetupOpenClaw defaults to copy mode (v2026.4.11+ compat) — asserts the cpSync(pluginDir, linkPath, ...) call exists in cmdSetupOpenClaw and the --link opt-in is parsed via args.includes("--link"). If a future edit flips the default back to symlink without also updating the assertion, the test fails loudly.tests/unit/openclaw-plugin.test.ts::src/openclaw/package.json declares openclaw.extensions (v2026.4.11+ discovery gate) — reads src/openclaw/package.json, asserts type === "module" and openclaw.extensions is an array containing "./index.ts". If the manifest is ever deleted or reshaped without updating this test, the suite fails before the change reaches release.Public test suite: 1105 → 1107, zero regressions. The one pre-existing assertion in the Shipping Condition 2 — setup-time migration text is present suite was updated from "plugins.slots.memory clawmem" to "openclaw plugins enable clawmem" to match the new next-steps output shape.
Captured on a representative multi-user install (VM 202: OpenClaw gateway runs as system user openclaw, ClawMem installed by admin user sciros, separate from himadmin):
[plugins] clawmem: plugin registered (kind=memory, bin=/home/sciros/clawmem/bin/clawmem, profile=balanced, budget=800)
[plugins] clawmem: registered 5 agent tools
[gateway] ready (7 plugins: acpx, browser, clawmem, device-pair, phone-control, talk-voice, telegram; 11.3s)
The runtime registration log line explicitly emits kind=memory, which is the §14.3 change proving the new registration path is live. The clawmem entry appears in the gateway ready line alongside the stock plugins, proving the packaging fix clears v2026.4.11’s discovery gate. The multi-user ownership check (suspicious ownership (uid=1001, expected uid=997 or root)) reproduced and cleared after sudo chown -R openclaw:openclaw ~/.openclaw/extensions/clawmem, which is now a documented step in docs/guides/openclaw-plugin.md for multi-user deployments.
GPT 5.4 High, session 019d72d5 (continues the session chain used from v0.7.1 through v0.9.0, now cumulative across 20+ turns). The §14.3 implementation reached zero remaining findings before the v2026.4.11 packaging gap was discovered. Codex was re-engaged with the packaging compat delta (new package.json, copy-default cmdSetupOpenClaw, next-steps rewrite, +2 regression gates, VM 202 e2e evidence) for a final pre-ship pass, same session.
v0.10.0 is drop-in safe for Claude Code users: cd ~/clawmem && git pull && systemctl --user restart clawmem-watcher.service. Nothing on the retrieval path changed.
OpenClaw users must also re-run clawmem setup openclaw (to switch the extensions dir from symlink to recursive copy), chown the new directory to the gateway user on multi-user installs, and restart the gateway. The full step-by-step is in docs/guides/upgrading.md. Upgrade OpenClaw to v2026.4.11+ first — v0.10.0’s setup and discovery behavior depend on v2026.4.11’s new plugin discovery contract.
<vault-facts> KG Injection + Session-Scoped Focus Topic BoostTwo context-surfacing upgrades. §11.1 adds a <vault-facts> SPO-triple injection block inside <vault-context> so the model gets structured “what is currently true about the entities in this prompt” alongside the existing <facts> (surfaced documents) and <relationships> (memory-graph edges) blocks. §11.4 adds a per-session topic steering lever (clawmem focus set "<topic>" --session-id <id>) that biases retrieval toward a declared topic for the duration of a working session without mutating any persisted state. Both changes live exclusively on the read path and are fail-open — the baseline pre-v0.9.0 <vault-context> shape is byte-identical when the new stages don’t fire, and every downstream stage (threshold, diversification, token budget, injection) is unchanged. v0.8.5 was the last feature release; v0.9.0 is drop-in safe: one idempotent expression-index migration, no breaking API changes, no required reindex/embed, no schema rewrites.
<vault-facts> knowledge-graph injection<vault-facts> comes from the raw prompt ONLY, via three independent paths: (a) a canonical vault:type:slug regex (e.g. default:project:clawmem), (b) proper-noun extraction validated via exact-match resolveEntityTypeExact that skips ambiguous names resolving to multiple types, and (c) a longer-first n-gram scan (3-gram > 2-gram > 1-gram, cased + lowercased) for technical vocabulary like forge-stack, oauth2, vm 202. Prompt-only is a HARD CONSTRAINT — entity seeds never come from surfacedDocs[i].body or any retrieval-phase field, so a topic-boosted off-topic doc from §11.4 cannot pollute the facts block with facts about unrelated entities.entity_id and the sourcePath from the first-matching path wins. For n-gram ties, longer n-grams outrank shorter ones (forge-stack as a 1-gram compound beats forge + stack as separate tokens).ClawMem and Bun, and the graph has ClawMem depends_on Bun), store.queryEntityTriples returns the triple from both sides (outgoing from ClawMem, incoming to Bun). buildVaultFactsBlock now dedupes by a stable ${subject}\u0000${predicate}\u0000${object} key before budgeting so the same fact isn’t emitted twice and budget isn’t spent twice. Per-entity maxTriplesPerEntity cap still applies BEFORE dedup (anti-monopoly is enforced per entity first, then dedup collapses cross-entity duplicates).factsTokens sub-budget — a new ProfileConfig.factsTokens field gives <vault-facts> a dedicated token allowance that cannot steal from the existing <facts> / <relationships> budget. speed=0 disables the stage entirely (zero overhead on the fast profile). balanced=200 tokens (~50 triples at default 4-char/token estimate). deep=250 tokens. Truncation always at the triple boundary, never mid-triple, never emits an empty block. If OVERHEAD >= budgetTokens the block is dropped — established blocks take priority.idx_entity_nodes_lower_name — store.ts adds CREATE INDEX IF NOT EXISTS idx_entity_nodes_lower_name ON entity_nodes(LOWER(name), vault) on first open. This expression index backs the new batchLookupNames query (WHERE LOWER(name) IN (...) AND vault = ?) which would otherwise degenerate to a full scan on large vaults. Idempotent, runs once, harmless if the binary is later rolled back to v0.8.5 (SQLite ignores the unused index).vaultInner). Per-entity queryTriples throw → skip that entity and continue. Budget too small for even one triple → drop the whole block. Any exception inside the if (profile.factsTokens > 0) { try { ... } catch {} } wrapper → degraded vault behaves identically to pre-§11.1. The baseline <vault-context> is always recoverable.clawmem focus set / show / clear CLI — three new subcommands write/read/delete a per-session focus file at ~/.cache/clawmem/sessions/<session_id>.focus (1024-byte cap, UTF-8, plain text). Session ID is resolved from --session-id <id>, then CLAUDE_SESSION_ID (Claude Code exposes this natively), then CLAWMEM_SESSION_ID. The focus file IS the primary signal read by context-surfacing — CLAWMEM_SESSION_FOCUS env var is a debug-only override that is NOT session-scoped and should not be used in multi-session deployments. CLAWMEM_FOCUS_ROOT env override is supported for hermetic testing.context-surfacing threads it as the intent parameter to expandQuery + rerank + extractSnippet. This is the existing query-time intent lever (already Codex-approved in the query pipeline), so the topic steers query expansion variants, reranker priority, and snippet-extraction sentence selection along the same code paths as a manually-provided intent on an MCP query() call.applyCompositeScoring and BEFORE the adaptive threshold filter + memory-type diversification stages, docs matching ALL tokens of the focus topic (bag-of-words against title/path/body[:800], case-insensitive) get a 1.4× multiplier on compositeScore. Non-matching docs get a 0.75× demote (clamped at a 0.5 floor via Math.max(demoteFactor, 0.5)). The boost fires per-result and re-sorts the scored set, so matching docs rise and non-matching docs drop but stay eligible if they would have made the threshold without the demote.applyTopicBoost(...) early-returns without mutating compositeScore. Without this short-circuit, uniformly demoting every non-matching doc would push borderline docs below the downstream adaptive threshold and silently shrink the result set compared to the no-topic baseline — a direct regression against the approved §11.4 spec (“topic set + zero matching docs → proceed with the normal results”). The fix pre-computes a per-result match flag array in a single pass, checks if (!anyMatch) return scored, and only enters the mutation loop when at least one match exists.sessionId, never writes to SQLite, never mutates confidence / status / snoozed_until / archived_at / any lifecycle column. Concurrent sessions on the same host cannot cross-contaminate each other’s topic biasing. This is load-bearing for multi-session deployments (OpenClaw daemon, mission-control concurrent chats) — a global memory_snooze would not have worked for session-scoped noise suppression without polluting other sessions’ retrieval state.tests/integration/context-surfacing-topic-boost.integration.test.ts drive the real contextSurfacing(store, input) handler end-to-end against a hermetic in-memory SQLite store with redirected CLAWMEM_FOCUS_ROOT: (1) no-topic vs non-matching-topic produces byte-identical <vault-context> output, (2) the byte-equality invariant holds even on a 3-doc result set exercising the full threshold + diversification pass, (3) matching topic produces observably different output from the no-topic baseline (positive control for the boost being active). The byte-equality check is what Codex specifically asked for in Turn 1 — it is the only test shape that proves the fail-open contract survives the full hook pipeline and not just the isolated applyTopicBoost unit.GPT 5.4 High, session 019d72d5 (continues the session chain used from v0.7.1 through v0.8.5, cumulative ~7.5M tokens across 16+ turns). Design review cleared after 6 turns across both §11.4 and §11.1 together. Implementation reviews ran sequentially:
applyTopicBoost demoted every non-matching doc uniformly even when ZERO docs matched the topic, which under the adaptive threshold filter downstream would push borderline docs below the threshold and silently shrink the result set. Fixed with pre-computed match array + early-return no-op + hook-level integration test asserting byte-equality between the no-topic baseline and a non-matching-topic run. Turn 2 zero findings.<vault-facts> could duplicate the same fact when the prompt resolves both endpoints of one triple, because store.queryEntityTriples(entityId) defaults to direction='both' and the block had no cross-entity dedupe — the same line would appear twice and spend budget twice. Fixed with cross-entity (subject, predicate, object) dedup Set applied before budgeting + 2 new unit tests (same triple from both endpoints → one line; distinct triples sharing one endpoint → both lines preserved). Turn 2 zero findings, verbatim: “No remaining findings on §11.1. Yes, §11.1 is now cleared to ship.”All nine design approvals from the design-phase review survived implementation unchanged (three-path extraction, validate-then-count ordering, cross-path entity-id dedup, longer-n-gram tie-breaker, stage placement after vaultInner, index migration safety, the four accepted design deviations).
+96 tests across the unit + integration layers on top of the v0.8.5 baseline:
tests/unit/session-focus.test.ts (51 tests) — focus file round-trip, resolveSessionTopic env-var-precedence, applyTopicBoost boost/demote math, demote floor clamp, zero-match NO-OP, empty-array early return, Unicode topic handling, file-size overflow guard.tests/unit/vault-facts.test.ts (49 tests) — extractCanonicalIds regex boundary cases (interior hyphens, trailing hyphens, word boundaries), extractProperNouns title-case sequences, generateNgramCandidates longer-first ordering + length tagging + dedup, batchLookupNames vault isolation, extractPromptEntities three-path orchestration + path-(a/b/c) happy paths + validate-first invariant against long capitalized noise + exact-100 boundary + cross-path dedup + ambiguity skip, buildVaultFactsBlock budget handling + truncation at triple boundary + empty-block drop + per-entity max cap + fail-open on DB throw + cross-entity dedup + distinct-triples-sharing-endpoint preservation + schema migration PRAGMA index_list assertion.tests/integration/context-surfacing-topic-boost.integration.test.ts (3 tests) — zero-match fail-open byte-equality × 2 variants (single-result + multi-result), matching topic observably changes output vs the no-topic baseline.Public test suite: 1025 → 1105, zero regressions. Skill-forge clawmem backport test suite: 1022 pass, zero regressions (53 files, smaller baseline because skill-forge has fewer test files overall).
v0.9.0 is safe to drop in — the only behavior change on existing code paths is the additive <vault-facts> block and the session-scoped topic boost, both gated on new state (entity seeds from the prompt and a per-session focus file). Everything else is unchanged:
CREATE INDEX IF NOT EXISTS idx_entity_nodes_lower_name statement that runs idempotently on first open. No column additions, no data rewrites, no downtime.clawmem focus subcommand is additive. Existing CLI commands and MCP tools are signature-compatible.CLAWMEM_FOCUS_ROOT (override for the focus file root, primarily for hermetic testing) and CLAWMEM_SESSION_FOCUS (debug-only override, NOT session-scoped). The new factsTokens profile field defaults to 0 / 200 / 250 for speed/balanced/deep — existing custom profiles that don’t set it will receive factsTokens: 0 from the type default (stage disabled) unless you add it explicitly.clawmem setup hooks re-run needed. Claude Code invokes ${binPath} hook ${name} at runtime, so upgrading the ClawMem binary alone propagates the new context-surfacing behavior.<vault-facts> reads from existing entity_triples rows populated by the v0.8.5 SPO extraction pipeline, and §11.4 reads from existing documents rows. No re-enrichment needed to see the new blocks.Confirming the features are live:
clawmem focus set "authentication" --session-id test1 && clawmem focus show --session-id test1 should print the topic and the expected file path at ~/.cache/clawmem/sessions/test1.focus.sqlite3 ~/.cache/clawmem/index.sqlite "SELECT name FROM entity_nodes LIMIT 5"), the hook should append a <vault-facts> block inside <vault-context> on balanced/deep profiles. Check by running echo "tell me about <entity>" | clawmem surface --context --stdin.Fixes a bug cluster (BACKLOG.md §1.6) where entity_triples was stuck at zero rows on production vaults regardless of activity, making kg_query return empty for every entity and silently hollowing out the WHY / ENTITY graph-traversal paths in intent_search. Nine bugs across the decision-extractor → observer → entity-resolution → triple-storage pipeline were traced and fixed across four Codex review turns. The fix is entirely additive and strictly improves pre-v0.8.5 behavior — no schema changes, no API breaks, no required migration steps.
<triples> blocks alongside <facts>, parsed and validated in parseObservationXml against a fixed predicate vocabulary. The pre-v0.8.5 regex-based extractTripleFromFact required subject verb object sentence shape, which rejected the majority of real observation facts (descriptive phrases like “ClawMem now deploys via systemd user units on VM 202”). The LLM path extracts relational claims without sentence-shape constraints. VALID_PREDICATES is a tight 13-predicate set — adopted, migrated_to, deployed_to, runs_on, replaced, depends_on, integrates_with, uses, prefers, avoids, caused_by, resolved_by, owned_by — and the parser rejects anything outside it. LITERAL_PREDICATES marks prefers/avoids as literal-object predicates (object is stored as a string, not resolved to an entity).ensureEntityCanonical — the old path minted entity_nodes rows with entity_type='auto' (not a valid compatibility bucket, so those entities never resolved via kg_query — a major root cause of the empty KG). The new helper in src/entity.ts resolves to a canonical vault:type:slug entity ID shared with the rest of A-MEM, never writes 'auto', and — unlike upsertEntity — does NOT bump mention_count, so SPO triple references don’t inflate A-MEM’s doc-mention counter. INSERT OR IGNORE + deterministic makeEntityId handles concurrent-insert races correctly.resolveEntityTypeExact — when the observer emits a bare entity name, the helper inherits its entity_type from entity_nodes only if exactly one entity in the vault matches. Zero matches return null (caller defaults to concept); multiple matches across buckets (e.g., “Alice” as person AND as project) also return null instead of arbitrarily picking. SELECT DISTINCT entity_type ... WHERE LOWER(name) = LOWER(?) — exact-match-only, no fuzzy fallback, fails closed on ambiguity.{decision, preference, milestone, problem, discovery, feature} — the pre-v0.8.5 gate rejected roughly 77% of real observations in production vaults because it only accepted decision/preference/milestone/problem while the majority of observer output was discovery. The new SPO_ELIGIBLE_OBSERVATION_TYPES set includes discovery and feature (the two most common types for product-development work) while still excluding refactor/bugfix/change (noisy types that would dilute the KG).observations/${date}-${session8}-${type}.md, which collided on UNIQUE(collection, path) whenever two observations of the same type appeared in one session (common for discovery during deep coding sessions). The second insertDocument threw, was silently caught, and the observation + its triples were dropped. The new path bakes an 8-char SHA256 obsHash slice into the filename — deterministic (identical bodies still collide → idempotent reruns), collision-safe under a birthday bound for realistic session sizes. Persistence logic is now in an exported persistObservationDoc helper so regressions are unit-testable.OBSERVATION_SYSTEM_PROMPT placed literal example text (<fact>Individual atomic fact</fact>) inside tags the parser later extracted, and a weak 1.7B model occasionally echoed that text verbatim into production entity_triples rows. The new prompt uses ellipsis placeholders outside data tags, prose field rules below the XML block, and two parser defenses: an exact-string blocklist (SCHEMA_PLACEHOLDER_STRINGS) + a narrow shape-only regex (``, <!--...-->, ${...}). Applied to title, every <fact>, and every triple <subject>/<object>.kg_query canonical-ID fallback — previously, a callerside canonical ID like default:project:clawmem was run through searchEntities, failed to match as free text, then fed through a slugification fallback (entity.toLowerCase().replace(/[^a-z0-9]+/g, "_")) that fabricated invalid IDs and returned misleading “no facts found for X” responses. The fallback now regex-tests for the canonical shape (/^[a-z][a-z0-9-]*:[a-z_]+:[a-z0-9_]+$/) and round-trips the ID verbatim if it matches, otherwise returns an explicit “no entity found” message with a format hint. The tool description + parameter description updated to advertise canonical-ID acceptance. kg_query is a pure superset — entity-name callers see no behavior change.source_doc_id provenance on every triple — addTriple callsites now pass sourceDocId: wit.docId from the persisted observation, enabling downstream provenance queries and joining triples back to their originating observation document. Previously dropped silently. source_fact is the reconstructed subject predicate object string (not JSON.stringify) so human inspection is readable.tests/unit/triple-extraction.test.ts was a copy-paste of the (now deleted) regex helper, not testing production code. Replaced with tests/unit/spo-extraction.test.ts covering the full new pipeline: parser triple extraction (canonical predicates, unknown predicate rejection, missing-field rejection, oversize rejection, placeholder rejection, 5-triple cap, case normalization), placeholder filtering (exact strings + template shapes + legitimate Example: false-positive preservation), VALID_PREDICATES/LITERAL_PREDICATES sanity, ensureEntityCanonical (canonical creation, zero mention_count bump, reuse, vault scoping, no 'auto' leak), resolveEntityTypeExact (single match, ambiguity rejection, vault isolation), end-to-end triple insertion via canonical IDs with real source_doc_id, and persistObservationDoc collision-free + idempotent + fact-less behavior.019d72d5 (same session used for v0.7.1 through v0.8.4), 4 turns of review-and-fix. Turn 1 raised 3 categories of bugs and a design direction. Turn 2 raised 3 High findings on the Turn 1 fix plan (upsertEntity bumping mention_count, resolveEntityType ambiguity unsafety, docId-threading path assumption) plus tighter predicate vocabulary recommendation. Turn 3 raised 1 High (observation path collision, a pre-existing blocker that the new pipeline surfaced) + 1 Low (placeholder regex false-positive on Example: prefix) plus a recommended integration test. Turn 4: zero remaining findings, clear to ship.Adds +34 tests (tests/unit/spo-extraction.test.ts, 31 Turn-3 parser/entity/provenance + 3 Turn-4 collision-free persistence) on top of the v0.8.4 baseline. Public test suite: 1006 → 1025, zero regressions.
v0.8.5 is safe to drop in — the fix is additive across the board and nothing existing breaks:
kg_query gained canonical-ID acceptance but continues to accept entity names; every other MCP tool signature is identical.clawmem setup hooks re-run needed. Claude Code invokes ${binPath} hook ${name} at runtime, so upgrading the ClawMem binary alone propagates the new decision-extractor behavior.entity_nodes.entity_type='auto' rows and placeholder source_fact strings from pre-v0.8.5 runs are harmless (they never resolve via kg_query), but can be deleted if you want a clean slate. See the “kg_query returns empty for every entity” entry in docs/troubleshooting.md for the exact sqlite3 cleanup commands and diagnostic symptoms.clawmem reindex --enrich is NOT required. Only run it if you specifically want triple extraction to re-fire across your existing observation history — but note that past observation transcripts are gone, so re-enrichment on already-persisted _clawmem/observations/*.md files will not recover lost same-type-collision observations. New Stop-hook activity from v0.8.5 onward is the cleanest source of triples.Confirming the fix is live — after a real Claude Code Stop-hook-firing session, sqlite3 ~/.cache/clawmem/index.sqlite "SELECT source_fact FROM entity_triples ORDER BY created_at DESC LIMIT 5" should show human-readable subject predicate object strings (e.g. "ClawMem depends_on Bun"), not JSON blobs or schema-placeholder echoes.
Fixes the OpenClaw plugin setup workflow that caused issue #5 (“plugin not found: clawmem”). Patch release — no schema changes, no migration required.
clawmem setup openclaw now auto-installs — previously only printed manual instructions (symlink + manifest copy + config set), which users frequently skipped or misconfigured. Now auto-creates ~/.openclaw/extensions/clawmem as a symlink to the plugin source, verifies the manifest exists, and prints only the remaining steps that require a gateway restart first (slot assignment, GPU endpoints, REST API). Handles stale symlinks (detects via readlink compare, replaces automatically after npm updates), existing directories (removes and re-symlinks), and regular file conflicts (aborts with clear message). Idempotent on re-run.clawmem setup openclaw --remove now auto-uninstalls — previously only printed removal instructions. Now removes the symlink/directory and resets the context engine slot to legacy via openclaw config set (if the OpenClaw CLI is available). Falls back to printing the manual command when the CLI is absent.openclaw.plugin.json — the plugin manifest shipped as plugin.json but OpenClaw expects openclaw.plugin.json. The old setup workflow worked around this with a copy step that wrote into the symlinked source tree (bad for protected npm prefixes and source checkouts). Now ships with the correct filename, eliminating the copy step entirely.plugins.slots.contextEngine was silently dropped on earlier versions. The warning is informational, not fatal.019d72d5, turns 25-27 (~4.32M cumulative tokens). Turn 25 raised 1 High (manifest copy into source tree) + 2 Medium (auto-install gap, version warning). Turn 26 raised 1 High (config set before gateway restart) + 1 Low (regular file conflict unhandled). Turn 27: zero remaining findings.No breaking changes. Existing users who previously ran the manual symlink steps are unaffected — setup openclaw detects the existing correct symlink and skips re-creation.
Context engine "clawmem" is not registered failure path. The PR was closed as superseded because v0.8.4 ended up shipping a broader auto-install fix, but the restart-ordering insight is preserved verbatim in v0.8.4’s printed next-steps and the code comment that documents the constraint.Two small safety/correctness fixes land alongside a major documentation restructure. Patch release — no schema changes, no migration required, no new env vars.
.slice(0, 10) cap that silently dropped legitimate entities on long-form content (research dumps, conversation synthesis, hub/index documents). The new ENTITY_CAP_BY_TYPE mapping in src/entity.ts scales the cap by content_type: research documents keep up to 15, hub and conversation documents keep up to 12, short types (decision, deductive, note, handoff, progress) stay at 8, and anything else — including untyped documents and unknown types — keeps the pre-v0.8.3 default of 10. extractEntities gained an optional contentType parameter and enrichDocumentEntities threads the column through from the document row. The LLM extraction prompt also advertises the dynamic cap directly (0-${cap} entities instead of the old hardcoded 0-10) so a compliant model no longer stops early on long-form documents. Input is trimmed and lowercased before lookup, so hand-authored frontmatter values like "Research" or " conversation " resolve cleanly.insertRelation — the primary memory_relations write API (store.ts:1545) now rejects relations where fromDoc === toDoc at the API boundary. A self-loop has no informational value for graph traversal and would pollute intent_search / find_similar neighborhoods. A mirror guard was added to the beads dependency bridge inside syncBeadsIssues (store.ts:~4228) because that path inserts directly into memory_relations without going through the wrapper. buildTemporalBackbone and buildSemanticGraph were left alone — both are structurally safe via their loop shape or SQL filter. An extended audit surfaced six additional INSERT sites (in amem.ts, entity.ts, consolidation.ts, conversation-synthesis.ts) that each already have their own structural or explicit self-loop protection; none required new guards.README.md (removed 7 inline subsections, ~75 lines) into a new chronological RELEASE_NOTES.md with every release from v0.1.1 through v0.8.3. README.md now reads as setup + usage + architecture, not changelog, and points to the new file. Upgrade action guidance for existing vaults continues to live in docs/guides/upgrading.md. This is the document you are reading.tests/unit/entity.test.ts (14 unit tests for entityCapForContentType covering all content types, defaults, unknown fallback, case/whitespace normalization; 11 integration tests for extractEntities covering per-type caps, untyped backward compat, and prompt-shape verification) and tests/unit/openviking-enhancements.test.ts (3 tests for the self-loop guard: plain rejection, mixed-valid-and-self-loop regression, and upsert weight-accumulation invariance). Public test suite: 978 → 1006, zero regressions.No breaking changes. Existing callers that don’t pass contentType get the default cap of 10, matching pre-v0.8.3 behavior exactly.
Both maintenance lanes can now be hosted by the long-lived clawmem watch watcher service in addition to the existing per-session clawmem mcp host. This makes the systemd-managed watcher the canonical 24/7 home for the v0.8.0 heavy maintenance lane — its quiet-window logic finally sees a live worker at the configured hours regardless of whether any Claude Code session is open. The light consolidation lane (Phase 1 backfill + Phase 2 merge + Phase 3 deductive synthesis + Phase 4 recall stats) now also acquires its own DB-backed worker_leases row before each tick, symmetric with the heavy lane’s existing exclusivity, so multiple host processes against the same vault cannot race on Phase 2 merges or Phase 3 deductive writes.
runConsolidationTick wraps every tick (Phase 1 → 4) in withWorkerLease against a new light-consolidation worker name with a 10-minute TTL. Two host processes (e.g. one watcher service + one per-session stdio MCP) cannot both consolidate the same near-duplicate observations or both INSERT a duplicate row into consolidated_observations. Phase 1 enrichment is also serialized — overkill for cost but cleaner for symmetry. The in-process isRunning reentrancy guard remains the cheap first defense before the SQLite lease round-trip.cmdWatch hosts both workers — clawmem watch honors the same CLAWMEM_ENABLE_CONSOLIDATION and CLAWMEM_HEAVY_LANE env-var gates as cmdMcp. Off by default. Mirror the existing systemd unit (or your wrapper .env) to opt in. The recommended deployment for v0.8.2+ is to set both env vars on clawmem-watcher.service and leave cmdMcp unset, so the heavy lane has a continuously available host independent of Claude Code session lifecycle.cmdMcp is now a fallback host with a heavy-lane warning — cmdMcp retains the same env-var gates so non-watcher deployments (e.g. macOS users running everything via Claude Code launchd) keep working unchanged. When CLAWMEM_HEAVY_LANE=true is set on a stdio MCP host, cmdMcp emits a one-line warning to stderr advising operators to move heavy-lane hosting to clawmem watch instead.stopConsolidationWorker and the closure returned by startHeavyMaintenanceWorker) are now async, clearing their setInterval AND polling their in-flight running flag until any mid-tick worker drains. This guarantees the worker’s withWorkerLease finally block runs against a still-open store, so the lease is released cleanly instead of leaking until TTL expiry. Bounded waits (15s light, 30s heavy) prevent a stuck tick from wedging shutdown indefinitely; the next process reclaims any stranded lease atomically.cmdWatch and cmdMcp now register their SIGINT/SIGTERM handlers BEFORE any worker initialization. A signal arriving in the brief window between worker startup and handler registration would otherwise terminate the host via the default signal action (exit 143) and skip the async drain entirely.tests/integration/cmdwatch-workers.integration.test.ts spawns bun src/clawmem.ts watch against a temp vault with short worker intervals, exercises the env-var gates, exercises a real heavy-lane tick (slow path, ~35s), and asserts the lease is released cleanly on SIGTERM.clawmem.ts cmdWatch() — a try/catch wrapped block had been silently destructuring getSkillContentRoot from ./config.ts, but that helper is forge-internal and was never exported in public ClawMem. The runtime catch swallowed the failure so it had no observable effect, but TypeScript flagged a static TS2339 error on the destructure. v0.8.2 removes the dead code path. No behavior change for public users.Adds +15 tests (9 light-lane lease unit + 5 cmdWatch fast subprocess + 1 cmdWatch slow subprocess) on top of the v0.8.1 baseline.
For operational guidance — enabling the workers via systemd drop-in, tuning intervals to your usage pattern, monitoring queries, and rollback steps — see docs/guides/systemd-services.md.
context-surfacing now builds its retrieval query from the current prompt plus up to two recent same-session prior prompts, so a short follow-up turn (“do the same for X”, “explain the rationale”) can still inherit the vocabulary of earlier turns. The raw prompt is persisted in a new nullable context_usage.query_text column so future hook ticks can reconstitute the multi-turn query from the DB. See multi-turn lookback for the full walkthrough.
query_text TEXT column on context_usage, guarded by PRAGMA table_info. Pre-v0.8.1 stores get the column added on first open; ad-hoc stores that skip the migration path degrade transparently via a feature-detect WeakMap so insertUsageFn never writes a column that doesn’t exist.MIN_PROMPT_LENGTH, shouldSkipRetrieval, heartbeat dedupe) do NOT persist their raw text because those turns are not meaningful user questions and carry a higher sensitivity profile. Post-retrieval empty paths (empty result set, threshold blocked, budget blocked) DO persist so a follow-up turn can still inherit the intent even when the current turn surfaced nothing.AND query_text != ? so a retry burst cannot eat into the 2-prior budget and leave the lookback window underfilled.session_id are invisible to the lookback. All fallback paths (missing column, DB error, no matching rows) return the current prompt unchanged — the hook never throws on lookback failures.Adds +27 tests (22 unit + 5 integration) on top of the v0.8.0 baseline.
A second, longer-interval consolidation worker that keeps Phase 2 + Phase 3 running on large vaults without starving interactive sessions. Off by default — set CLAWMEM_HEAVY_LANE=true to enable. The existing 5-minute light-lane worker is unchanged. See heavy maintenance lane for the architectural walkthrough.
CLAWMEM_HEAVY_LANE_WINDOW_START / CLAWMEM_HEAVY_LANE_WINDOW_END (0-23). Supports midnight wraparound (e.g., 22→6). Null on either bound means “always in window”.context_usage — counts hook injections in the last 10 minutes and skips the tick when the rate exceeds CLAWMEM_HEAVY_LANE_MAX_USAGES (default 30). No new query_activity table; reuses v0.7.0 telemetry.worker_leases table with atomic INSERT ... ON CONFLICT DO UPDATE ... WHERE expires_at <= ? acquisition, random 16-byte fencing tokens, and TTL reclaim. Safe under multi-process contention; any SQLite error translates to a lease_unavailable skip rather than a thrown exception.COALESCE(recall_stats.last_recalled_at, documents.last_accessed_at, documents.modified_at) ASC so long-unseen docs bubble up first. Empty recall_stats falls through to access-time without erroring.CLAWMEM_HEAVY_LANE_SURPRISAL=true plumbs k-NN anomaly-ranked doc ids (via the existing computeSurprisalScores) into Phase 2 as an explicit candidateIds filter. Degrades to stale-first on vaults without embeddings and logs selector: 'surprisal-fallback-stale' in the journal.maintenance_runs journal — every scheduled attempt writes a row: status (started/completed/failed/skipped), reason for skips, selected/processed/created/null_call counts, and a metrics_json payload with selector type and full DeductiveSynthesisStats breakdown. Operators can reconstruct any lane decision without reading worker logs.guarded: true to consolidateObservations, which overrides CLAWMEM_MERGE_GUARD_DRY_RUN inside findSimilarConsolidation so experimenting operators cannot weaken heavy-lane enforcement via env flag.Adds +56 tests (13 worker-lease + 35 maintenance unit + 8 maintenance integration) on top of the v0.7.2 baseline.
Opt-in LLM pass that runs after clawmem mine finishes indexing an imported collection. Operates on the freshly imported content_type='conversation' documents and extracts structured knowledge facts (decisions / preferences / milestones / problems) plus cross-fact relations, writing each fact as a first-class searchable document alongside the raw conversation exchanges. See post-import synthesis for the architectural walkthrough.
clawmem mine <dir> --synthesize [--synthesis-max-docs N]. Off by default. When omitted, existing mine behaviour is byte-identical to v0.7.1.saveMemory, and populates a local alias map. Pass 2 resolves cross-fact links against the local map first, falling back to collection-scoped SQL lookup. Forward references (link to a fact extracted later in the same run) are resolved correctly.(sourceDocId, slug(title), short sha256(normalizedTitle)), so reruns over the same conversation batch hit the saveMemory update branch instead of creating parallel rows. Same-slug collisions are disambiguated by the stable hash suffix, not encounter order.memory_relations insert uses ON CONFLICT DO UPDATE SET weight = MAX(weight, excluded.weight), which is idempotent on equal-weight reruns but still accepts stronger later evidence without double-counting.indexCollection commits does not roll back the mine import.llmFailures counts actual LLM path failures (null, thrown, non-array JSON), while docsWithNoFacts counts docs where the LLM responded validly but returned zero structured facts. Previously these were conflated as nullCalls.Adds +63 tests (46 unit + 5 integration + 12 regression) on top of the v0.7.1 baseline.
Five independent safety gates around the consolidation pipeline and context surfacing, aimed at preventing contamination, cross-entity merges, and unchecked contradictions from landing in the vault. Every extraction ships with full unit + integration test coverage (+158 tests on top of the v0.7.0 baseline). See consolidation safety for the architectural walkthrough.
contradicts (plural) convention across the entire codebase, eliminating silent query misses on the legacy singular formentity_mentions, with lexical proper-noun fallback) and runs dual-threshold normalized 3-gram cosine similarity before merging similar observations. Cross-entity merges are hard-rejected when anchor sets differ materially, preventing context bleed where “Alice decided X” merges into “Bob decided X”. Thresholds are env-overridable (CLAWMEM_MERGE_SCORE_NORMAL=0.93, _STRICT=0.98). Dry-run mode via CLAWMEM_MERGE_GUARD_DRY_RUN for calibration.link policy (insert new row + contradicts edge, default) or supersede policy (mark old row status='inactive'). Configurable via CLAWMEM_CONTRADICTION_POLICY and CLAWMEM_CONTRADICTION_MIN_CONFIDENCE. Phase 3 deductive synthesis applies the same gate to deductive dedupe matches.entity_mentions) + LLM validator (fail-open with validatorFallbackAccepts counter) + dedupe. Per-reason rejection stats exposed via DeductiveSynthesisStats so Phase 3 yield can be diagnosed without enabling extra logging.context-surfacing now always prepends an <instruction> block framing the surfaced facts as background knowledge the model already holds, and appends an optional <relationships> block listing memory-graph edges where BOTH endpoints are in the surfaced doc set. The relationships block is the first thing dropped when the payload would overflow CLAWMEM_PROFILE’s token budget, preserving facts-first behaviour while giving the model graph-level reasoning hooks directly in-prompt.Extracts recall tracking patterns from OpenClaw’s dreaming memory consolidation system. Tracks which documents are surfaced by retrieval, which queries surfaced them, and whether the assistant cited them — per-turn, not per-session. Validated by GPT 5.4 High across 8 review turns (2.6M tokens).
recall_events (append-only event log), recall_stats (derived summary with diversity / spacing / negative counts), turn_index column on context_usage + recall_events, contradict_confidence column on memory_relations.feedback-loop segments the transcript into turns and zips with context_usage rows by turn_index, checking references per-turn rather than session-globally. Eliminates cross-turn attribution noise where a document surfaced in turn 3 gets credited by a reference in turn 8.vault parameter. context_usage writes are mirrored into the named vault without cross-DB foreign keys. New list_vaults() and vault_sync() tools for vault management. Configured in config.yaml under vaults: or via CLAWMEM_VAULTS env var.lifecycle_status and lifecycle_sweep now surface pin candidates (high diversity + spacing) and snooze candidates (high noise ratio), scoped to active docs with collection/path in output.busy_timeout=15s during DDL init (was 0ms, causing SQLITE_BUSY on concurrent Stop hooks), reset to 5s for normal operations.New files: src/recall-buffer.ts, src/recall-attribution.ts. Adds +36 recall tracking tests; 659 total passing.
Consolidation worker gains a Phase 3 that synthesizes higher-order deductive observations from recent related facts. Introduces k-NN surprisal scoring for curator triage, embed-state tracking with retries, and a cooldown-based LLM remote fallback. Honcho deep analysis informed the deductive synthesis and surprisal patterns. GPT 5.4 Codex reviewed across 4 turns, 5.2M tokens. 623 tests passing.
decision / preference / milestone / problem, last 7 days) into first-class content_type='deductive' documents with source_doc_ids provenance and supporting edges in memory_relations. Infinite half-life, 0.85 baseline, decay-exempt — deductions compound over time rather than fading.session-bootstrap getCurrentFocus() surfaces deductive insights in a dedicated “Derived Insights” section. context-surfacing tags them as (deductive) so the agent knows they are synthesized rather than directly observed.computeSurprisalScores() uses k-NN average-neighbor-distance over sqlite-vec embeddings to identify anomalous observations for curator triage. High surprisal = outlier relative to its semantic neighborhood.embed_state (pending / synced / failed), embed_error, and embed_attempts. Failed docs retried up to 3 attempts. clearAllEmbeddings() resets state. getHashesNeedingFragments() catches missing seq=0 primary embeddings on resume.generate() and expandQuery() fall back to local node-llama-cpp on transport failures. Structured failure classification: transport errors trigger a 60s cooldown; HTTP errors and AbortError do not. Concurrent race guard via pre-fetch cooldown re-check.Clarified the LLM fallback description in README — transport vs HTTP error distinction, cooldown semantics, “silently falls back” language replaced with explicit cooldown mechanism description. No behavior changes.
New clawmem mine CLI imports conversation exports from six different chat formats. Observation taxonomy expanded from a single “observation” type into four first-class subtypes. GPT 5.4 reviewed across 3 turns, 6 issues found and fixed (consecutive-assistant loss, unawaited writes, permissive plain-text detection, Slack multi-party handling, preference decay exemption, YAML escaping).
clawmem mine <dir> — imports conversation exports from Claude Code, ChatGPT, Claude.ai, Slack, and plain text into the indexing pipeline. New src/normalize.ts format normalizer supports all six formats with per-format robustness fixes.conversation content type — 45-day half-life, 0.55 baseline, optimized for chat-log characteristics (shorter-lived relevance than decisions or docs, but longer than handoffs).preference (decay-exempt, Infinity half-life: user preferences persist indefinitely), milestone (60-day half-life), problem (60-day half-life).content_type treatment instead of the pre-v0.5.0 flattening into a single “observation” bucket.Bug fix release. Resolves clawmem update crashes on Obsidian vaults with bare YAML dates or booleans in frontmatter.
gray-matter auto-coerces YAML values — title: 2023-09-27 becomes a Date object, title: true becomes a boolean. Bun’s SQLite driver rejects these as bind parameters with “Binding expected string, TypedArray, boolean, number, bigint or null”, crashing the indexer mid-run.
str() helper in parseDocument() checks all frontmatter fields and coerces to string.safeTitle guards in insertDocument / updateDocument / reactivateDocument catch any Date/boolean leakage past the parse layer.Affected frontmatter fields: title, domain, workstream, content_type, review_by — any field gray-matter can coerce.
Version-number bumps only. No user-visible changes. Kept for npm release continuity between v0.3.4 and v0.4.2.
Bug fix release. Shell timeout wrappers killed hook processes with exit 124 (no stderr), producing Failed with non-blocking status code errors in Claude Code on every hook event.
timeout property — clawmem setup hooks now generates the native Claude Code hook timeout field instead of wrapping commands in shell timeout.decision-extractor / handoff-generator / feedback-loop regularly exceeds 10s on CPU or under load.timeout removed from command strings — only the new native field carries timeout semantics.v0.3.2 / v0.3.3 / v0.3.4 are version-number bumps only with no user-visible changes.
OpenClaw compatibility release. Reviewed by GPT 5.4 High across 3 turns, 797K tokens.
OpenClaw v2026.3.28+ removed the legacy compaction fallback that compact() implementations with ownsCompaction=false were relying on. ClawMem’s OpenClaw plugin needed to delegate compaction to the runtime directly instead of returning compacted: false and expecting OpenClaw to fall back.
compact() delegates to runtime — uses delegateCompactionToRuntime() from openclaw/plugin-sdk/core. precompact-extract still runs first as a side-effect so pre-compaction state is captured regardless of who performs the compaction.bootstrap() caches context, before_prompt_build consumes it once. Previous behavior invoked bootstrap twice per session.before_compaction hook — precompact-extract now runs once per compaction, not twice.extractContext() instead of the legacy systemMessage field.clearSession() for per-session cleanup.bootstrappedSessions / isBootstrapped() helpers.Without this fix, OpenClaw sessions never compact on v2026.3.28+.
Documentation-only release. Custom Stop hooks that exit 0 with no stdout cause Claude Code to report Failed with non-blocking status code: No stderr output. Documented the root cause and fix pattern in troubleshooting.md and setup-hooks.md.
Bug fix release. Stop hooks were hanging or OOM-ing on transcripts larger than ~10MB because readTranscript() loaded the entire file.
Buffer accumulation with a single UTF-8 decode at the end prevents multi-byte character corruption at chunk boundaries.fd with try/finally — no descriptor leak.validateTranscriptPath() limit raised from 50MB to 1GB — Claude Code sessions can genuinely produce very large transcripts.Minor fixes. No substantive feature changes beyond v0.2.6.
memory_pin, memory_snooze, and memory_forget now use a 4-stage search cascade instead of BM25-only, preventing No matching memory found failures when the document exists but BM25 fails on multi-term queries.
findMemoryCandidates() cascade — exact path match → BM25 → title-token overlap → vector similarity. Async pipeline with path detection, stopword filtering, minimum match rule (max(2, ceil(n/2)) terms), vector fallback on cascade exhaustion.selectLifecycleTarget() confidence gate — ambiguous matches return a candidate list for memory_forget (destructive, requires confirmation), top hit for pin / snooze (non-destructive, single choice).docs/reference/mcp-tools.md updated with the new search behavior.Bug fix release. Hook SQLite busy_timeout was 500ms while watcher/MCP used 5000ms. During A-MEM enrichment or heavy indexing, watcher write locks exceeded 500ms, causing the hook’s DB open to fail with SQLITE_BUSY — surfaced as UserPromptSubmit hook error in Claude Code.
busy_timeout raised from 500ms to 5000ms — matches MCP server.Bug fix release. The watcher used fs.watch(recursive: true) which registered inotify watches on every subdirectory, including excluded dirs (gits/, node_modules/, .git/). Broad collection paths like ~/Projects caused 200K+ file descriptors, hanging WSL and triggering inotify limit errors on Linux.
EXCLUDED_DIRS from indexer.ts.EXCLUDED_DIRS from indexer.ts for watcher to share.troubleshooting.md, CLAUDE.md, AGENTS.md, SKILL.md.Diagnosis command added: ls /proc/$(pgrep -f "clawmem.*watch")/fd | wc -l — healthy watchers stay under 15K FDs.
Entity resolution pipeline rewritten with quality filters, type-agnostic canonical resolution, and IDF-based entity edge scoring. Addresses the v0.2.0 entity extraction quality issues flagged during a spot audit — prompts were producing title-as-entity extractions at 67% rate because the LLM echoed named examples from the prompt.
isLowQualityEntity() filter — rejects title-as-entity (Levenshtein > 0.85), names longer than 60 chars, template placeholders, trailing colons, and invalid locations.person / org / location are isolated; project / service / tool / concept merge freely within a shared tech bucket, capturing the common LLM confusion between them.0-10 entities (was 3-15 — upper bound too high invited hallucination), generic placeholder (was named examples the LLM echoed at 67% rate), negative instructions for titles and headings.isValidLocation() — positive-signal only (IP addresses, VM \d+ pattern). No length fallback; the old fallback was accepting FQDN-looking heading fragments.clearDocEntityState guard — handles externally-wiped enrichment state without crashing.LlamaCpp → LLM interface refactor for entity.ts and intent.ts function signatures.docs/internals/entity-resolution.md with the bucket system and model quality guidance.Bug fix release. Entity enrichment could double-count mentions or leave partial state on mid-run failures.
entity_enrichment_state table with SHA256(title+body) input hash — tracks which documents have been enriched and against what content.mention_count inflation when the same entity appears under multiple aliases in one document.--enrich flag fix — clawmem reindex --enrich now queues unchanged documents for entity backfill. Previously it only queued changed documents, which meant existing vaults couldn’t backfill entities after upgrading.--enrich vs --force distinction and the wrapper bypass trap (scripts running bun run src/clawmem.ts directly miss GPU env var defaults from the bin/clawmem wrapper).Seven patterns extracted from the Hindsight memory engine plus a memory nudge pattern from Hermes Agent, reviewed by GPT 5.4 High across three rounds. Introduces entity resolution, multi-path graph retrieval, temporal query extraction, 3-tier consolidation, observation invalidation, and memory nudges.
⚠ Migration required: Existing vaults upgrading from v0.1.x must run
clawmem reindex --enrichto populate the new entity tables and trigger A-MEM enrichment on existing documents.reindex --forcealone is NOT sufficient — the A-MEM pipeline skips entity extraction for update-path documents to avoid churn, so the--enrichflag is required to backfill. See docs/guides/upgrading.md and the troubleshooting entry onreindex --force after v0.2.0 upgrade shows no entity extractionfor details.
query tool (Tier 3 only), alongside existing BM25 + vector channels<vault-nudge> injection prompting lifecycle tool use after N turns of inactivity. Configurable via CLAWMEM_NUDGE_INTERVAL.HOOK_EVENT_MAP was missing the curator-nudge hook; it existed only in skill-forge. Backported so public users get the curator-nudge hook wired correctly.staleness-check now runs getArchiveCandidates + archiveDocuments on session start when lifecycle policy is configured. Fail-open: any error logs and continues, never blocks session startup.Bug fix release. The watcher was processing Claude Code session transcript .jsonl files as if they were memory documents, causing SQLite write lock contention that triggered UserPromptSubmit hook error on the context-surfacing hook. Watcher now excludes session transcripts explicitly (only .beads/*.jsonl is still processed).
v0.1.4 / v0.1.5 / v0.1.7 are version-number bumps with minor doc fixes (tool count correction 25 → 28, hook count fixes, manual hook config reference in setup-hooks.md, hook cold start latency notes, stale troubleshooting cleanup).
Context-surfacing moves from absolute score thresholds to ratio-based adaptive thresholds (Tier 1 of the adaptive threshold roadmap). Introduces budget-aware deep-profile escalation that spends remaining hook time budget on query expansion and cross-encoder reranking.
speed 0.65/0.24/0.18, balanced 0.55/0.20/0.15, deep 0.45/0.16/0.12. MCP tools remain on fixed absolute thresholds (agents control their own limits).CLAWMEM_PROFILE=deep and the fast path (BM25+vector) finishes under 4s, the remaining time budget is spent on (1) query expansion via LLM to discover candidates keyword+vector missed, and (2) cross-encoder reranking of the top 15 candidates for a deeper relevance signal. Hard stop at 6s; fail-open to fast-path results on GPU failure or timeout. Only fires on deep; speed and balanced unchanged.deep profile minScore lowered from 0.35 to 0.25 — composite scoring with recency/confidence decay was filtering out all results at 0.35 for vaults with older documents. Validated end-to-end: deep profile returns results in ~2s, well within the 8s hook timeout.First public releases to npm. Baseline feature set:
session-bootstrap, context-surfacing, staleness-check, decision-extractor, handoff-generator, feedback-loop, precompact-extract, postcompact-inject, curator-nudge