Skip to content

perf(plugins): reuse startup runtime registry#76004

Merged
steipete merged 3 commits into
openclaw:mainfrom
DmitryPogodaev:perf/memoize-prepared-extra-params
May 2, 2026
Merged

perf(plugins): reuse startup runtime registry#76004
steipete merged 3 commits into
openclaw:mainfrom
DmitryPogodaev:perf/memoize-prepared-extra-params

Conversation

@DmitryPogodaev

Copy link
Copy Markdown
Contributor

Summary

Memoize resolvePreparedExtraParams per OpenClawConfig object identity. Profiling on a stable warm gateway shows it synchronously costs ~1.9 sec per embedded turn even when nothing about the agent / model / config has changed.

Background

resolvePreparedExtraParams runs on every embedded turn through buildAgentRuntimePlan. It calls two provider plugin hooks (prepareProviderExtraParams, resolveProviderExtraParamsForTransport) which trigger provider runtime plugin resolution. The result is deterministic for stable inputs (provider, modelId, agentId, workspaceDir, agentDir, thinkingLevel, resolvedTransport, extraParamsOverride, resolvedExtraParams) within a given config lifetime, but currently is recomputed on every dispatch.

Approach

  • Outer WeakMap keyed by OpenClawConfig object reference. Hot-reload installs a fresh config object via setRuntimeConfig(...), so the previous config bucket is garbage-collected automatically — no manual invalidation hook needed.
  • Inner Map keyed by a deterministic JSON serialization of the call-site inputs that influence the prepared extra-params output.

Inputs intentionally excluded from the key

  • params.model — its identity is captured indirectly via provider+modelId and through resolvedTransport when the transport hook needs it; including the full ProviderRuntimeModel object would defeat caching for callers that build a fresh model object per turn.
  • params.cfg — already used as the cache bucket identity.

Risk note

Provider plugins implementing prepareExtraParams / resolveProviderExtraParamsForTransport are expected to be deterministic for a given (config, provider, model, thinkingLevel, extraParams) tuple. All bundled providers satisfy this. If a future plugin needs dynamic state (env var, time-of-day, runtime telemetry) within these hooks, caching would mask that within one config lifetime — but that pattern is already incompatible with the surrounding embedded-run infrastructure that resolves these hooks exactly once per turn anyway.

If maintainers prefer a feature-flagged opt-in or invalidation hooked to plugin registry version changes, happy to adapt.

Measurements

Personal VPS, codex harness, warm gateway, simple ping message:

Stage Before After (expected)
buildAgentRuntimePlan (sync) ~2.8 s ~0.9 s
Total pre-codex setup ~6.5 s ~4.6 s

Combined with the sibling PR for resolveTranscriptPolicy, pre-codex setup drops to ~3.7 s.

Test plan

  • Existing tests in extra-params.test.ts should pass unchanged.
  • On a real deployment, send several messages and verify the second+ dispatches show no prepareProviderExtraParams re-resolution overhead in profiling.

🤖 Patch authored after debugging slow message dispatch on a personal VPS.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels May 2, 2026
@clawsweeper

clawsweeper Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge.

Summary
The PR memoizes prepared provider extra params and rewires provider, tool, channel, web, capability, memory, and migration runtime helpers to reuse loaded startup or standalone plugin registries.

Reproducibility: yes. A source-level reproduction is to call resolvePluginProviders, resolvePluginWebProviders, resolvePluginTools, getActiveMemorySearchManager, or outbound channel resolution on the PR head with a configured plugin and no compatible active registry; the PR returns empty or unavailable results where current main loads the scoped runtime registry.

Next step before merge
The remaining blockers are cross-cutting plugin runtime loading and security-sensitive registry compatibility decisions that should be settled by a maintainer before repair automation changes the branch.

Security
Needs attention: The diff still has a concrete env-scoping risk in plugin registry reuse, which is a plugin code-execution boundary.

Review findings

  • [P2] Restore provider loads on registry misses — src/plugins/providers.runtime.ts:312-319
  • [P2] Restore web provider cold loads — src/plugins/web-provider-runtime-shared.ts:190-195
  • [P2] Preserve outbound channel bootstrap recovery — src/infra/outbound/channel-bootstrap.runtime.ts:8-12
Review details

Best possible solution:

Keep the extra-param cache, but make registry reuse a compatible fast path only and preserve the existing cold-load, auto-enable, and recovery fallbacks for runtime helpers.

Do we have a high-confidence way to reproduce the issue?

Yes. A source-level reproduction is to call resolvePluginProviders, resolvePluginWebProviders, resolvePluginTools, getActiveMemorySearchManager, or outbound channel resolution on the PR head with a configured plugin and no compatible active registry; the PR returns empty or unavailable results where current main loads the scoped runtime registry.

Is this the best way to solve the issue?

No. Memoizing prepared extra params per stable config is a narrow maintainable optimization, but replacing resolver fallbacks with loaded-registry-only reads across shared plugin surfaces is not the safest implementation boundary.

Full review comments:

  • [P2] Restore provider loads on registry misses — src/plugins/providers.runtime.ts:312-319
    Runtime provider resolution now only probes a loaded compatible registry and returns [] on a miss. Existing provider hook paths rely on this helper to load the scoped provider registry on cold starts, so configured provider plugins can be skipped before their hooks run.
    Confidence: 0.9
  • [P2] Restore web provider cold loads — src/plugins/web-provider-runtime-shared.ts:190-195
    After a compatible-registry miss, the non-empty web-provider scope now falls through to return []. Search/fetch providers that have not already been loaded at startup disappear on cold runtime paths, so keep the loaded-registry fast path but fall back to the scoped load used on main.
    Confidence: 0.9
  • [P2] Preserve outbound channel bootstrap recovery — src/infra/outbound/channel-bootstrap.runtime.ts:8-12
    bootstrapOutboundChannelPlugin is now a no-op, but outbound resolution still calls it after loaded-channel misses. That removes the recovery path that auto-enables and loads the requested channel plugin after registry refreshes or cold outbound sends.
    Confidence: 0.86
  • [P2] Load memory slots before reporting unavailable — src/plugins/memory-runtime.ts:16-21
    When no memory runtime is active, this now probes the loaded registry and then immediately reads the same missing runtime. Configured memory slots need this path to load the slot plugin before getActiveMemorySearchManager reports memory unavailable.
    Confidence: 0.87
  • [P2] Keep plugin tools from disappearing on cold registries — src/plugins/tools.ts:315-321
    resolvePluginTools now only reads the loaded channel registry. Direct callers that have not first called the new standalone ensure helper can lose plugin tools, so either keep a scoped resolver/load fallback here or make the helper contract impossible to bypass.
    Confidence: 0.82
  • [P2] Honor env compatibility for active-registry reuse — src/plugins/active-runtime-registry.ts:92-104
    env is accepted here, but callers without loadOptions are still matched only by workspace and plugin ids. Provider hook lookups with a custom env can reuse a registry loaded under another OPENCLAW_HOME or plugin root, so env-scoped calls should require full load-option compatibility or miss.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.91

Security concerns:

  • [medium] Env-scoped calls can reuse the wrong registry — src/plugins/active-runtime-registry.ts:92
    getLoadedRuntimePluginRegistry() accepts env, but env-only callers without loadOptions fall through to workspace/plugin-id matching. A custom-env provider hook can observe plugin code and policy from a registry loaded under a different OPENCLAW_HOME or plugin root.
    Confidence: 0.86

Acceptance criteria:

  • pnpm test src/plugins/providers.test.ts src/plugins/web-provider-runtime-shared.test.ts src/plugins/memory-runtime.test.ts src/plugins/tools.optional.test.ts src/infra/outbound/channel-resolution.test.ts src/infra/outbound/message.test.ts
  • pnpm check:changed

What I checked:

  • Current main loads runtime registries on compatible miss: resolveRuntimePluginRegistry() checks for a compatible active registry and then falls back to loadOpenClawPlugins(options) when no compatible registry is present and no load is in flight. (src/plugins/loader.ts:1200, 695960975a60)
  • PR provider runtime returns empty on loaded-registry miss: The PR changes runtime provider resolution to call getLoadedRuntimePluginRegistry(...) and return [] on a miss, instead of using the current main resolver/load fallback. (src/plugins/providers.runtime.ts:312, 4f3b95166c95)
  • PR web provider runtime drops cold load fallback: The PR maps compatible loaded registries, but after non-in-flight scoped misses it reaches return [] where current main calls loadOpenClawPlugins(loadOptions). (src/plugins/web-provider-runtime-shared.ts:195, 4f3b95166c95)
  • PR outbound channel bootstrap is a no-op: The PR replaces the current auto-enable plus resolveRuntimePluginRegistry(...) recovery path with void params, while resolveOutboundChannelPlugin() still calls this hook after loaded-channel misses. (src/infra/outbound/channel-bootstrap.runtime.ts:12, 4f3b95166c95)
  • PR env-scoped active lookup remains under-scoped: getLoadedRuntimePluginRegistry() accepts env, but env-only callers without loadOptions fall through to workspace/plugin-id checks; provider hooks call it with env and workspaceDir only. (src/plugins/active-runtime-registry.ts:92, 4f3b95166c95)
  • Maintainer history points to plugin runtime owners: Recent GitHub commit history for loader/provider/web/tool/memory paths is concentrated around steipete, with adjacent plugin-tool/capability work from shakkernerd and memory runtime work from vincentkoc.

Likely related people:

  • steipete: Recent commits on the central plugin loader, provider runtime, web-provider runtime, plugin tools, and the PR branch itself all route through this maintainer; local blame also points current main’s relevant loader/provider/bootstrap lines at the current shallow main commit by Peter Steinberger. (role: recent maintainer and likely follow-up owner; confidence: high; commits: 738cf18a0f17, ed8f50f240a8, d13a2063c421; files: src/plugins/loader.ts, src/plugins/providers.runtime.ts, src/plugins/web-provider-runtime-shared.ts)
  • shakkernerd: Recent merged work scoped plugin registry reuse, tool manifest gating, and capability-provider discovery, which overlap the PR’s registry reuse and tool/capability surfaces. (role: adjacent owner; confidence: medium; commits: 53c2dbe9e92c, 3cf1dd982ba0, 7641783d6b0e; files: src/plugins/loader.ts, src/plugins/tools.ts, src/plugins/capability-provider-runtime.ts)
  • vincentkoc: Recent history includes memory runtime plugin loading and broader plugin cycle/activation work, which overlaps the memory-runtime regression surface in this PR. (role: adjacent owner; confidence: medium; commits: b96a75c95b54, 7198a9f0eeed, 982383338373; files: src/plugins/memory-runtime.ts, src/plugins/runtime.ts)

Remaining risk / open question:

  • The patch touches plugin code-execution and registry reuse paths, so the env-scoping concern is security-sensitive rather than a purely functional cache bug.
  • No tests were run because this was a read-only review; the verdict is based on source, PR diff, current-main behavior, and existing test expectations.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 695960975a60.

@steipete steipete force-pushed the perf/memoize-prepared-extra-params branch from d61869e to 983a94f Compare May 2, 2026 11:38
@steipete steipete force-pushed the perf/memoize-prepared-extra-params branch from 983a94f to 499ccb1 Compare May 2, 2026 11:59
@openclaw-barnacle openclaw-barnacle Bot added the commands Command implementations label May 2, 2026
Co-authored-by: DmitryPogodaev <pogodaev.dm@gmail.com>
@steipete steipete force-pushed the perf/memoize-prepared-extra-params branch from 499ccb1 to 3800850 Compare May 2, 2026 12:04
@openclaw-barnacle openclaw-barnacle Bot added the cli CLI command changes label May 2, 2026
@steipete steipete changed the title perf(agents/pi-embedded): memoize resolvePreparedExtraParams per config perf(plugins): reuse startup runtime registry May 2, 2026
@steipete steipete merged commit 8283c5d into openclaw:main May 2, 2026
109 of 110 checks passed
@steipete

steipete commented May 2, 2026

Copy link
Copy Markdown
Contributor

Landed in main.

  • Gate: local focused runtime/plugin tests, tsgo:core:test, git diff --check; exact PR SHA CI green on 4f3b951.
  • PR head before squash: 4f3b951
  • Merge commit: 8283c5d

Thanks @DmitryPogodaev!

steipete pushed a commit to hclsys/moltbot that referenced this pull request May 3, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
steipete pushed a commit to hclsys/moltbot that referenced this pull request May 3, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
steipete pushed a commit that referenced this pull request May 3, 2026
…76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR #76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…penclaw#76598)

`resolvePluginTools` returned an empty tool list when no pre-warmed
channel/active registry was found after startup — the on-demand fallback
removed by PR openclaw#76004 was only added back for memory and capability-provider
surfaces, leaving path-based (origin "config") plugin tool factories silent.

Fix: when `resolvePluginToolRegistry` returns null, trigger a standalone
registry load via `ensureStandaloneRuntimePluginRegistryLoaded`, then retry.
Adds regression test asserting tools are resolved without pre-warming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling cli CLI command changes commands Command implementations size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants