openclaw - ✅(Solved) Fix perf: resolvePluginProviders called repeatedly on ARM64, active-memory subagent takes 66-96s [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#73075Fetched 2026-04-28 06:27:49
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
closed ×1cross-referenced ×1subscribed ×1

On ARM64 devices (tested: Raspberry Pi 4, 4 GB RAM), the active-memory plugin's subagent takes 66–96 seconds from message receipt to first LLM call. On x86 this is likely masked by faster CPUs, but the underlying cache bugs affect all platforms.

Three root-cause bugs cause resolvePluginProviders (~20s on ARM64) to be called repeatedly instead of once:


Root Cause

On ARM64 devices (tested: Raspberry Pi 4, 4 GB RAM), the active-memory plugin's subagent takes 66–96 seconds from message receipt to first LLM call. On x86 this is likely masked by faster CPUs, but the underlying cache bugs affect all platforms.

Three root-cause bugs cause resolvePluginProviders (~20s on ARM64) to be called repeatedly instead of once:


Fix Action

Fixed

PR fix notes

PR #73076: perf: cache model resolution to avoid repeated plugin-provider loads on ARM64

Description (problem / solution / changelog)

Fixes #73075

What

Three caching bugs cause resolvePluginProviders (~20s on ARM64) to be called repeatedly. This PR fixes all three.

Bug 1 — buildModelsJsonFingerprint includes modelsFileMtimeMs After planOpenClawModelsJson writes models.json, the stored cache entry (computed pre-write) never matches the new mtime again. Every subsequent caller re-runs the full planOpenClawModelsJson flow.

Fix: remove modelsFileMtimeMs from the fingerprint. The content comparison inside planOpenClawModelsJson already handles external changes.

Bug 2 — readyCache has one entry per path; different configs overwrite each other When agents with different configs (e.g. main agent and an active-memory subagent modified by applyActiveMemoryRuntimeConfigSnapshot) call ensureOpenClawModelsJson, they compute different fingerprints and continuously overwrite each other's cache entry.

Fix: add a noopCache to MODELS_JSON_STATE keyed by (path, mtime). A noop result is config-agnostic — if nothing was written, no config variant produces a different outcome.

Bug 3 — resolveExplicitModelWithRegistry re-invokes resolvePluginProviders per call shouldSuppressBuiltInModelresolveProviderPluginsForCatalogHooks re-loads all provider plugins on each agent run because the hook cache key includes the full config.

Fix: cache resolveExplicitModelWithRegistry results by (provider, modelId, agentDir), which is stable for a gateway session.

Measured on Raspberry Pi 4 (ARM64, 4 GB RAM)

MetricBeforeAfter
Subagent preprocessing (warm)66–75s~3s
active-memory total elapsed (warm)~96s~14s
Main agent preprocessing (warm)~65s~200ms

Test plan

  • src/agents/models-config.write-serialization.test.ts — passes
  • src/agents/models-config.skips-writing-models-json-no-env-token.test.ts — passes
  • TypeScript type check (pnpm tsgo) — no errors
  • Verify on ARM64 device (Raspberry Pi 4) — measured above

🤖 Generated with Claude Code

Changed files

  • CHANGELOG.md (modified, +2/-1)
  • docs/plugins/architecture-internals.md (modified, +1/-1)
  • docs/plugins/manifest.md (modified, +14/-5)
  • docs/plugins/sdk-provider-plugins.md (modified, +1/-1)
  • docs/plugins/sdk-subpaths.md (modified, +1/-1)
  • extensions/feishu/src/setup-surface.test.ts (modified, +1/-0)
  • extensions/memory-lancedb/config.test.ts (modified, +12/-1)
  • extensions/memory-lancedb/config.ts (modified, +5/-5)
  • extensions/memory-lancedb/openclaw.plugin.json (modified, +1/-0)
  • extensions/openai/openai-codex-provider.ts (modified, +0/-9)
  • extensions/openai/openai-provider.ts (modified, +0/-14)
  • extensions/openai/test-support/provider-catalog.contract-test-support.ts (modified, +2/-23)
  • extensions/qwen/index.test.ts (modified, +2/-36)
  • extensions/qwen/index.ts (modified, +0/-39)
  • extensions/qwen/openclaw.plugin.json (modified, +22/-0)
  • scripts/test-projects.test-support.mjs (modified, +8/-0)
  • src/agents/model-suppression.test.ts (modified, +5/-16)
  • src/agents/model-suppression.ts (modified, +3/-12)
  • src/agents/models-config.ts (modified, +32/-8)
  • src/agents/models-config.write-serialization.test.ts (modified, +39/-0)
  • src/agents/pi-embedded-runner/model.forward-compat.test.ts (modified, +0/-1)
  • src/agents/pi-embedded-runner/model.startup-retry.test.ts (modified, +0/-1)
  • src/cli/plugins-cli.install.test.ts (modified, +26/-0)
  • src/infra/provider-usage.auth.normalizes-keys.test.ts (modified, +0/-1)
  • src/model-catalog/manifest-planner.test.ts (modified, +6/-0)
  • src/model-catalog/manifest-planner.ts (modified, +2/-0)
  • src/model-catalog/normalize.test.ts (modified, +8/-0)
  • src/model-catalog/normalize.ts (modified, +15/-0)
  • src/model-catalog/types.ts (modified, +4/-0)
  • src/plugin-sdk/provider-catalog-runtime.ts (modified, +0/-1)
  • src/plugin-sdk/provider-test-contracts.ts (modified, +0/-1)
  • src/plugin-sdk/test-helpers/provider-catalog.ts (modified, +3/-10)
  • src/plugin-sdk/testing.ts (modified, +0/-1)
  • src/plugins/manifest-model-suppression.test.ts (modified, +88/-0)
  • src/plugins/manifest-model-suppression.ts (modified, +75/-1)
  • src/plugins/provider-hook-runtime.ts (modified, +134/-41)
  • src/plugins/provider-runtime.test-support.ts (modified, +0/-25)
  • src/plugins/provider-runtime.test.ts (modified, +281/-27)
  • src/plugins/provider-runtime.ts (modified, +12/-57)
  • src/plugins/types.ts (modified, +4/-6)
  • test/scripts/test-projects.test.ts (modified, +11/-0)
RAW_BUFFERClick to expand / collapse

Summary

On ARM64 devices (tested: Raspberry Pi 4, 4 GB RAM), the active-memory plugin's subagent takes 66–96 seconds from message receipt to first LLM call. On x86 this is likely masked by faster CPUs, but the underlying cache bugs affect all platforms.

Three root-cause bugs cause resolvePluginProviders (~20s on ARM64) to be called repeatedly instead of once:


Bug 1: ensureOpenClawModelsJson fingerprint includes modelsFileMtimeMs

File: src/agents/models-config.tsbuildModelsJsonFingerprint

The fingerprint includes the mtime of models.json. After planOpenClawModelsJson writes the file, the stored readyCache entry was computed with the pre-write mtime. Every subsequent caller computes a fingerprint with the post-write mtime → always mismatches → always re-runs planOpenClawModelsJson.

The content comparison inside planOpenClawModelsJson already handles external changes; the mtime is redundant and harmful here.


Bug 2: readyCache has one entry per file path — different configs overwrite each other

File: src/agents/models-config.tsensureOpenClawModelsJson

MODELS_JSON_STATE.readyCache is a Map<targetPath, Promise<...>>. When a main agent and an active-memory subagent (whose config is modified by applyActiveMemoryRuntimeConfigSnapshot) both call ensureOpenClawModelsJson, they compute different fingerprints and continuously overwrite each other's cache entry. Neither ever gets a cache hit.

A noop result means the generated models.json content already matches disk — this is true regardless of which config variant called it. A cross-config noop cache keyed by (path, mtime) allows all callers to share the result.


Bug 3: resolveExplicitModelWithRegistry re-invokes resolvePluginProviders per call

File: src/agents/pi-embedded-runner/model.ts

resolveExplicitModelWithRegistry calls shouldSuppressBuiltInModel which calls resolveProviderPluginsForCatalogHooksresolvePluginProviders. The provider-runtime hook cache key includes the full config, so callers with slightly different configs (main agent vs subagent) each pay the full ~20s provider-load cost independently.

The result of resolveExplicitModelWithRegistry is stable within a gateway session (model definitions come from openclaw.json or models.json, neither changes without a restart).


Measured impact (Raspberry Pi 4, ARM64)

MetricBeforeAfter fix
Subagent preprocessing (warm)66–75s~3s
active-memory total elapsed (warm)~96s~14s
Main agent preprocessing (warm)~65s~0.2s

The remaining ~14s is dominated by the actual LLM call (~11s).


Proposed fix

PR: https://github.com/jochen/openclaw/pull/new/fix/arm64-model-resolution-performance

  1. Remove modelsFileMtimeMs from buildModelsJsonFingerprint (bug 1)
  2. Add noopCache: Map<path, {mtime, result}> to MODELS_JSON_STATE; populate it after any noop result and check it at the top of ensureOpenClawModelsJson (bug 2)
  3. Cache resolveExplicitModelWithRegistry results by (provider, modelId, agentDir) (bug 3)

All existing models-config tests pass. The fix is ~90 lines.

extent analysis

TL;DR

The most likely fix involves addressing three root-cause bugs by modifying the buildModelsJsonFingerprint function, implementing a noopCache in MODELS_JSON_STATE, and caching resolveExplicitModelWithRegistry results.

Guidance

  • Review the buildModelsJsonFingerprint function in src/agents/models-config.ts to ensure it does not include the modelsFileMtimeMs in the fingerprint, as this causes repeated calls to resolvePluginProviders.
  • Implement a noopCache in MODELS_JSON_STATE to store results of ensureOpenClawModelsJson when the generated models.json content already matches disk, allowing all callers to share the result.
  • Cache the results of resolveExplicitModelWithRegistry by (provider, modelId, agentDir) to prevent repeated invocations of resolvePluginProviders due to slightly different configs.

Example

// Example of how to modify buildModelsJsonFingerprint
function buildModelsJsonFingerprint(modelsJsonContent: string): string {
  // Remove modelsFileMtimeMs from the fingerprint
  const fingerprint = crypto.createHash('sha256');
  fingerprint.update(modelsJsonContent);
  return fingerprint.digest('hex');
}

// Example of how to implement noopCache in MODELS_JSON_STATE
const MODELS_JSON_STATE = {
  // ...
  noopCache: new Map<string, { mtime: number; result: any }>(),
};

function ensureOpenClawModelsJson(targetPath: string): Promise<any> {
  // Check noopCache at the top of the function
  const cachedResult = MODELS_JSON_STATE.noopCache.get(targetPath);
  if (cachedResult) {
    return cachedResult.result;
  }
  // ...
}

// Example of how to cache resolveExplicitModelWithRegistry results
const resolveExplicitModelWithRegistryCache = new Map<string, any>();

function resolveExplicitModelWithRegistry(provider: string

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING