fix(plugin): pin cache-hit MCP placeholder tools for the whole session#6105
Merged
SivanCola merged 2 commits intoJul 6, 2026
Merged
Conversation
The provider request's tools array is part of the cached prompt prefix, and cache_shape.go already tracked mid-session tool changes as a "tools" prefix invalidation (a full-conversation cache miss at 10x pricing). The lazy-placeholder mechanism registered byte-stable cached schemas at boot — and then trySwap replaced them with the live tools as soon as the background handshake finished. Whenever the live handshake differed from the cache (description tweaks, schema upgrades, newly added tools), the swap rewrote the tools array mid-session and cold-started the conversation's provider cache. Pin cache-hit placeholders instead: trySwap no longer touches the registry for them. The lazyTools keep presenting the cached bytes the model has seen since boot and forward Execute to the real tools through the shared spawn state (which already worked — the swap was never functionally necessary). Live drift still lands in the schema cache via saveLazyCachedSchema, so the NEXT session presents the updated surface: freshness deferred one session for byte-stable tool bytes within this one — the same trade the environment-probe snapshot makes for the system prompt. The cache-miss connect stub still swaps (there was nothing real to present); that is a one-time cost per server, and the background kick usually completes before the first user message anyway. A snapshot with zero cached tools now also takes the stub path — previously its live tools joined the registry mid-session with no placeholders at all. Covers all trySwap callers including the IsServerAlreadyConnected shared-host recovery paths. Verified by reverting the guard: the new divergent-handshake test fails with the old swap behavior.
…ble one macOS CI caught a race in the new divergent-handshake test: the stale cache the test writes is itself loadable, so waitForCachedSchema could return it before the background saveLazyCachedSchema landed. Poll until the cache actually contains the live tool set (bounded by the same 5s).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCP 工具集会话内固定——"高缓存率"专项的最后一块。provider 请求的 tools 数组是缓存前缀的一部分,
cache_shape.go早已把会话中途的工具变化记为"tools"前缀失效(整段对话 10 倍 miss 计价冷启动)。现状调查结论:lazy placeholder 机制其实已经完成了 90% 的工作——cache-hit 时占位符携带完整缓存 schema(与真实工具走同一个
provider.CanonicalizeSchema,无 drift 时逐字节相同),boot 时即注册。问题出在最后一步:后台握手一完成,trySwap就把注册表里的占位符替换成 live 工具。只要 live 握手与缓存有任何差异(描述微调、schema 升级、server 新增工具),tools 数组就在会话中途改字节 → 前缀全灭。修复(最小改动):
trySwap不再重写注册表。占位符从 boot 起呈现的缓存字节保持到会话结束,Execute 通过共享 spawn 状态转发到真实工具——这条转发路径本来就存在(lazy.go的 spawnReady 分支),swap 从来不是功能必需的。live 差异照旧写入 schema 缓存(saveLazyCachedSchema),下一个会话呈现新表面——与 fix(environment): persist probe snapshots so the prompt prefix stays byte-stable #6093 环境探测快照同一个取舍:新鲜度延迟一个会话,换会话内字节稳定。trySwap调用方,包括IsServerAlreadyConnected共享 host 恢复路径(修改在函数内部区分,天然覆盖)。行为边界(review 时请关注):live 握手缺少缓存中某工具时,该占位符调用报 "did not expose tool (cached schema may be stale)"——与修复前行为一致(旧 swap 同样不移除消失的 cache-hit 占位符);live 新增的工具本会话不可见,下会话出现。
Verification
TestLazyCacheHitPinsToolBytesAcrossDivergentHandshake:缓存故意与 live 分歧(过期描述/schema + 缺一个工具),后台握手完成并 Execute 后,注册表Schemas()字节与 boot 时完全一致、live-only 工具未中途加入、刷新后的磁盘缓存已含 live 真集。反向验证:恢复旧 swap 行为后该测试失败。TestLazyEmptyCachedToolsFallsBackToConnectStub;更新TestLazyCacheHitSyncSpawn(断言从"swap 成 remoteTool"改为"占位符保留 + schema 字节不变 + 二次调用转发成功")。internal/plugin(含-race)、internal/boot、internal/control、internal/agent、desktop 全套(37s)全绿;go vet干净。Cache impact
Cache-impact: high - this IS a cache-stability fix: removes the mid-session tools-array rewrite class of prefix invalidation. Tool surface may lag live server state by one session on cache-hit drift.
Cache-guard: TestLazyCacheHitPinsToolBytesAcrossDivergentHandshake asserts registry schema bytes are identical before/after a divergent handshake; reverse-verified against the old swap behavior.
System-prompt-review: N/A (tools array, not system prompt; no prompt content changes)
Refs #2945, #5614; completes the byte-stability line (#6093 system prompt, #6094 request prefix, this PR tools array).