DeepSeek Harness(DSH)最近上线了一项更新:支持动态修改系统提示词且不破坏 KV Cache。在 Agent 框架中,KV Cache 的命中率直接影响响应速度——命中与否的延迟相差可达数十倍。系统提示词通常放在请求最前面,一旦修改,后面的历史对话还能复用缓存吗?
核心思路:追加而非重建
DSH 的做法是尽量保留已经发给模型的前缀,把更新后的内容追加到历史后面,从而实现提示词的动态更新。其中,运行时上下文以 user 消息更新,系统提示词以 system 消息更新——后者需要模型支持特定语义。这与 Claude Code 和 Codex 的动态工具加载思路类似,但 DSH 是我遇到的第一个把这个思路应用到所有上下文(包括系统提示词)中的框架。

切换模型时 Session 的变化
目前还没有手动修改系统提示词的方法,但通过切换模型可以触发系统提示词的变更。切换后,历史中的旧内容仍然存在,最新版本的提示词被追加到了最后面。这种"保留前缀 + 追加新内容"的策略是保留 KV Cache 的关键。

系统提示词的拼接机制
DSH 的提示词分成 section() 和 context() 两种类型:
- section():注册系统提示词(角色设定、行为规则、工具使用指导),以 system 身份发送给 LLM
- context():注册运行时上下文(当前环境、状态),以 user 身份发送给 LLM
每个步骤开始前,agent-loop 调用 systemPrompt.assemble():先合并全局与当前作用域的贡献,同名内容由更近的作用域覆盖;再按顺序排列段落,求值动态文本,并交给组装扩展点处理。渲染阶段替换 {{model}} 等变量,过滤空段,用空行连接文本。
Sections 的有条件追加
插件通过 section() 注册系统提示词段落,每个段落包含 name(名称)、order(顺序)、text(内容,支持动态函数)和 complete(是否作为完整独立提示词)四个参数。
内置顺序中,Harness 身份段是 -1000,persona 前缀是 0,工具指导如 Bash 是 1000、Read 是 1100。每次 assemble() 收集段落、排序、调用 text 函数,然后在 step 中替换变量、去掉空段。
关键判断:第一次写入开头的 system/message;后续如果文本没变,不重复写;如果文本变了,且模型适配器声明支持(目前仅 Flash 模型支持 systemPromptUpdate: 'in-history'),就把新版追加到历史后面。模型不支持或请求序列需要重建时,则归并到开头的 system。
Context 的完整快照追加
context() 注册的上下文以 user role 消息发送,不需要模型专门支持。多个 context 按 order 排序拼接,不与 section 混排。每次组装时:
- 将所有有效 context 排序、替换变量,拼成完整快照
- 与 Session 中最近的上下文快照比较,相同则不追加
- 文本不同,则准备一条 user role 消息,本步被接受后写入 Session
经常变化的环境与状态信息适合放在 context 中,系统级行为规则则放在 section 中。
为什么能保留 KV Cache
原因已经清晰:DSH 在系统提示词变化时,把新的提示词追加到 session 末尾,旧的前缀保持不变。这意味着模型需要经过专门训练,让 LLM 更重视后出现的 system prompt。
这与 Claude Code/Codex 的 Tool Search、Deferred Tools 思路一脉相承:让新增信息尽量出现在已有前缀之后。不过 DSH 把这种思路扩展到了更广泛的指令与上下文更新中,不仅仅限于工具定义的按需加载。
总结
DeepSeek Harness 的这项更新虽然看起来是个小改动,但背后体现的是对 LLM 推理效率的深刻理解。KV Cache 的命中率直接决定了 Agent 的响应速度,而"追加而非重建"的策略在保持上下文完整性的同时最大化了缓存复用。当前仅 Flash 模型支持动态提示词变更,期待后续更多模型适配这一特性,以及 DSH 补上 Deferred Tools 和 Tool Search 机制。