Repository navigation
fix(integrations): make the OpenClaw plugin work on OpenClaw 2026.10 - #1966
Conversation
OpenClaw 2026.10 and later runs a context engine for durable turns only when the engine declares current-turn transcript semantics and implements commitTurn(). The Basic Memory engine did neither, so every turn degraded to OpenClaw's legacy engine and automatic capture never ran (#1880). - Declare transcriptSemantics (before-current-turn-entry-v1, atomic-idempotent-v1). The fence holds because assemble() passes the host's messages through unchanged and the engine stores no transcript. - Add commitTurn(), which captures the turn keyed by the host's advancementKey. Each entry carries a hashed marker, and a key already in today's or yesterday's conversation note returns "duplicate". Captures are serialized so the check and the append cannot interleave. Errors now propagate so the host keeps the turn queued and retries it. - Keep afterTurn() for older hosts. OpenClaw skips afterTurn() for turns it routes through commitTurn(), so no turn is written twice. - Build against openclaw ^2026.10.6. The bare "openclaw/plugin-sdk" export is gone, so derive ContextEngine types from OpenClawPluginApi.registerContextEngine in plugin-sdk/core. - README: install commands that work on 2026.10 (`plugins enable --slot` no longer exists), and state that capture and recall need the contextEngine slot; the memory slot alone captures nothing. Verified live on OpenClaw 2026.10.9 with a tool-free turn: no "degraded to legacy" warning, and the turn landed in the conversation note. Four turns across four sessions and one gateway restart produced four entries with four distinct markers. Closes #1880 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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e49f7255a0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000) | ||
| for (const candidate of [ | ||
| title, | ||
| `conversations-${conversationDate(yesterday)}`, | ||
| ]) { |
There was a problem hiding this comment.
Preserve turn deduplication across multi-day downtime
If the conversation append succeeds but the gateway crashes before acknowledging the advancement, restarting more than one UTC calendar day later causes the queued turn to be appended again: its marker exists only in an older note, which this lookup never checks. Replaying the same key on October 26 and October 29 reproduces two committed results and two notes containing the identical exchange. This violates the newly declared atomic-idempotent-v1 semantics and permanently duplicates canonical conversation content. Use a durable key lookup or a stable destination derived from the original turn rather than limiting deduplication to the retry date and yesterday.
AGENTS.md reference: AGENTS.md:L157-L161
Useful? React with 👍 / 👎.
package.json lists published files explicitly, and context-engine/types.ts (added in the previous commit) was missing. dist/ was complete, so the runtime entry worked, but hosts that load the TypeScript source entry (./index.ts) would fail to resolve the import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Added commit 16a3082. It lists #1968 is stacked on this PR: it registers an OpenClaw memory capability and fixes search scores shown as percentages. |
OpenClaw's heartbeat polls reach the context engine as ordinary turns, so each one was saved to the daily conversation note as "[OpenClaw heartbeat poll]" / "NO_REPLY". commitTurn() and afterTurn() now acknowledge heartbeat turns (isHeartbeat) and turns whose reply is the NO_REPLY silent token without recording them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Added commit 1e29bb0. Capture now skips OpenClaw heartbeat turns and silent replies. During live testing, OpenClaw's heartbeat poll was saved to the conversation note as a full turn ( Verified by a new unit test in |
…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>
`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>
…e results Both tool descriptions tell the agent to cite the note's permalink and file_path when confirming a save, but file_path appeared only in the structured details, not in the text the agent reads. The result text now includes a File: line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
…t_note edit_note replaced every failure with 'Failed to edit note "...". It may not exist.' An agent that passed `section` with append got that message for an existing note, while the server's real reason was "INVALID_ARGUMENTS: `section` is only for section commands, not append." edit_note now shows the server's reason (new describeToolError helper in bm-client.ts), and still says plainly when the note does not exist. bm_edit already supports insert_before_section and insert_after_section, but the plugin offered only append, prepend, find_replace and replace_section. Agents adding an observation used append, which lands after "## Relations". edit_note now exposes both insert operations, and its description and docs/agent-guide.md say to use insert_after_section to add to a section. Verified live on OpenClaw 2026.10.9 through an agent: append with `section` returned the INVALID_ARGUMENTS reason; insert_after_section "Observations" placed the line directly under that heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
…dy tool output
Fixes the follow-ups found while testing the plugin on OpenClaw 2026.10.9.
Startup
- Service start returned only after connecting to bm, creating the
project and seeding two schema notes. A first start took about 12 s
(the first note write alone took 4-9 s), against OpenClaw's 5 s service
limit. The timed-out start failed the plugin, and the failed hot reload
left the gateway unable to list models until a restart.
BmClient.start() now runs connect plus project creation as a startup
gate. Tool calls wait for it, and a failed startup is retried by the
next tool call. The service starts it without waiting, and seeds
schemas afterwards. The bm --version probe and the uv auto-install are
now async, so neither blocks OpenClaw's event loop.
- On a fresh install the workspace directory does not exist until the
first agent run, and spawning bm into it failed with a misleading
"ENOENT ... posix_spawn 'bm'". The service now creates it first.
Tool output
- search_notes, the CLI and the memory provider previewed the start of
each note. A fact deep in a long note, such as a day of captured
conversation, was matched but cut off. They now preview the server's
matched_chunk, and search_notes shows up to 500 characters.
- build_context printed each note, then repeated its observations. It
now lists only observations missing from the shown body, and shows an
unresolved [[link]] by its name ("(no note yet)") instead of "null".
- Every tool's catch-all ("It may not exist", "Is Basic Memory running?
Check logs") now shows the server's reason (describeToolError /
describeNoteError in bm-client.ts), and says "does not exist" only when
the server reported NOT_FOUND.
Verified live on OpenClaw 2026.10.9: a hot install against a brand-new
project applied in 0.09 s ("Plugin replacement applied") instead of timing
out, and seeding finished in the background. Through an agent, search_notes
previewed the matching turn, build_context listed the observation once,
and read_note on a missing note returned "it does not exist. NOT_FOUND: ...".
Not changed here: most of `openclaw memory status`'s 11-13 s is OpenClaw
itself (about 8.7 s before bm starts). The bm mcp share (about 1.7 s) is
#1731.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
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
e49f725516a30828context-engine/types.tswas missing from the npmfileslist.1e29bb0dNO_REPLYturns were captured as conversation.2f554e17memory_search/memory_getnever ran, and OpenClaw's memory features fell back to its file index. Search scores displayed as "118%".60c153d8openclaw memory statusprinted "Health: ready" and then never exited.a32aa118write_note/edit_notetold agents to citefile_path, but the result text did not include it.61a1d305edit_notehid every server error behind "It may not exist", and offered no way to add to a section.ccccfbbebuild_contextrepeated observations and printednullfor 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 implementcommitTurn(), an atomic write keyed byadvancementKeythat is safe to repeat. Our engine did neither. OpenClaw loggedContext engine "openclaw-basic-memory" degraded to "legacy" ... current-turn transcript fencing is not declaredand captured nothing.assemble()passes the host's messages through unchanged and the engine stores no transcript.commitTurn(). It captures the turn keyed byadvancementKey.<!-- openclaw-turn:<sha256 prefix> -->marker.duplicate.afterTurn()stays for older hosts. OpenClaw skipsafterTurn()for turns it sends tocommitTurn().isHeartbeat) and replies that are exactlyNO_REPLY.openclaw ^2026.10.6. 2026.10 removed the bareopenclaw/plugin-sdkexport, soContextEnginetypes now come fromOpenClawPluginApi.registerContextEngine.2. Memory provider
The plugin registered
memory_searchandmemory_getas 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 calledregisterMemoryCapability, OpenClaw's "exclusive memory-plugin API".memory/basic-memory-capability.tsnow registers a capability:providerRuntimewithsearch,get,healthandcloseover the shared BM client. Gatewaymemory.searchv2,memory.get,memory.statusand doctor now read Basic Memory.minScore.not_found; any other failure propagates.context.assertCurrent(), so a revoked caller is refused.bm mcpconnection onclose().recallToolNames:search_notes,read_noteandbuild_context, for Active Memory.providerFlushPlanResolver: the pre-compaction flush saves throughwrite_note/edit_noteinstead of a workspace file.promptBuilder: a short system-prompt section that points agents atsearch_notes. Otherwise OpenClaw fills this field from Memory Core, which points agents at its file-onlymemory_search.The dead
tools/memory-provider.tsis removed, and its tools are dropped fromopenclaw.plugin.json.Design decision. Memory Core's
memory_searchcannot be routed to Basic Memory: it always opens Memory Core's own index (backend: "builtin"). This PR keeps Memory Core loaded and steers agents tosearch_notesthrough the prompt section and thesearch_notesdescription. 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
(score 1.18)instead of "118%" insearch_notes,/recalland the CLI. Basic Memory's fused scores reach 1.3 by design (search_reader.py:349).write_noteandedit_noteresults now include aFile:line, matching their descriptions.edit_noteerrors. Failures show the server's reason, such asINVALID_ARGUMENTS: \section` is only for section commands, not append.`, instead of "It may not exist".edit_notesection inserts.insert_after_sectionandinsert_before_sectionare exposed. Agents can add an observation under## Observations;appendalways lands after## Relations.openclaw config set, because 2026.10.9 has noplugins enable --slot. They also state that capture and recall require thecontextEngineslot.4. Startup and tool output
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.bm --versionprobe and theuvauto-install are async.bm. On a fresh install, OpenClaw creates it only on the first agent run, and spawning into it failed withENOENT ... posix_spawn 'bm'.matched_chunk, up to 500 characters, insearch_notes, the CLI and the memory provider. Before, a fact deep in a day's conversation note was matched but cut off.build_contextlists only observations missing from the shown note body. It shows an unresolved[[link]]by its name, with "(no note yet)", instead ofnull.describeToolError/describeNoteError). It says "does not exist" only when the server reportedNOT_FOUND.How it was verified
Automated.
just package-check-openclawexits 0 with 278 Bun tests passing. It covers type check, Biome lint, build and thenpm packdry 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.
openclaw gateway call memory.status{"providerId": "openclaw-basic-memory", "status": "ready"}memory.searchv2 /memory.getnextFrom; an unknown id returnednot_found.openclaw memory status --agent mainHealth: readyand no leftoverbm mcpprocesswrite_note,edit_note,search_notes,build_context,read_noteanddelete_note; each worked on the first trytrigger=heartbeatin the gateway log) and was not recorded. The 15:00 heartbeat, before the fix, had been.search_notes"test plant fern" previewed the matching turn instead of the note's openingbuild_contextrelates_to → **No Such Note Zebra** (no note yet).read_notereturnedit does not exist. NOT_FOUND: No note matches ...edit_noteerrors and insertssectionreturned theINVALID_ARGUMENTSreason;insert_after_section"Observations" placed the line directly under that headingNot verified
afterTurn()fallback is unchanged, so the peer range stays>=2026.5.2.claude-cli: these never callassemble(), so recall text is not injected. That is host behaviour.search_notesalready gives.Not fixed here:
openclaw memory statustakes 11–13 sTimestamped output shows where the time goes:
bm mcpconnectLoading the plugin module takes about 0.2–0.3 s per pass. No plugin change would make a real difference. The
bmshare 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