Skip to content

image/video/music generate tools eagerly list capability providers, causing ~15-18s reload per turn #74096

@poolside-ventures

Description

@poolside-ventures

Bug type

Behavior bug (incorrect output/state without crash) — performance.

Beta release blocker

No

Summary

The image-generate, video-generate, and music-generate agent tools eagerly list runtime capability providers during tool registration, even when the agent has an explicit and complete modelConfig and the provider list goes unused. On hosted gateways with plugins.entries non-empty, each provider listing triggers a full plugin-registry reload (~5–6s per call), adding ~15–18s of wall-clock latency to every agent turn for capabilities the agent isn't using.

Steps to reproduce

  1. Run any gateway with openclaw.json containing at least one plugin in plugins.entries. Confirmed against 2026.4.26-f53b52ad6d21.
  2. Connect any agent that registers the built-in image-generate / video-generate / music-generate tools (i.e. all agents, since these are core agent tools).
  3. Send a single inbound message.
  4. Observe register() being invoked 3 additional times during the turn (4 total counting the initial load), with stack runPluginRegisterSync ← loadOpenClawPlugins ← resolveRuntimePluginRegistry ← resolvePluginCapabilityProviders ← resolvePluginImageGenerationProviders ← buildProviderMaps ← listImageGenerationProviders (and likewise for video / music).

Observed log (top of stack on the 3 in-turn reloads):

register #3 caller: ... resolvePluginCapabilityProviders ← resolvePluginImageGenerationProviders ← buildProviderMaps ← listImageGenerationProviders
register #4 caller: ... resolvePluginCapabilityProviders ← resolvePluginVideoGenerationProviders ← buildProviderMaps ← listVideoGenerationProviders
register #5 caller: ... resolvePluginCapabilityProviders ← resolvePluginMusicGenerationProviders ← buildProviderMaps ← listMusicGenerationProviders

Expected behavior

When an agent has an explicit complete modelConfig for image / video / music generation, resolveCapabilityModelConfigForTool short-circuits at the hasToolModelConfig(explicit) check and returns without using the provider list. In that path, listing providers is pure waste — and on hosted gateways it's a 5–6s cache miss per call.

Actual behavior

resolveCapabilityModelConfigForTool (in src/agents/tools/media-tool-shared.ts:246) takes providers: CapabilityProvider[] as a required eagerly-evaluated parameter. The 3 callers (image-generate-tool.ts:202, video-generate-tool.ts:232, music-generate-tool.ts:138) call listRuntime{Image,Video,Music}GenerationProviders({ config: cfg }) to compute it before calling resolveCapabilityModelConfigForTool, so providers are listed even when hasToolModelConfig(explicit) would short-circuit.

listRuntime{...}GenerationProviders flows through resolvePluginCapabilityProviders (src/plugins/capability-provider-runtime.ts:316) which calls resolveRuntimePluginRegistry({config: compatConfig, activate: false}). That's a cache key not held by the active registry, so it falls through to loadOpenClawPlugins, re-imports every plugin, and re-runs each plugin's register() (~5–6s each in production).

This is independent of #73793 — it's about avoiding the lookup entirely when not needed.

Environment

  • OpenClaw 2026.4.26-f53b52ad6d21 (production gateway)
  • Node v24.14.0
  • Linux x64 (Elestio-hosted Docker container)
  • Active plugins: memory-core, plus a third-party channel plugin

Logs / evidence

Per-turn timing from instrumented third-party plugin (paths trimmed):

register #3 caller: runPluginRegisterSync ← loadOpenClawPlugins ← resolveRuntimePluginRegistry ← resolvePluginCapabilityProviders ← resolvePluginImageGenerationProviders ← buildProviderMaps ← listImageGenerationProviders
register #4 caller: ... ← resolvePluginVideoGenerationProviders ← buildProviderMaps ← listVideoGenerationProviders
register #5 caller: ... ← resolvePluginMusicGenerationProviders ← buildProviderMaps ← listMusicGenerationProviders

Proposed fix

Change the providers parameter on resolveCapabilityModelConfigForTool from CapabilityProvider[] to () => CapabilityProvider[] so the listing is deferred until the function actually needs it (i.e. when modelConfig is incomplete and candidate resolution must run). Update the 3 internal callers to pass thunks. PR with fix + regression tests filed alongside.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions