Skip to content

fix(integrations): make the OpenClaw plugin work on OpenClaw 2026.10 - #1966

Merged
groksrc merged 8 commits into
mainfrom
fix/1880-openclaw-commit-turn
Oct 29, 2026
Merged

groksrc merged 8 commits into
mainfrom
fix/1880-openclaw-commit-turn

Conversation

@groksrc

@groksrc groksrc commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

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

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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T19:18:47.236982Z e49f725 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +1009 to +1013
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
for (const candidate of [
title,
`conversations-${conversationDate(yesterday)}`,
]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@groksrc

groksrc commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

Added commit 16a3082. It lists context-engine/types.ts in package.json files, which the first commit missed. dist/ already contained the file, so the runtime entry and the live test were unaffected. Hosts that load the TypeScript source entry (./index.ts) would have failed to resolve the import.

#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>
@groksrc

groksrc commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

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 ([OpenClaw heartbeat poll] / NO_REPLY). Both commitTurn() and afterTurn() now acknowledge such a turn without recording it. They check two things: the host's isHeartbeat flag, and a reply that is exactly the NO_REPLY silent token.

Verified by a new unit test in context-engine/basic-memory-context-engine.test.ts. I did not wait for a live heartbeat after the change.

groksrc and others added 3 commits October 29, 2026 15:27
…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>
@groksrc groksrc changed the title fix(integrations): commit OpenClaw turns through commitTurn fix(integrations): make the OpenClaw plugin work on OpenClaw 2026.10 Oct 29, 2026
groksrc and others added 2 commits October 29, 2026 15:37
…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>
@groksrc
groksrc merged commit caa1592 into main Oct 29, 2026
27 checks passed
@groksrc
groksrc deleted the fix/1880-openclaw-commit-turn branch October 29, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] OpenClaw 2026.10.6 degrades Basic Memory context engine to legacy; auto-capture never runs

1 participant