Skip to content

fix(memory): keep active memory capability visible across plugin scopes#69458

Closed
rzyns wants to merge 20 commits into
openclaw:mainfrom
rzyns:fix/memory-capability-workspace-scope
Closed

fix(memory): keep active memory capability visible across plugin scopes#69458
rzyns wants to merge 20 commits into
openclaw:mainfrom
rzyns:fix/memory-capability-workspace-scope

Conversation

@rzyns

@rzyns rzyns commented Apr 20, 2026

Copy link
Copy Markdown

Summary

  • Hoist activeMemoryCapability out of the scope-swapped memoryPluginState so a plugin scope that does not include the memory plugin no longer wipes the capability globally — fixes the memory-wiki bridge seeing capabilityPluginId: null and reporting zero workspaces/artifacts.
  • Add clearActiveMemoryCapability teardown; _resetMemoryPluginState (test-only) now calls both it and clearMemoryPluginState. Remove the now-unused CachedPluginState.memoryCapability field from the loader.
  • Add loader and memory-state regression tests that reproduce the exact OC-40 scenario: register the memory capability in scope A, activate scope B with a different cacheKey and onlyPluginIds that excludes the memory plugin, assert listActiveMemoryPublicArtifacts still returns the artifacts.

Fixes OC-40.

Test plan

  • src/plugins/memory-state.test.ts — 14/14 pass
  • src/plugin-sdk/memory-host-core.test.ts — pass
  • src/plugins/registry.dual-kind-memory-gate.test.ts — pass
  • extensions/memory-wiki/src/bridge.test.ts — 6/6 pass
  • src/plugins/loader.test.ts — 97/99 pass (the 2 failing tests — sanitizes plugin scope debug…, suppresses trust warning logs… — reproduce on main without this change, so they are pre-existing flakes unrelated to this PR)
  • pnpm tsgo:core + pnpm tsgo:core:test pass
  • Gateway-level verification after merge: run openclaw wiki bridge import --json against a live workspace and confirm workspaces > 0, artifactCount > 0

🤖 Generated with Claude Code

szponeczek and others added 12 commits April 19, 2026 11:49
…-instrumentation

Plugins: add plugin scope debug logs
Before this change `memoryPluginState.capability` was stored inside the
scope-swapped plugin state, so activating a plugin scope that did not
include the memory plugin (via `clearMemoryPluginState` +
`restoreMemoryPluginState`) would wipe the active capability globally.
The memory-wiki bridge then observed `capabilityPluginId: null` and
could not read memory-core's public artifacts, so wiki ingestion saw
zero workspaces.

Hoist `activeMemoryCapability` into its own module-level var so it lives
outside the per-scope snapshot. `clearMemoryPluginState` and
`restoreMemoryPluginState` no longer touch the capability; a new
`clearActiveMemoryCapability` teardown hook is exposed and
`_resetMemoryPluginState` (test-only) calls both helpers. The loader's
`CachedPluginState.memoryCapability` cache field is removed since the
capability is no longer scope-bound.

Adds regression tests that register a memory capability in one scope
and load a second scope (different cacheKey, memory plugin excluded),
then assert `listActiveMemoryPublicArtifacts` still returns the
artifacts.

Fixes OC-40.
@greptile-apps

greptile-apps Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes OC-40 by hoisting activeMemoryCapability out of the scope-swapped memoryPluginState object into a standalone module-level variable. Previously, clearMemoryPluginState() — called on every plugin-scope activation — would wipe memoryPluginState.capability, causing the memory-wiki bridge running in a scope that excluded the memory plugin to see capabilityPluginId: null and return zero artifacts. The fix is minimal and correct: the new clearActiveMemoryCapability teardown is only called by _resetMemoryPluginState (test-only), not by the per-scope clear, and regression tests at both the unit level (memory-state.test.ts) and the integration level (loader.test.ts) reproduce the exact OC-40 scenario.

Confidence Score: 5/5

Safe to merge; the core fix is correct and well-tested, remaining findings are P2 style/cleanup concerns.

The architectural change is sound — moving activeMemoryCapability outside memoryPluginState cleanly solves the cross-scope visibility problem. Regression tests cover the exact OC-40 scenario end-to-end. All other findings are P2: debug instrumentation left in production (gated by env var, appears intentional for post-merge gateway verification) and a minor gap in clearPluginLoaderCache not clearing the new variable.

Review the debug probe scaffolding in extensions/memory-core/index.ts and extensions/memory-core/src/public-artifacts.ts for removal once gateway-level verification is complete.

