Repository navigation
Conversation
…lity
On OpenClaw 2026.10 the plugin's memory_search and memory_get never ran.
The bundled Memory Core plugin registers both names even when another
plugin owns the memory slot, so OpenClaw rejected ours ("plugin tool name
conflict"). The plugin also never called registerMemoryCapability, so the
host's memory consumers fell back to Memory Core's file index.
Register an OpenClaw memory capability (memory/basic-memory-capability.ts):
- providerRuntime: search, get, health and close over the shared BmClient.
Gateway memory.search v2, memory.get, memory.status and doctor now read
Basic Memory. Fused scores (0-1.3) are capped at 1 for the host's 0-1
minScore scale, and a missing note returns not_found while other
failures propagate.
- recallToolNames: search_notes, read_note, build_context for Active Memory.
- providerFlushPlanResolver: a tools-arm pre-compaction flush that saves
through write_note and edit_note instead of a workspace file.
- promptBuilder: a system-prompt section pointing agents at search_notes.
Without it OpenClaw fills the field from Memory Core, whose section points
at its file-only memory_search.
Remove the dead memory_search/memory_get registrations (tools/memory-
provider.ts) and drop them from the plugin manifest.
Show search scores as raw numbers ("score 1.18") in search_notes, /recall
and the CLI. They were multiplied by 100 and shown as a percentage, so a
strong match on both keyword and vector signals displayed as "118%".
Docs: README, CLAUDE.md and DEVELOPMENT.md describe the capability;
MEMORY_TASK_FLOW.md is marked superseded.
Verified live on OpenClaw 2026.10.9: memory.status returns
{providerId: openclaw-basic-memory, status: ready}; memory.search v2 and
memory.get return Basic Memory notes, line windows and not_found; no tool
name conflicts after a gateway restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
`openclaw memory status --agent main` printed "Health: ready" and then never exited. The CLI loads the plugin without starting its service, so the provider's health() call spawned a `bm mcp` child lazily, and the reader's close() was a no-op. The child's open pipes kept the process alive. BmClient now records whether the plugin service owns the session (isServiceRunning(), true only between start() and stop()). A reader opened while the service is not running owns its connection and stops it on close(). In the gateway, close() still leaves the shared session open. Also rewrite the search_notes description. It told agents to use memory_search for cross-source recall covering MEMORY.md and tasks, which no longer exists on OpenClaw 2026.10. Verified live on OpenClaw 2026.10.9: `openclaw memory status --agent main` exits 0 after about 13 s, reporting the openclaw-basic-memory provider as ready, and leaves no extra `bm mcp` process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Added commit 3e5954e with two fixes found by the OpenClaw agent during testing. 1.
Verified live on 2026.10.9: the command exits 0 after about 13 s and reports the 2. The Checked, no change needed: This branch is no longer rebased on the latest #1966 commits, because updating it that way would need a force-push. The PR diff is unaffected. |
…1966) This PR makes the OpenClaw plugin work on OpenClaw 2026.10 and later. On those hosts, conversation capture never ran (#1880), the plugin's memory search never ran, and OpenClaw's memory features read only workspace files. All fixes were tested together against a live OpenClaw 2026.10.9 gateway. This PR now also contains the work from #1968, which is closed so that one branch carries everything. ## Commits | Commit | What it fixes | |---|---| | `e49f7255` | **Capture (#1880).** OpenClaw 2026.10 rejected the context engine on every turn, so nothing was captured. | | `16a30828` | `context-engine/types.ts` was missing from the npm `files` list. | | `1e29bb0d` | Heartbeat polls and `NO_REPLY` turns were captured as conversation. | | `2f554e17` | **Memory provider.** The plugin's `memory_search`/`memory_get` never ran, and OpenClaw's memory features fell back to its file index. Search scores displayed as "118%". | | `60c153d8` | `openclaw memory status` printed "Health: ready" and then never exited. | | `a32aa118` | `write_note`/`edit_note` told agents to cite `file_path`, but the result text did not include it. | | `61a1d305` | `edit_note` hid every server error behind "It may not exist", and offered no way to add to a section. | | `ccccfbbe` | A first start overran OpenClaw's 5 s service limit and could wedge the gateway; a missing workspace directory broke a fresh install; search previews cut off long notes; `build_context` repeated observations and printed `null` for unresolved links; every other tool hid server errors. | ## 1. Capture (#1880) OpenClaw 2026.10 runs a context engine for durable turns only if the engine meets two requirements. It must declare `info.transcriptSemantics` (`currentTurnFence: "before-current-turn-entry-v1"`, `turnAdvancementIdempotency: "atomic-idempotent-v1"`). It must also implement `commitTurn()`, an atomic write keyed by `advancementKey` that is safe to repeat. Our engine did neither. OpenClaw logged `Context engine "openclaw-basic-memory" degraded to "legacy" ... current-turn transcript fencing is not declared` and captured nothing. - **Declarations.** The engine declares both values. The fence holds because `assemble()` passes the host's messages through unchanged and the engine stores no transcript. - **`commitTurn()`.** It captures the turn keyed by `advancementKey`. - Each entry carries a hidden `<!-- openclaw-turn:<sha256 prefix> -->` marker. - A key already present in today's or yesterday's conversation note returns `duplicate`. - Captures run one at a time, so a duplicate check and its append cannot interleave. - Errors propagate, so OpenClaw keeps the turn queued and retries it. - **Fallback.** `afterTurn()` stays for older hosts. OpenClaw skips `afterTurn()` for turns it sends to `commitTurn()`. - **Housekeeping turns.** Both paths skip heartbeat turns (`isHeartbeat`) and replies that are exactly `NO_REPLY`. - **Build.** The plugin builds against `openclaw ^2026.10.6`. 2026.10 removed the bare `openclaw/plugin-sdk` export, so `ContextEngine` types now come from `OpenClawPluginApi.registerContextEngine`. ## 2. Memory provider The plugin registered `memory_search` and `memory_get` as plain tools. OpenClaw's bundled Memory Core plugin registers the same names even when another plugin owns the memory slot, so OpenClaw rejected ours (`plugin tool name conflict`). The plugin also never called `registerMemoryCapability`, OpenClaw's "exclusive memory-plugin API". `memory/basic-memory-capability.ts` now registers a capability: - **`providerRuntime`** with `search`, `get`, `health` and `close` over the shared BM client. Gateway `memory.search` v2, `memory.get`, `memory.status` and doctor now read Basic Memory. - Fused scores (0–1.3) are capped at 1 for the host's 0–1 `minScore`. - A missing note returns `not_found`; any other failure propagates. - Every BM call is wrapped in `context.assertCurrent()`, so a revoked caller is refused. - A reader opened outside the plugin service, as in a one-shot CLI command, stops its own `bm mcp` connection on `close()`. - **`recallToolNames`**: `search_notes`, `read_note` and `build_context`, for Active Memory. - **`providerFlushPlanResolver`**: the pre-compaction flush saves through `write_note`/`edit_note` instead of a workspace file. - **`promptBuilder`**: a short system-prompt section that points agents at `search_notes`. Otherwise OpenClaw fills this field from Memory Core, which points agents at its file-only `memory_search`. The dead `tools/memory-provider.ts` is removed, and its tools are dropped from `openclaw.plugin.json`. **Design decision.** Memory Core's `memory_search` cannot be routed to Basic Memory: it always opens Memory Core's own index (`backend: "builtin"`). This PR keeps Memory Core loaded and steers agents to `search_notes` through the prompt section and the `search_notes` description. The alternatives were to disable Memory Core, which also loses its file memory and its background memory-consolidation feature ("dreaming"), or to re-add the combined search under a new tool name. ## 3. Smaller fixes - **Scores.** Search results show `(score 1.18)` instead of "118%" in `search_notes`, `/recall` and the CLI. Basic Memory's fused scores reach 1.3 by design (`search_reader.py:349`). - **File paths.** `write_note` and `edit_note` results now include a `File:` line, matching their descriptions. - **`edit_note` errors.** Failures show the server's reason, such as `INVALID_ARGUMENTS: \`section\` is only for section commands, not append.`, instead of "It may not exist". - **`edit_note` section inserts.** `insert_after_section` and `insert_before_section` are exposed. Agents can add an observation under `## Observations`; `append` always lands after `## Relations`. - **README.** The install steps set both slots with `openclaw config set`, because 2026.10.9 has no `plugins enable --slot`. They also state that capture and recall require the `contextEngine` slot. ## 4. Startup and tool output - **Background startup.** Service start no longer waits for `bm`. - `BmClient.start()` runs connect plus project creation as a startup gate. Tool calls wait for the gate, and a failed startup is retried by the next tool call. - Schema seeding runs after the gate opens. - The `bm --version` probe and the `uv` auto-install are async. - Measured: the first note write takes 4–9 s and a first start about 12 s, against OpenClaw's 5 s limit. A timed-out start failed the plugin, and the failed hot reload left the gateway unable to list models. - **Workspace directory.** The service creates the workspace before spawning `bm`. On a fresh install, OpenClaw creates it only on the first agent run, and spawning into it failed with `ENOENT ... posix_spawn 'bm'`. - **Search previews** use the server's `matched_chunk`, up to 500 characters, in `search_notes`, the CLI and the memory provider. Before, a fact deep in a day's conversation note was matched but cut off. - **`build_context`** lists only observations missing from the shown note body. It shows an unresolved `[[link]]` by its name, with "(no note yet)", instead of `null`. - **Errors.** Every tool's catch-all now shows the server's reason (`describeToolError`/`describeNoteError`). It says "does not exist" only when the server reported `NOT_FOUND`. ## How it was verified **Automated.** `just package-check-openclaw` exits 0 with 278 Bun tests passing. It covers type check, Biome lint, build and the `npm pack` dry run. **Live, on OpenClaw 2026.10.9 (macOS VM).** I installed packed tarballs of this code with both slots set. Basic Memory ran from this branch with an isolated config. | Check | Result | |---|---| | Tool-free turn, then check for "degraded to legacy" | No warning after the fix. The only one is from the pre-fix reproduction. | | Turn captured automatically | 16 turns recorded, each with its own marker | | Restart, then check for duplicates | 4 turns across 4 sessions and 1 restart gave 4 entries with 4 distinct markers | | `openclaw gateway call memory.status` | `{"providerId": "openclaw-basic-memory", "status": "ready"}` | | `memory.search` v2 / `memory.get` | Basic Memory hits. A line window returned `nextFrom`; an unknown id returned `not_found`. | | `openclaw memory status --agent main` | Exits 0 in about 13 s with `Health: ready` and no leftover `bm mcp` process | | Tool-name conflicts after restart | None | | Agent read/write loop | An OpenClaw agent ran `write_note`, `edit_note`, `search_notes`, `build_context`, `read_note` and `delete_note`; each worked on the first try | | Heartbeat skip | The 16:00 CDT heartbeat turn ran (`trigger=heartbeat` in the gateway log) and was not recorded. The 15:00 heartbeat, before the fix, had been. | | Hot install, brand-new project | "Plugin replacement applied" 0.09 s after start, instead of a timeout. Connect and schema seeding finished in the background 2.5 s later. | | Search preview | `search_notes` "test plant fern" previewed the matching turn instead of the note's opening | | `build_context` | The observation is listed once. A link to a missing note shows `relates_to → **No Such Note Zebra** (no note yet)`. | | Missing note | `read_note` returned `it does not exist. NOT_FOUND: No note matches ...` | | `edit_note` errors and inserts | Append with `section` returned the `INVALID_ARGUMENTS` reason; `insert_after_section` "Observations" placed the line directly under that heading | ## Not verified - **Missing workspace directory:** covered by a unit test that starts the service with a nonexistent workspace. The VM's workspace already exists, so this was not reproduced live after the fix. - **Pre-compaction flush and Active Memory recall:** not exercised live. The flush plan shape is unit-tested. - **OpenClaw before 2026.10:** not run. The `afterTurn()` fallback is unchanged, so the peer range stays `>=2026.5.2`. - **Automatic recall on CLI runtimes such as `claude-cli`:** these never call `assemble()`, so recall text is not injected. That is host behaviour. - **Access policy:** every OpenClaw caller with memory access can read every note in the project, the same access `search_notes` already gives. ## Not fixed here: `openclaw memory status` takes 11–13 s Timestamped output shows where the time goes: | Stage | Time | Owner | |---|---|---| | CLI start until plugins register | 0 → 5.3 s | OpenClaw | | Second plugin load pass | 5.3 → 8.7 s | OpenClaw | | `bm mcp` connect | 8.7 → 10.6 s | Basic Memory: #1731, eager FastAPI import at MCP startup | | Health call and close | 10.6 → 11.5 s | plugin | Loading the plugin module takes about 0.2–0.3 s per pass. No plugin change would make a real difference. The `bm` share is tracked in #1731, which is waiting on Python 3.15 lazy imports (#1740 was declined). ## Next step A maintainer reviews and merges. Closes #1880 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Drew Cain <groksrc@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This PR makes Basic Memory OpenClaw's actual memory provider on OpenClaw 2026.10 and later. Today the plugin's memory tools never run there, and OpenClaw's memory features read only workspace files.
Stacked on #1966. The base branch is
fix/1880-openclaw-commit-turn, so this diff shows only this change. Merge #1966 first; this PR's base then moves tomain.Problem
Two separate faults made the plugin's memory search invisible on OpenClaw 2026.10.9:
memory_searchandmemory_getwithapi.registerTool(tools/memory-provider.ts). OpenClaw's bundled Memory Core plugin registers the same names, even when another plugin owns the memory slot (dist/extensions/memory-core/index.js:989in the installed OpenClaw). OpenClaw rejects ours and logsplugin tool name conflict (openclaw-basic-memory): memory_search. The plugin's combined search never ran.api.registerMemoryCapability. Its docs call this "the exclusive memory-plugin API" (docs/plugins/sdk-overview/memory-and-context.md). The plugin never called it. So gatewaymemory.search/memory.get/memory.status, doctor, Active Memory recall and the pre-compaction flush all used Memory Core's file index.A third, smaller bug:
search_notes,/recalland the CLI showed scores as percentages. Basic Memory fuses keyword and vector scores into the range 0 to 1.3 (src/basic_memory/repository/search_reader.py:349,FUSION_BONUS = 0.3), so a strong match displayed as "118%".What changed
memory/basic-memory-capability.ts, registered inindex.ts:providerRuntimeopens a reader over the plugin's shared BM client withsearch,get,healthandclose. Capabilities: sourcememory, no pagination, no automatic-recall candidates, no project filter.searchcaps fused scores at 1 for the host's 0–1minScorescale, and enforcesminScore.getapplies the host'sfrom/lineswindow. A missing note returnsnot_found, and any other failure propagates.healthlists projects to prove the BM session answers, without reading note content.context.assertCurrent(), so a caller whose authority was revoked is refused.recallToolNames: ["search_notes", "read_note", "build_context"]for Active Memory deep recall.providerFlushPlanResolverreturns a tools-arm plan. The pre-compaction flush saves withwrite_note/edit_note, looks up withsearch_notes/read_note, and repliesNO_REPLYwhen there is nothing to save.promptBuilderadds a short "Basic Memory" system-prompt section. Without it, OpenClaw fills the field from Memory Core's registration, whose section points agents at its file-onlymemory_search.tools/memory-provider.tsand its tests. They never ran on current OpenClaw, and Memory Core'smemory_searchalready coversMEMORY.md.memory_search/memory_getare also dropped fromopenclaw.plugin.json.(score 1.18)intools/search-notes.ts,commands/slash.tsandcommands/cli.ts.isNoteNotFoundErrorinbm-client.tsis now exported for reuse.package.jsonfilesswapstools/memory-provider.tsformemory/basic-memory-capability.ts.CLAUDE.mdandDEVELOPMENT.mddescribe the capability.MEMORY_TASK_FLOW.mdis marked superseded.Design decision: agents keep two searches
Memory Core's
memory_searchcannot be routed to Basic Memory. It always opens Memory Core's own index (getMemorySearchManagerindist/memory-h3csZjpI.mjsreportsbackend: "builtin"), and the docs say Memory Core tools "keep their existing ownership". Of the three options, this PR keeps Memory Core and steers agents tosearch_notesthrough the prompt section. The alternatives were:How it was verified
Automated.
just package-check-openclawexits 0 with 264 Bun tests passing.memory/basic-memory-capability.test.tscovers capabilities, hit mapping and score cap,minScore, line windows,not_foundversus propagated errors, revoked callers, health, the flush plan and the prompt section.index.test.tsnow asserts the capability is registered andmemory_search/memory_getare not.tools/search-notes.test.tschecks a fused score of 1.18 renders as(score 1.18)with no "%".Live, on OpenClaw 2026.10.9. I installed the packed tarball and did a full gateway restart. Basic Memory ran from #1966's branch with an isolated config.
openclaw gateway call memory.status{"providerId": "openclaw-basic-memory", "status": "ready"}memory.searchwithversion: 2conversations/conversations-2026-10-29.md, score 1memory.getwithfrom: 10, lines: 3status: ok, 3 lines,truncated: true,nextFrom: 13memory.getwith an unknown idstatus: not_foundtool name conflictlinessearch_notesand Memory Core'smemory_searchNot verified
search_notesandread_notealready expose. A group-chat caller is not restricted to a "conversation" audience; adding that would be a separate policy decision.Follow-up found during testing
search_notespreviews only the first 200 characters of each hit. Conversation capture appends a whole day's turns to one note, so a fact from a later turn is matched but cut off before the agent sees it. A fresh session found a captured fact through Memory Core's files instead. This should be its own issue.Next step
A maintainer reviews this after #1966 merges.
🤖 Generated with Claude Code