[Design/BUG] 动态插件把工具注册进全局作用域:一个会话的工具增删打爆所有会话的提示词缓存 #963
Unanswered
Kitten-BLACK
asked this question in
Q&A
Replies: 2 comments
|
并非单日,是2分钟连续几轮工具调用就直接泄露了8M的未命中,直接把一个500k前文300步的会话命中率整体拉低到80% |
0 replies
|
你补的跨会话时间线很有价值, 我最近做了一个呈现层的缓解方案:dsh-progressive-tools。插件挂在 profile 的 host plane,对已有和后续 preset 生效;模型侧长期看到的是一组固定 discovery 入口,动态 registry 里的普通工具只以
v0.2.3 已经发布: dsh plugin --profile web add dsh-progressive-tools你这个 500k 前文、连续动态工具启停的样本很适合测呈现层方案能挡住多少 cache miss。如果复测时 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
提交日期:2026-08-14 | 环境:
@deepseek-ai/dsh0.1.0-rc.6(web profile,Windows) | 模型路由:deepseek-official / deepseek-v4-flash摘要(Abstract)
Dynamic Cordis plugins mount under a process-root
cordis-dynamicgroup, so every tool they register viactx.tools.register()lands in the global tool layer ofdsh-tools. That layer is merged into every session's visible tool set on every request (toolsarray of the chat-completions call). DeepSeek's automatic context cache is keyed on the exact input prefix, which includes the serializedtoolsdefinitions — so any add/remove of a global tool invalidates the full prompt prefix for every session sharing the API key. In one day of normal use (creating/stopping debug dynamic plugins), the affected session accumulated 8,094,273 cache-miss input tokens; each tool change cost a full ~470k-token re-processing, and the same registration was observed leaking into a second, unrelated session's request within 3 seconds.现象(Symptoms)
request/header事件 reason=change 频繁出现)。根因(Root Cause)
代码路径
dsh-cordis-host-runner/lib/index.js:2531startHostHalf,index.js:919),ctx 是进程根作用域,没有 agent scope。sandboxRegisterTool→ctx.tools.register(tool)(dsh-cordis-host-runner/lib/index.js:541-543),最终插入dsh-tools的全局层(dsh-tools/lib/index.js:2755-2763)。dsh-tools/lib/index.js:2836-2862view(scope)把this.layers.global.tools并入每个 agent 的可见集合。dsh-system-prompt/lib/index.js:240assemble()→ 收集全局+作用域全部工具 schema →orderTools()(无toolOrder配置时按名称字典序稳定排序,index.js:46)。顺序稳定,但集合随注册增删变化。dsh-agent-loop/lib/index.js:670buildRequest把 tools 放进请求;变化时记录会话事件request/header(reason=change)。dsh-llm-deepseek/lib/index.js:107-135把 tools 序列化为 chat-completions 顶层tools参数。dsh-llm-deepseek/lib/index.js:183-197,inputTokens = prompt_tokens - prompt_cache_hit_tokens(即纯未命中),cacheReadTokens = prompt_cache_hit_tokens。失效机制
DeepSeek 自动上下文缓存按输入前缀精确匹配(官方文档 kv_cache),输入 token 流 = [system + tools 定义序列化] + 全部历史消息。tools 参数参与前缀编码(外部佐证:openclaw #107076 — Dynamic tool list before CACHE_BOUNDARY breaks prefix caching for exact-match providers (GLM-5.2, DeepSeek))。因此任意全局工具注册/注销 → 所有会话的下一请求前缀分叉 → 全量缓存 miss。
控制对照:同一会话 11:42:13 有一次非工具类 header 变化(
reasoningEffort从 adapter 默认改为显式),紧随其后的请求 miss 仅 654 token(缓存未破);而每次 tools 集合变化,miss 都等于上下文总量——证明破坏前缀的正是 tools 数组。证据(Evidence)
数据来源:会话持久化日志
$DSH_HOME/sessions/<cwd>/<session>/session.jsonl.zstd(解压后为 JSONL,含request/header与assistant/chunk的 usage 事件)。表 1:d39(框架推进会话)工具变动 vs 下一请求缓存 miss(节选,完整 30 条见附录)
inputTokens(未命中)= 8,094,273。表 2:跨会话泄漏(同一时刻,两个无关会话)
cordis_define创建插件prompt-dump并 run → 工具注册进全局层07d 全程没有创建任何插件,纯粹因为共享全局工具层而损失整段缓存。
影响(Impact)
UNKNOWN_TOOL错误。tool "x" is already registered in this scope。修复建议(Suggested Fix)
cordis-dynamic组从进程根rootCtx改为发起会话的 agent 作用域(dsh-cordis-host-runner的requireGroup()/startHostHalf需要拿到发起 agent 的 ctx,或为每个会话建独立 group)。动态插件工具从此只出现在发起会话的请求里,跨会话污染消除;会话内频繁 run/stop 的代价也仅由该会话自己承担。tools/change事件已存在)在 Web UI 提示"工具集已变化,将导致前缀缓存失效"。toolOrder已可固定顺序(仅影响顺序,不影响集合);可进一步提供"冻结工具集"开关供长会话使用。附录:d39 完整工具变动时间线(30 次)
合计未命中 inputTokens = 8,094,273(含工具变动之外的新消息增量)。
复现步骤(Repro)
cordis_define定义一个inject: ['tools']、在apply里ctx.tools.register({ name: 'probe_x', ... })的插件,cordis_run。request/header记录 tools 变化(reason=change),usage 中cacheReadTokens归零、inputTokens≈ 会话 B 全量上下文。cordis_stop/cordis_undefine:会话 B 再次全量 miss。All reactions