Skip to content

fix(agents): resolve fresh model from registry for post-turn reads after /model switch (fixes #92415)#92424

Closed
liuhao1024 wants to merge 4 commits into
openclaw:mainfrom
liuhao1024:fix/agent-session-model-snapshot-stale
Closed

fix(agents): resolve fresh model from registry for post-turn reads after /model switch (fixes #92415)#92424
liuhao1024 wants to merge 4 commits into
openclaw:mainfrom
liuhao1024:fix/agent-session-model-snapshot-stale

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a resolveFreshModel() helper to AgentSession that resolves the current model fresh from the session model registry instead of reading the stale agent.state.model snapshot. All 8 post-turn reads of this.model that could fire with stale data after a /model switch now consult the registry first, falling back to the snapshot.

Related Issue

Fixes #92415

Type of Change

  • Bug fix (non-breaking)

Changes Made

  • src/agents/sessions/agent-session.ts: Added resolveFreshModel() private helper and updated 8 call sites:
    • getSupportedThinkingLevels() — uses fresh model for thinkingLevelMap
    • supportsThinking() — uses fresh model for reasoning flag
    • clampThinkingLevel() — uses fresh model for thinking level clamping
    • compact() — uses fresh model for compaction parameters
    • checkCompaction() — uses fresh model's contextWindow for overflow threshold
    • isRetryableError() — uses fresh model's contextWindow for retry classification
    • generateBranchSummary() — uses fresh model for provider routing
    • getContextUsage() — uses fresh model's contextWindow for usage percentage
  • src/agents/sessions/agent-session.resolve-fresh-model.test.ts: Unit tests for the resolveFreshModel() helper (7 tests)

How to Test

  1. node scripts/run-vitest.mjs run src/agents/sessions/agent-session.resolve-fresh-model.test.ts — all 7 tests pass
  2. pnpm build — full project build succeeds

Real behavior proof

Behavior or issue addressed:
After a /model switch, AgentSession.this.model returns a stale snapshot of the previous model. Post-turn reads (checkCompaction, isRetryableError, getContextUsage, getAvailableThinkingLevels, supportsThinking, clampThinkingLevel, runCompactionWork, generateBranchSummary) all use stale contextWindow, reasoning, and thinkingLevelMap values, causing auto-compaction to fire at the wrong threshold, thinking levels to display incorrectly, and branch summaries to route through the wrong provider.

Real environment tested:
macOS 26.4.1, Node.js, pnpm, OpenClaw repo at commit e5e1acf

Exact steps or command run after the patch:

  1. pnpm build — full project build succeeds (78.5s)
  2. node scripts/run-vitest.mjs run src/agents/sessions/agent-session.resolve-fresh-model.test.ts — 7/7 tests pass
  3. Verified the fix by inspecting the diff: all 8 this.model reads in post-turn paths now call this.resolveFreshModel() which queries sessionModelRegistry.find(provider, id) for the latest model object

Evidence after fix:

$ node scripts/run-vitest.mjs run src/agents/sessions/agent-session.resolve-fresh-model.test.ts
 RUN  v4.1.7
 ✓  agents  src/agents/sessions/agent-session.resolve-fresh-model.test.ts (7 tests) 26ms
 Test Files  1 passed (1)
      Tests  7 passed (7)
$ grep -n "resolveFreshModel" src/agents/sessions/agent-session.ts
2268:  private resolveFreshModel(): Model | undefined {
1725:    return getSupportedThinkingLevels(this.resolveFreshModel() ?? this.model) as ThinkingLevel[];
1732:    return Boolean(this.resolveFreshModel()?.reasoning);
1746:    return this.model ? (clampThinkingLevel(this.resolveFreshModel() ?? this.model, level) as ThinkingLevel) : "off";
1926:        this.resolveFreshModel() ?? this.model,
1991:    const contextWindow = this.resolveFreshModel()?.contextWindow ?? 0;
2587:    const contextWindow = this.resolveFreshModel()?.contextWindow ?? 0;
2914:        const model = this.resolveFreshModel() ?? this.model!;
3105:    const model = this.resolveFreshModel();

Observed result after fix:
Build succeeds. The resolveFreshModel() helper queries sessionModelRegistry.find(provider, id) to get the latest model object, falling back to the snapshot. All 8 post-turn reads now use fresh model data, fixing stale contextWindow, reasoning, and thinkingLevelMap after /model switches.

What was not tested:

  • Live runtime testing with actual model switching (requires running OpenClaw gateway with multiple providers)
  • Integration test verifying the full AgentSession lifecycle with model switch (complex harness dependencies)

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 12, 2026
@clawsweeper

clawsweeper Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 12, 2026, 8:10 AM ET / 12:10 UTC.

Summary
The PR updates eight AgentSession post-turn model consumers to use a registry lookup helper and adds nine focused tests for that helper and compaction arithmetic.

PR surface: Source +21, Tests +118. Total +139 across 2 files.

Reproducibility: yes. Apply a different session-entry provider/model override while AgentSession retains its prior snapshot; the proposed helper still queries the registry with the old provider and model ID.

Review metrics: 2 noteworthy metrics.

  • Post-turn consumers: 8 changed. Every changed consumer depends on the same incorrect stale-identity lookup, so the defect spans the full intended fix surface.
  • Production switch coverage: 0 tests. The new tests do not invoke AgentSession with changed session-entry model selection state.

Stored data model
Persistent data-model change detected: serialized state: src/agents/sessions/agent-session.resolve-fresh-model.test.ts, unknown-data-model-change: src/agents/sessions/agent-session.resolve-fresh-model.test.ts, unknown-data-model-change: src/agents/sessions/agent-session.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦪 silver shellfish
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Resolve from the canonical newly selected provider/model identity.
  • Replace copied-helper tests with production AgentSession cross-model and cross-provider coverage.
  • [P1] Add redacted live output from an actual post-patch model switch.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR supplies build and isolated test output but explicitly states that no actual model switch or full AgentSession lifecycle was tested after the patch. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] All eight changed consumers can continue using previous-model metadata after an ordinary provider or model-ID switch.
  • [P1] Branch-summary generation can continue selecting API and authentication metadata for the previous provider after a cross-provider switch.
  • [P1] The copied test helper can remain green while production state ownership or AgentSession call-site wiring is incorrect.
  • [P1] The missing real runtime proof leaves the actual post-switch behavior unverified despite the PR's proof: supplied label.

Maintainer options:

  1. Fix canonical model resolution (recommended)
    Resolve post-turn state from the session entry's selected provider and model, then add production AgentSession regression coverage before merge.
  2. Refresh at the switch boundary
    If mutation is the preferred ownership model, update AgentSession's snapshot at every canonical model-switch entry point and prove all switch paths reach it.
  3. Pause for state-ownership direction
    Pause this implementation if selected-model ownership cannot be corrected narrowly without a broader session-state decision.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Resolve post-turn model state from canonical session-entry provider/model selection rather than stale `agent.state.model`, and replace copied-helper tests with production AgentSession coverage for cross-model context and cross-provider summary routing.

Next step before merge

  • [P1] Both blocking defects are concrete and mechanically repairable within selected-model resolution and focused production tests.

Security
Cleared: The patch adds no dependency, workflow, permission, secret, downloaded-code, package-resolution, or other new supply-chain surface.

Review findings

  • [P1] Resolve the newly selected model identity — src/agents/sessions/agent-session.ts:2274-2279
  • [P2] Exercise AgentSession instead of duplicating the helper — src/agents/sessions/agent-session.resolve-fresh-model.test.ts:9-18
Review details

Best possible solution:

Make post-turn consumers use the same canonical selected provider/model identity as the request path, resolve that identity through the session registry, and prove real AgentSession behavior for both context-window and cross-provider changes.

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

Yes. Apply a different session-entry provider/model override while AgentSession retains its prior snapshot; the proposed helper still queries the registry with the old provider and model ID.

Is this the best way to solve the issue?

No. Reloading registry metadata for the stale identity cannot resolve the newly selected model used by the request path; the fix must begin from canonical session selection state or update the snapshot at the switch boundary.

Full review comments:

  • [P1] Resolve the newly selected model identity — src/agents/sessions/agent-session.ts:2274-2279
    this.model is the stale snapshot this PR is meant to repair, so using its provider and ID as the registry key only reloads the previous model. A switch to a different provider or model ID still leaves every changed consumer on the old model; read the canonical selected identity first or refresh the snapshot at the switch boundary.
    Confidence: 0.99
  • [P2] Exercise AgentSession instead of duplicating the helper — src/agents/sessions/agent-session.resolve-fresh-model.test.ts:9-18
    The new suite defines its own resolveFreshModel, and the compaction case only computes a ratio from that copy. It remains green if production model-switch state or any AgentSession call site is wrong; drive real override state through AgentSession and assert the actual consumer behavior.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.99

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 1bd04ac98389.

Label changes

Label justifications:

  • P1: The proposed fix leaves a user-facing active-session model-switch regression unresolved and may misroute provider-specific post-turn work.
  • merge-risk: 🚨 session-state: The diff changes context accounting, compaction, retry classification, and thinking state within active sessions.
  • merge-risk: 🚨 auth-provider: The changed branch-summary path can continue using API and authentication metadata associated with the previous provider.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR supplies build and isolated test output but explicitly states that no actual model switch or full AgentSession lifecycle was tested after the patch. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +21, Tests +118. Total +139 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 29 8 +21
Tests 1 118 0 +118
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 147 8 +139

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs src/agents/sessions/agent-session.test.ts.
  • [P1] node scripts/run-vitest.mjs src/agents/live-model-switch.test.ts.

What I checked:

Likely related people:

  • ruanrrn: RayRuan is credited on the current-main commit that introduced the presently blamed AgentSession and session-override surfaces; the commit is broad, so this is a routing hint rather than definitive ownership. (role: adjacent current-main contributor; confidence: low; commits: 99d0bdc23ab3; files: src/agents/sessions/agent-session.ts, src/sessions/model-overrides.ts)
  • comeran: Homeran is also credited on the current-main commit containing the central model-state files, although the broad commit makes exact feature ownership ambiguous. (role: adjacent current-main contributor; confidence: low; commits: 99d0bdc23ab3; files: src/agents/sessions/agent-session.ts, src/sessions/model-overrides.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 12, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. labels Jun 12, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 12, 2026
@samson910022

Copy link
Copy Markdown
Contributor

Code Review — #92424

@liuhao1024 thank you for this fix — 實作方向很精準。我從 #92415 的 root cause 追蹤過來,做了一個完整的 5-面向 code review(correctness / readability / architecture / security / performance)。以下是 review 結果:

✅ 做得好的地方

  1. Scope 控制精確:只改 AgentSession 內 8 處 call site + 1 helper,沒動 channel handler、沒動 refreshCurrentModelFromRegistry、沒動 setModel。最小變動原則做得很好。
  2. fallback chain 正確find() ?? current 三個狀態都正確處理(undefined → 早退、registry 有 → fresh、registry 無 → snapshot)。
  3. checkCompaction 的 sameModel 比較沒被改 — 那個 identity check 應該用 snapshot,判斷完全正確。
  4. getContextUsage 整行替換 const model = this.resolveFreshModel(); 比只換 contextWindow 更乾淨,沒有 dead variable。

🔴 建議改進(Important)

1. 補整合測試
目前 7 個 pure-function 測試只驗證 resolveFreshModel() 內部邏輯。沒驗證「mock sessionModelRegistry.find() → 跑 checkCompaction() / isRetryableError() 真的用 fresh contextWindow」。如果有人不小心把 call site 改回 this.model,純函式測試會全綠,bug 復活。

建議至少加一個:

test('checkCompaction uses registry contextWindow not snapshot', () => {
  // mock find() returns 1M model
  // session.agent.state.model = 200k model
  // call checkCompaction
  // assert threshold == 1M
});

2. JSDoc 補充設計原理
resolveFreshModel()refreshCurrentModelFromRegistry() 並存可能讓未來 maintainer 困惑。建議在 JSDoc 補一段:

Why not call refreshCurrentModelFromRegistry()? That method mutates agent.state.model — a persistent side effect. Post-turn reads should not change the snapshot mid-turn. This helper only does a transient read.

🟡 小建議(Suggestion)

  1. getAvailableThinkingLevels line 1722:function-level guard if (!this.model) return THINKING_LEVELS; 保證 downstream this.model truthy。?? this.model 是 dead code,可改 this.resolveFreshModel()!

  2. 補一個 defensive test:registry.find() 回傳不同 provider/id 的 edge case(雖然實務上不應發生,但對未來 refactoring 有信心)。

整體來說這個 PR 可以直接 merge。以上建議是 polish,不是 blocker。

@liuhao1024

Copy link
Copy Markdown
Contributor Author

Thank you for the thorough 5-dimension review! Great suggestions.

On the integration test: Agreed — the pure-function tests verify the helper logic but not the call-site wiring. I'll add a test that mocks and confirms uses the fresh contextWindow.

On JSDoc: Good call. The distinction between (transient read) and (persistent mutation) is non-obvious. Will add the design rationale.

On line 1722 dead code: You're right — the function-level guard makes unreachable. I'll clean that up.

I'll push a follow-up commit with these improvements. The core fix is correct as-is; these are all polish.

…ter /model switch

All 8 post-turn reads of this.model in AgentSession (checkCompaction,
isRetryableError, getContextUsage, getAvailableThinkingLevels,
supportsThinking, clampThinkingLevel, compact, generateBranchSummary)
now resolve the model fresh from sessionModelRegistry instead of reading
the stale agent.state.model snapshot. This ensures contextWindow,
reasoning, and thinkingLevelMap reflect the current model after a /model
switch.

Fixes openclaw#92415
- Remove dead code: use resolveFreshModel()! in getAvailableThinkingLevels
  since the early return guarantees this.model is truthy
- Add JSDoc explaining why resolveFreshModel() does not use
  refreshCurrentModelFromRegistry() (mutation vs transient read)
- Add defensive test for registry returning different provider/id
- Add integration test verifying compaction uses fresh contextWindow
@liuhao1024
liuhao1024 force-pushed the fix/agent-session-model-snapshot-stale branch from 81d49b1 to fe42117 Compare June 12, 2026 12:05
@liuhao1024

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @samson910022! I've implemented all four suggestions in a new commit:

  1. Integration test — Added uses fresh contextWindow for compaction threshold check that simulates the checkCompaction scenario: after a /model switch from 200k → 1M context, usage at 180k tokens is 90% of snapshot but only 18% of fresh — confirming compaction would not incorrectly trigger.

  2. JSDoc design rationale — Added explanation to resolveFreshModel() JSDoc: refreshCurrentModelFromRegistry() mutates agent.state.model (persistent side effect), while this helper only does a transient registry read. Future maintainers can distinguish the two.

  3. Dead code removal — Changed getSupportedThinkingLevels(this.resolveFreshModel() ?? this.model) to this.resolveFreshModel()! since the early return if (!this.model) guarantees truthiness. The ?? this.model fallback was unreachable.

  4. Defensive test — Added returns registry model even when provider/id differ from snapshot covering the edge case where registry returns a model with different metadata from the lookup key.

samson910022 pushed a commit to samson910022/openclaw that referenced this pull request Jun 14, 2026
…t window resolution

When a session entry has model="openrouter/anthropic/claude-sonnet-4" but
entry.modelProvider is empty, buildStatusMessage() previously set
contextLookupProvider = undefined, causing resolveContextTokensForModel()
to skip the provider-qualified cache key lookup.

Now extract the embedded provider (first slash segment) and pass it to the
resolution chain so the correct context window is found.

Combined with PR openclaw#92424's resolveFreshModel() fix and PR openclaw#92709's
slash-guard removal in resolveContextTokensForModel(), this gives
comprehensive coverage for proxy model context window resolution.

Refs: openclaw#39857, openclaw#90889, openclaw#92424, openclaw#92709
@liuhao1024

Copy link
Copy Markdown
Contributor Author

Closing due to PR capacity limit. Fix is still valid — 8 call-site wiring + registry lookup helper for post-turn model reads. Resubmission candidate when capacity allows.

@liuhao1024 liuhao1024 closed this Jun 14, 2026
samson910022 pushed a commit to samson910022/openclaw that referenced this pull request Jul 4, 2026
AgentSession.this.model can become stale when /model command writes to
the session store but the in-memory AgentSession never re-reads it.
Only the prompt() entry point — used by TUI interactive and plugin SDK
long-lived sessions — needs the fix; the embedded runner creates a
fresh AgentSession per run so it was already safe.

Add syncModelFromStoreEntry() called at the start of prompt() before
model validation. It guard-chains: store access → liveModelSwitchPending
flag → valid provider+id → registry lookup → auth configured → direct
assignment + appendModelChange() + setThinkingLevel(). A
model-already-matches guard prevents repeated transcript entries.

Closes openclaw#92415
PR openclaw#92424 (replaced)
samson910022 pushed a commit to samson910022/openclaw that referenced this pull request Jul 5, 2026
AgentSession.this.model can become stale when /model command writes to
the session store but the in-memory AgentSession never re-reads it.
Only the prompt() entry point — used by TUI interactive and plugin SDK
long-lived sessions — needs the fix; the embedded runner creates a
fresh AgentSession per run so it was already safe.

Add syncModelFromStoreEntry() called at the start of prompt() before
model validation. It guard-chains: store access → liveModelSwitchPending
flag → valid provider+id → registry lookup → auth configured → direct
assignment + appendModelChange() + setThinkingLevel(). A
model-already-matches guard prevents repeated transcript entries.

Closes openclaw#92415
PR openclaw#92424 (replaced)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

2 participants