You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ensureStandaloneRuntimePluginRegistryLoaded (called from dispatchReplyFromConfig via ensureRuntimePluginsLoaded) reloads the full standalone runtime plugin registry on the first inbound dispatch per process, even when loadGatewayStartupPluginRuntime has already populated an active registry at boot. This adds ~4.4s + ~25 MB heap allocation to the first user's dispatch latency.
Instrumented heap profiling on a clean prod-mirror host (Hetzner CX33, 2 vCPU, mock-LLM, rebased onto current origin/main HEAD b1abf9d8ae) shows the lazy load fires inside the dispatch path, after gateway boot completes:
Cohort
totalMs
dispatchMs
heapDeltaDispMB
gcCount
Warm boot, first fresh-user DM
10185
8478
260
24
Second fresh-user DM (registry warm)
6770
5000
7-15
10
The 4-5s + ~250 MB transient delta on DM#1 is loadOpenClawPlugins running inside the request, not anything user-specific.
Send a second message (any user); dispatch latency drops 40-50%
Root cause
getLoadedRuntimePluginRegistry (src/plugins/active-runtime-registry.ts:71-105) — when called with surface: "active", loadOptions, and requiredPluginIds.length > 0, uses strict cache-key equality via resolveCompatibleRuntimePluginRegistry(loadOptions):
Gateway-startup (loadGatewayStartupPluginRuntime) builds PluginLoadOptions with 9+ fields (onlyPluginIds, activationSourceConfig, autoEnabledReasons, preferSetupRuntimeForChannelPlugins, ...). Dispatch-time ensureRuntimePluginsLoaded builds 3-field options (config, workspaceDir, optional runtimeOptions). Cache keys never match → strict path returns undefined → full loadOpenClawPlugins reload on the request hot path.
This is the same bug family addressed by closed PR #74118 (proposed a fast-path on getActivePluginRegistry(); closed per Codex feedback on missing workspace-compat handling).
Proposed fix direction
In getLoadedRuntimePluginRegistry, when the strict cache-key match misses, fall through to the existing workspace+plugin-id compatibility check on the active registry (already present in lines 92-104 for the non-loadOptions case) instead of returning undefined:
if(surface==="active"&¶ms.loadOptions&&requiredPluginIds?.length!==0){constcompatible=resolveCompatibleRuntimePluginRegistry(params.loadOptions);if(compatible&®istryContainsPluginIds(compatible,requiredPluginIds)){returncompatible;}// Fall through to workspace+plugin-id check on active registry// instead of returning undefined unconditionally}// Existing fall-through (workspace check + registryContainsPluginIds) now// also serves the strict-cache-miss case
Preserves laziness per src/plugins/CLAUDE.md — still loads on demand when active registry is genuinely missing or workspace-incompatible
Affects both ensureStandaloneRuntimePluginRegistryLoaded and ensureRuntimePluginsLoaded (the wrapper), so dispatch-path and standalone-path callers both benefit
Artifacts available on request: 3 .heapprofile files (1.8–3.4 MB), 2 heap snapshots (181 MB each), full latency-trace logs.
Environment
OpenClaw main at b1abf9d8ae (chore(release): refresh base config schema)
Node 24.14.0, Linux 6.x (Debian bookworm), Docker, 2 vCPU CX33
Multiagent extension under test (oneclaw-multiagent) routes dispatch through stock dispatchReplyFromConfig — bottleneck is in upstream loader, not extension code
I have a draft PR with the patch + workspace-compat regression test + changelog entry. Will link once posted.
Summary
ensureStandaloneRuntimePluginRegistryLoaded(called fromdispatchReplyFromConfigviaensureRuntimePluginsLoaded) reloads the full standalone runtime plugin registry on the first inbound dispatch per process, even whenloadGatewayStartupPluginRuntimehas already populated an active registry at boot. This adds ~4.4s + ~25 MB heap allocation to the first user's dispatch latency.Instrumented heap profiling on a clean prod-mirror host (Hetzner CX33, 2 vCPU, mock-LLM, rebased onto current
origin/mainHEADb1abf9d8ae) shows the lazy load fires inside the dispatch path, after gateway boot completes:The 4-5s + ~250 MB transient delta on DM#1 is
loadOpenClawPluginsrunning inside the request, not anything user-specific.Reproduction
~/.openclaw/{shared/users.json,plugins/<id>/user-agents.json,sandbox/containers,openclaw.json*}loadOpenClawPluginsallocations underdispatchReplyFromConfig → ensureRuntimePluginsLoaded → ensureStandaloneRuntimePluginRegistryLoadedRoot cause
getLoadedRuntimePluginRegistry(src/plugins/active-runtime-registry.ts:71-105) — when called withsurface: "active",loadOptions, andrequiredPluginIds.length > 0, uses strict cache-key equality viaresolveCompatibleRuntimePluginRegistry(loadOptions):Gateway-startup (
loadGatewayStartupPluginRuntime) buildsPluginLoadOptionswith 9+ fields (onlyPluginIds,activationSourceConfig,autoEnabledReasons,preferSetupRuntimeForChannelPlugins, ...). Dispatch-timeensureRuntimePluginsLoadedbuilds 3-field options (config,workspaceDir, optionalruntimeOptions). Cache keys never match → strict path returnsundefined→ fullloadOpenClawPluginsreload on the request hot path.This is the same bug family addressed by closed PR #74118 (proposed a fast-path on
getActivePluginRegistry(); closed per Codex feedback on missing workspace-compat handling).Proposed fix direction
In
getLoadedRuntimePluginRegistry, when the strict cache-key match misses, fall through to the existing workspace+plugin-id compatibility check on the active registry (already present in lines 92-104 for the non-loadOptions case) instead of returningundefined:Properties:
src/plugins/CLAUDE.md— still loads on demand when active registry is genuinely missing or workspace-incompatibleensureStandaloneRuntimePluginRegistryLoadedandensureRuntimePluginsLoaded(the wrapper), so dispatch-path and standalone-path callers both benefitEvidence
--heap-prof, 64K interval) — pre-rebase + post-rebase. Top call site:ensureStandaloneRuntimePluginRegistryLoaded → loadOpenClawPluginsat ~25 MB cumulative inside dispatch.origin/mainHEAD:Artifacts available on request: 3
.heapprofilefiles (1.8–3.4 MB), 2 heap snapshots (181 MB each), full latency-trace logs.Environment
mainatb1abf9d8ae(chore(release): refresh base config schema)oneclaw-multiagent) routes dispatch through stockdispatchReplyFromConfig— bottleneck is in upstream loader, not extension codeI have a draft PR with the patch + workspace-compat regression test + changelog entry. Will link once posted.