Comments Outside Diff (2)

  1. extensions/memory-core/index.ts, line 30-105 (link)

    P2 Debug scaffolding left in production

    The step/try/catch instrumentation was added to diagnose OC-40 and is gated behind OPENCLAW_DEBUG_PLUGIN_SCOPE === "1", but it rewrites the entire register function body with three extra indentation levels and a floating step helper that live permanently in production code. If this is meant to stay as a long-term diagnostic aid it should be in a shared utility; otherwise it should be removed once the gateway-level verification in the test plan is complete.

    A similar pattern appears in extensions/memory-core/src/public-artifacts.ts (the [memory-core-artifacts-probe] block) and src/plugins/memory-state.ts (the [memory-bridge-probe] block in listActiveMemoryPublicArtifacts).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/memory-core/index.ts
    Line: 30-105
    
    Comment:
    **Debug scaffolding left in production**
    
    The `step`/`try`/`catch` instrumentation was added to diagnose OC-40 and is gated behind `OPENCLAW_DEBUG_PLUGIN_SCOPE === "1"`, but it rewrites the entire `register` function body with three extra indentation levels and a floating `step` helper that live permanently in production code. If this is meant to stay as a long-term diagnostic aid it should be in a shared utility; otherwise it should be removed once the gateway-level verification in the test plan is complete.
    
    A similar pattern appears in `extensions/memory-core/src/public-artifacts.ts` (the `[memory-core-artifacts-probe]` block) and `src/plugins/memory-state.ts` (the `[memory-bridge-probe]` block in `listActiveMemoryPublicArtifacts`).
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. src/plugins/loader.ts, line 226-235 (link)

    P2 clearPluginLoaderCache does not clear activeMemoryCapability

    clearPluginLoaderCache resets all scoped plugin state (including clearMemoryPluginState), but the newly-extracted activeMemoryCapability variable is not cleared. The in-code comment explains that the capability is "workspace-scoped" and intentionally survives scope swaps, but a full loader-cache clear (e.g. on workspace change or in long-lived test helpers that call this without also calling _resetMemoryPluginState) would leave a stale capability behind. Consider whether clearActiveMemoryCapability() should also be called here, or add a code comment explicitly justifying the omission.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/plugins/loader.ts
    Line: 226-235
    
    Comment:
    **`clearPluginLoaderCache` does not clear `activeMemoryCapability`**
    
    `clearPluginLoaderCache` resets all scoped plugin state (including `clearMemoryPluginState`), but the newly-extracted `activeMemoryCapability` variable is not cleared. The in-code comment explains that the capability is "workspace-scoped" and intentionally survives scope swaps, but a full loader-cache clear (e.g. on workspace change or in long-lived test helpers that call this without also calling `_resetMemoryPluginState`) would leave a stale capability behind. Consider whether `clearActiveMemoryCapability()` should also be called here, or add a code comment explicitly justifying the omission.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-core/index.ts
Line: 30-105

Comment:
**Debug scaffolding left in production**

The `step`/`try`/`catch` instrumentation was added to diagnose OC-40 and is gated behind `OPENCLAW_DEBUG_PLUGIN_SCOPE === "1"`, but it rewrites the entire `register` function body with three extra indentation levels and a floating `step` helper that live permanently in production code. If this is meant to stay as a long-term diagnostic aid it should be in a shared utility; otherwise it should be removed once the gateway-level verification in the test plan is complete.

A similar pattern appears in `extensions/memory-core/src/public-artifacts.ts` (the `[memory-core-artifacts-probe]` block) and `src/plugins/memory-state.ts` (the `[memory-bridge-probe]` block in `listActiveMemoryPublicArtifacts`).

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/plugins/loader.ts
Line: 226-235

Comment:
**`clearPluginLoaderCache` does not clear `activeMemoryCapability`**

`clearPluginLoaderCache` resets all scoped plugin state (including `clearMemoryPluginState`), but the newly-extracted `activeMemoryCapability` variable is not cleared. The in-code comment explains that the capability is "workspace-scoped" and intentionally survives scope swaps, but a full loader-cache clear (e.g. on workspace change or in long-lived test helpers that call this without also calling `_resetMemoryPluginState`) would leave a stale capability behind. Consider whether `clearActiveMemoryCapability()` should also be called here, or add a code comment explicitly justifying the omission.

```suggestion
  clearMemoryPluginState();
  clearActiveMemoryCapability();
}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(memory): keep active memory capabili..." | Re-trigger Greptile

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

Copy link
Copy Markdown
Contributor

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: 82768d82a2

ℹ️ 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 thread src/plugins/loader.ts
Comment on lines 1556 to 1559
restoreMemoryPluginState({
capability: cached.memoryCapability,
corpusSupplements: cached.memoryCorpusSupplements,
promptBuilder: cached.memoryPromptBuilder,
promptSupplements: cached.memoryPromptSupplements,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Restore active memory capability on cached plugin loads

The cache-hit path now restores only legacy memory state and returns without restoring the scope’s memory capability, so switching between cached scopes can leave activeMemoryCapability pointing at the wrong plugin. Concretely, if scope A (memory plugin A) is cached, then scope B (memory plugin B) loads, a later cache hit for scope A will keep B’s capability because no capability is rehydrated here before return; calls like buildMemoryPromptSection, getMemoryRuntime, and listActiveMemoryPublicArtifacts can therefore use the wrong memory implementation for the active cache key.

Useful? React with 👍 / 👎.

Addresses follow-ups from PR openclaw#69458 code review:

- Strip temporary `[memory-core-register-probe]` step wrapper in
  `extensions/memory-core/index.ts` (removes try/catch control-flow
  change that existed purely for debug).
- Strip `[memory-core-artifacts-probe]` in
  `extensions/memory-core/src/public-artifacts.ts`.
- Strip `[memory-bridge-probe]` in
  `src/plugins/memory-state.ts#listActiveMemoryPublicArtifacts`.
- Restore strict `publicArtifacts.listArtifacts` invocation via a
  local bind — narrower than `cap?.capability.publicArtifacts !== undefined`
  and satisfies oxlint `unbound-method` because the reference is read
  into a const before the optional call.

The structured `[plugin-scope-debug]` diagnostic in `src/plugins/loader.ts`
stays — it has its own sanitization, tests, and is the long-term entry
point for scope diagnostics.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
szponeczek and others added 2 commits April 20, 2026 23:02
…eiver

Extracting listArtifacts into a local const stripped the `this` binding;
plugins implementing the method-shorthand type with `this`-bound state
would have run with `this === undefined`. Calling via `provider?.listArtifacts(params)`
keeps the receiver bound while still satisfying oxlint's unbound-method rule.

Flagged by Codex review on PR #3.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
refactor(memory): clean up OC-40 diagnostic probes
@openclaw-barnacle openclaw-barnacle Bot removed the extensions: memory-core Extension: memory-core label Apr 21, 2026

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

Copy link
Copy Markdown
Contributor

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: 4bfed6291a

ℹ️ 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".

// artifacts even when memory-core was loaded under a different cache key).
// Keeping it outside `memoryPluginState` means `clearMemoryPluginState` /
// `restoreMemoryPluginState` do not disturb it. See OC-40.
let activeMemoryCapability: MemoryPluginCapabilityRegistration | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Scope active memory capability per activation context

activeMemoryCapability is now process-global and is only cleared by the test-only _resetMemoryPluginState(), so normal plugin reloads can keep a stale memory capability when no memory plugin re-registers (for example after setting plugins.slots.memory to "none" or when the selected memory plugin fails to load). In that state, getMemoryRuntime, buildMemoryPromptSection, and listActiveMemoryPublicArtifacts continue using the previous plugin even though the new activation no longer selected it, which makes memory behavior inconsistent with current config.

Useful? React with 👍 / 👎.

Resolves conflict in src/plugins/loader.test.ts preserving both
regressions: OC-40 (memory capability survives scope swaps excluding
memory plugin) and upstream (previously registered memory capability
survives activate:false snapshot loads).

Fixes two slots.memory: null spots in test helpers — the slot type is
now string | undefined, so use the "none" sentinel to opt out.

Includes Greptile P2 fix in src/plugins/loader.ts: clearPluginLoaderCache
now calls clearActiveMemoryCapability to clear the hoisted capability
on full loader resets (used for test isolation, not scope swaps).

Pre-existing Telegram AbortSignal typecheck at extensions/telegram/src/bot.ts:193,195
is unrelated to this merge; --no-verify used per authorization for
that specific known failure.
@rzyns
rzyns requested a review from a team as a code owner April 21, 2026 11:59
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime agents Agent runtime and tooling labels Apr 21, 2026
@szponeczek

Copy link
Copy Markdown
Contributor

Follow-up merge pushed (3165989):

  • Resolved src/plugins/loader.test.ts merge conflict preserving both regressions: OC-40 (memory capability survives scope swaps that exclude the memory plugin) and upstream (previously registered memory capability survives activate:false snapshot loads).
  • Fixed two slots.memory: null spots in test helpers — slot type is now string | undefined, so switched to the "none" sentinel to opt out.
  • Greptile P2 addressed on the prior commit: clearPluginLoaderCache() now calls clearActiveMemoryCapability() to clear the hoisted capability on full loader resets (that path is test isolation / full reset, not scope swaps).

Verification (clean env, env -i … OPENCLAW_STATE_DIR=<tmp>):

  • src/plugins/memory-state.test.ts — 14/14 pass
  • src/plugins/loader.test.ts — 104/104 pass
  • src/plugins/loader.runtime-registry.test.ts — 9/9 pass

Pre-commit hook surfaced only the known pre-existing Telegram AbortSignal typecheck at extensions/telegram/src/bot.ts:193,195 (TS2739) — unrelated to this change and authorized to bypass, so the merge commit used --no-verify for that reason.

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

Copy link
Copy Markdown
Contributor

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: 1bf4c5a7d4

ℹ️ 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 thread src/plugins/loader.ts
Comment on lines 2402 to 2405
restoreMemoryPluginState({
capability: previousMemoryCapability,
corpusSupplements: previousMemoryCorpusSupplements,
promptBuilder: previousMemoryPromptBuilder,
promptSupplements: previousMemoryPromptSupplements,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Restore memory capability on failed plugin registration

If a memory plugin calls registerMemoryCapability(...) and then throws during register, this rollback restores only legacy memory state and leaves activeMemoryCapability mutated by the failed plugin. That means subsequent memory prompt/runtime/public-artifact calls can run against a plugin that never successfully loaded. The rollback needs to snapshot and restore capability state alongside the other previousMemory* values.

Useful? React with 👍 / 👎.

Comment on lines +82 to +86
const memorySlot = context.activationSourceConfig.plugins?.slots?.memory?.trim();
if (!memorySlot) {
return scopedPluginIds;
}
return [...new Set([...scopedPluginIds, memorySlot])];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Skip disabled memory slot when building CLI plugin scope

This appends the raw plugins.slots.memory value from activation config without normalizing sentinel values. When users set slots.memory: "none", "none" is added to onlyPluginIds; if a plugin with id none is present, command-scoped CLI loading will unexpectedly activate it even though memory was explicitly disabled. Normalize this slot value the same way as runtime config (none => disabled) before adding it to the scoped plugin list.

Useful? React with 👍 / 👎.

Conflict with openclaw#69537 (perf(plugin-sdk): per-phase + per-jiti-call probes
for bundled channel entries), which extracted the file-local
`shouldProfilePluginLoader` / `profilePluginLoaderSync` helpers into a
new shared module `src/plugins/plugin-load-profile.ts` (exposed as
`withProfile`). Our branch added the plugin-scope-debug helpers just
above the now-removed profile functions, so the regions overlapped.

Resolution: keep the plugin-scope-debug helpers (still consumed by
emitPluginScopeDebugLog call sites in this file); drop the file-local
profile helpers in favour of the shared `withProfile` import (already
pulled in by the merge).

Verified:
- pnpm tsgo (core) — 0 errors
- pnpm test src/plugins/loader.test.ts — 104/104

Committed with --no-verify: pre-commit hook ran tsgo:extensions (public
plugin contract triggers extensions lane) and flagged 2 pre-existing
TS2739 errors in extensions/telegram/src/bot.ts (cross-realm AbortSignal
type divergence between grammY node-fetch types and Node globals, noted
in the comment at the call site). Errors pre-date this merge; unrelated
to the conflict resolution.

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

Copy link
Copy Markdown
Contributor

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: eec662d943

ℹ️ 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 thread src/plugins/loader.ts
Comment on lines 2363 to 2366
restoreMemoryPluginState({
capability: previousMemoryCapability,
corpusSupplements: previousMemoryCorpusSupplements,
promptBuilder: previousMemoryPromptBuilder,
promptSupplements: previousMemoryPromptSupplements,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Restore memory capability after activate:false snapshot loads

The activate:false path is intended to be read-only, but this restore block now only reinstates legacy memory state and leaves activeMemoryCapability as whatever the snapshot load's plugin registration last set. If a snapshot load (for example, provider/status discovery paths that call loadOpenClawPlugins with activate:false) touches a memory plugin, it can silently change the process-global memory capability even though no activating reload occurred, so later calls like getMemoryRuntime, buildMemoryPromptSection, or listActiveMemoryPublicArtifacts can run against the wrong plugin.

Useful? React with 👍 / 👎.

@prtags

prtags Bot commented Apr 23, 2026

Copy link
Copy Markdown

Related work from PRtags group cheerful-ox-0bzt

Title: Open PR duplicate: memory capability lost across plugin scopes

Number Title
#68041 fix: load all plugins for wiki/memory CLI + preserve memory capability during snapshot loads
#69458* fix(memory): keep active memory capability visible across plugin scopes

* This PR

@vincentkoc

Copy link
Copy Markdown
Member

Thanks for the detailed repro work. The Memory Wiki bridge 0-artifacts/0-workspaces path has been closed by #72615, which routes the affected CLI commands through the Gateway runtime and adds focused bridge/CLI regressions. Closing this broader plugin-scope capability approach as superseded for the now-closed bridge issue.

@vincentkoc vincentkoc closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling extensions: memory-wiki gateway Gateway runtime size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants