[AI] fix(memory): backfill provider.model with resolved model name in…#91660
[AI] fix(memory): backfill provider.model with resolved model name in…#91660xydt-tanshanshan wants to merge 3 commits into
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 5:57 AM ET / 09:57 UTC. Summary PR surface: Source +3, Tests +101. Total +104 across 2 files. Reproducibility: yes. from source inspection rather than an executed current-main repro: current main can return an adapter's empty Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land the narrow source-layer backfill after maintainers accept the upgrade-sensitive memory identity normalization, while keeping broader provider auto-detection and migration policy on separate related work. Do we have a high-confidence way to reproduce the issue? Yes, from source inspection rather than an executed current-main repro: current main can return an adapter's empty Is this the best way to solve the issue? Yes. Backfilling at AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c86fe21cada0. Label changesLabel justifications:
Evidence reviewedPR surface: Source +3, Tests +101. Total +104 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (5 earlier review cycles)
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@vincentkoc this is ready for review — 🐚 platinum hermit, proof supplied. Small fix: backfills empty provider.model with the resolved adapter model in createWithAdapter(), preventing invalid meta when model discovery runs before model resolution. Mind taking a look? |
5fce864 to
fae2c94
Compare
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
… createWithAdapter resolveProviderModel() correctly resolves the embedding model name (e.g. adapter.defaultModel like text-embedding-3-small), but the resolved value was only passed to adapter.create() options and never backfilled onto the returned provider.model field. This caused provider.model to remain empty, making downstream identity checks in MemoryIndexManager always report mismatched and Dirty: yes, corrupting index meta, cache keys, and vector search queries. Backfill provider.model with resolvedModel when result.provider.model is empty, fixing the root cause at the single source rather than adding fallbacks at 13 downstream sites. Refs openclaw#90042
…WithAdapter Two new tests: 1. adapter returns provider.model= → backfill fills resolvedModel 2. adapter already populates provider.model → preserved unchanged Addresses ClawSweeper real-behavior-proof requirement on PR for openclaw#90042.
Prove that existing indexes with empty adapter model metadata transition safely after the backfill: stored meta (written by the identity path) already carries the correct model; after upgrade, provider.model matches the stored meta, preventing spurious identity mismatches on first boot. Ref: openclaw#90042, openclaw#91691
|
@clawsweeper re-review |
fae2c94 to
7019b4f
Compare
|
Regarding the The change is one line at Behavior Matrix
Only Scenario 2 ChangesScenario 2 is exactly the bug reported in issue #90042 — when the user does not specify a model and relies on Upgrade Compatibility Conclusion
The 3 regression tests in this PR already cover all 4 scenarios. Let me know if you'd like an additional test explicitly verifying "existing index upgrades from empty model". @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Live behavior proof: provider.model backfill + real embeddingRegarding P1 Risk 2 ("not a full live existing-index reindex plus memory_search query"), here is a live proof using the production Real environment tested: Linux x86_64, Node v24.13.1, PR branch Exact steps or command run after this patch: node --import tsx --input-type=module -e "
import { createEmbeddingProvider } from './extensions/memory-core/src/memory/embeddings.ts';
// Scenario 1: Explicit model + live embedding
const r1 = await createEmbeddingProvider({
provider: 'openai-compatible',
model: 'Qwen3-Embedding-8B',
config: { models: { providers: { 'openai-compatible': {
api: 'openai', baseUrl: 'https://maas-apigateway.dt.zte.com.cn/model/qwen3-embedding-8b/v1',
apiKey: '<redacted>'
} } } },
remote: { baseUrl: 'https://maas-apigateway.dt.zte.com.cn/model/qwen3-embedding-8b/v1', apiKey: '<redacted>' },
});
console.log('provider.model:', JSON.stringify(r1.provider?.model));
const vec = await r1.provider?.embedQuery?.('hello world from openclaw memory proof');
console.log('embedQuery() dims:', vec?.length);
// Scenario 2: No explicit model — backfill from adapter.defaultModel
const r2 = await createEmbeddingProvider({
provider: 'openai',
model: '',
config: { models: { providers: { openai: {
api: 'openai', baseUrl: 'https://maas-apigateway.dt.zte.com.cn/model/qwen3-embedding-8b/v1',
apiKey: '<redacted>'
} } } },
remote: { baseUrl: 'https://maas-apigateway.dt.zte.com.cn/model/qwen3-embedding-8b/v1', apiKey: '<redacted>' },
});
console.log('provider.model (backfilled):', JSON.stringify(r2.provider?.model));
"Evidence after fix: Observed result after fix:
What was not tested: Full @clawsweeper re-review |
When an adapter returns an empty provider.model, backfill it with the resolved model name from resolveProviderModel. This prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, backfill it with the resolved model name from resolveProviderModel. This prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, backfill it with the resolved model name from resolveProviderModel. This prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
When an adapter returns an empty provider.model, set it to the resolved model name from resolveProviderModel. Uses direct property assignment instead of spread to preserve prototype-defined methods on plugin provider objects (P1 finding). Prevents downstream identity mismatches and broken vector search (issue openclaw#90042). Supersedes openclaw#91660 (31 days old, merge conflicts).
Summary
resolveProviderModel()increateWithAdapter()correctly resolves the embedding model name (falling back toadapter.defaultModelwhen the user-provided model is empty), but the resolved value was only passed toadapter.create()as themodeloption and never backfilled onto the returnedprovider.modelfield. This leftprovider.modelas an empty string"", causing every downstream use ofthis.provider.modelinMemoryIndexManagerto propagate the empty value:refreshIndexIdentityDirty,resolveCurrentIndexIdentityState,runSync) always reported"mismatched"→Dirty: yeswriteMetastamped empty model into SQLite meta → permanent identity corruption on next bootcomputeProviderKey()computed wrong cache hashes based on{ id, model: "" }searchVectorWHERE c.model = ''matched zero rows → broken vector searchwriteChunkswrote empty model into chunk IDs and database columnsBackfill
provider.modelwithresolvedModelwhenresult.provider.modelis empty, fixing the root cause at the single source (createWithAdapter) rather than adding|| this.settings.modelfallbacks at 13 downstream sites.Change Type
Scope
Linked Issue
Motivation
The
memory_searchtool was permanently stuck withDirty: yesbecausethis.provider.modelwas empty aftercreate(). TheresolveProviderModel()function correctly resolved the model name, but the resolved name was not stored back on theproviderobject. This is a regression that affects any user who relies onadapter.defaultModelto fill in their embedding model configuration.A recent partial fix (commit
50aaf1f9b, PR #90816) addressed the identity comparison path inresolveCurrentIndexIdentityStateby addingresolveEmbeddingProviderFallbackModelfallback, but that only fixed one symptom (the "plain status" identity check before provider initialization). After provider initialization,this.provider.modelremains empty, so the bug still manifests at runtime. This PR fixes the root cause.Changes
File:
extensions/memory-core/src/memory/embeddings.tsresolveProviderModel()return value intoresolvedModellocal variableprovider.modelwithresolvedModelwhenresult.provider.modelis empty:{ ...result.provider, model: result.provider.model || resolvedModel }No new imports, no new functions, no config/default surface changes.
Verification
extensions/memory-core/src/memory/embeddings.test.ts— 8 passedextensions/memory-core/src/memory/manager-reindex-state.test.ts— 9 passedReal Behavior Proof
Behavior addressed:
provider.modelis now correctly populated with the resolved model name aftercreateWithAdapter(), fixing the identity mismatch andDirty: yescascade inMemoryIndexManager. Before the fix,provider.modelwas an empty string"", causing identity checks to always report"mismatched",writeMetato stamp empty model into SQLite meta,computeProviderKey()to compute wrong hashes,searchVector WHERE c.model = ''to match zero rows, andwriteChunksto write empty model into chunk IDs.Real environment tested: Linux (Ubuntu), Node.js 22.19+, local checkout on branch
fix/memory-provider-model-backfill-90042. Embedding provider: ZTE MaaSqwen3-embedding-8bendpoint routed viaopenaiadapter withremote.baseUrloverride (local network cannot reachapi.openai.comdue to geographic restrictions).Exact steps or command run after this patch:
Evidence after fix: Terminal output from
pnpm openclaw memory status --deepshowingprovider.modelbackfilled viaadapter.defaultModelwhen no explicit model is configured (the exact bug scenario from issue #90042):Model: text-embedding-3-smallprovesprovider.modelis correctly backfilled fromadapter.defaultModel— before the fix, this field was""(empty string), causing the identity mismatch cascade. The 404 is an environment limitation (ZTE endpoint does not host OpenAI's model), not a code defect.With an explicit model name pointing to the ZTE endpoint, embeddings initialize successfully:
Before-fix comparison from issue #90042 reporter (macOS, v2026.6.1):
provider.model""(empty string)"text-embedding-3-small"(resolved from adapter.defaultModel)"mismatched"→Dirty: yes"matched"→Dirty: nocomputeProviderKeyhash{ id, model: "" }{ id, model: "text-embedding-3-small" }searchVector WHERE c.modelWHERE c.model = ''(0 results)WHERE c.model = 'text-embedding-3-small'(correct)writeMeta model""persisted to SQLite"text-embedding-3-small"persistedObserved result after fix: Root cause fixed at single source (
createWithAdapter, line 198). All 13 downstreamthis.provider.modelusage sites now receive the correct resolved model name instead of empty string. The|| resolvedModelfallback does not override adapters that already populateprovider.modelcorrectly.What was not tested: Full
openclaw memory index --forcewith live embedding provider — the ZTE MaaS embedding endpoint has strict rate limits (HTTP 429) that prevented completing a full reindex of 323 chunks. Thememory status --deepterminal output above serves as the primary real-behavior verification; unit tests are supplemental.Risks
createWithAdapter(), the canonical source for provider creation. Downstream sites unchanged.provider.modelwith the resolved value rather than adding|| this.settings.modelfallbacks at 13 sites, which would be more fragile and harder to maintain.provider.modelcorrectly, the|| resolvedModelfallback does not override it. If the adapter returns empty, the resolved model fills in. No breaking change for existing correctly-populating adapters.50aaf1f9b(PR fix(memory): resolve adapter default model in plain status identity check #90816) added aresolveEmbeddingProviderFallbackModelfallback inresolveCurrentIndexIdentityStatefor the pre-initialization path. This PR fixes the post-initialization root cause, making the fix(memory): resolve adapter default model in plain status identity check #90816 fallback no longer necessary for the identity check (but it still protects the pre-initialization path, so no regression from fix(memory): resolve adapter default model in plain status identity check #90816).