Agent X-Ray
RuntimeNotesAbout
Notes/源码拆解/DeepSeek Harness/第9章

第9章:上下文工程 —— 系统提示词的装配与压缩

9 分钟 · 更新于 2026-09-01

第9章:上下文工程 —— 系统提示词的装配与压缩

上一章讲了会话的事件溯源。这一章看它最大的受益者:上下文工程。两个问题:① 模型每次请求看到的系统提示词,是怎么由几十个插件各贡献一段拼出来的?② 上下文快满了怎么办?

答案分别是:dsh-system-prompt段/变量机制,和 dsh-compaction-basic + dsh-compaction-tool-result-pruner + dsh-spill-policy三件套压缩体系。全部有本机实测数据支撑。


一、先看目标:9927 字符从哪来

第 1 章说过,本机实测的系统提示词是 9927 字符。它不是一个人写的,而是装配出来的。dsh-system-prompt 的机制:

工具包拥有自身的跨调用指导(tool:bashtool:read 等);此插件拥有 harness:identitydeployment:persona。 —— dsh-system-prompt/README.zh.md

每个插件调 ctx.systemPrompt.section({ name, order, text }) 贡献一段,dsh-system-promptorder 排序拼装。

1.1 保留的 order 区间

order内容
-100harness:identity固定文本 You are an AI agent powered by DeepSeek Harness.
0deployment:persona部署人格(本机实测由 web-app patch 配置)
100–199工具引导各工具包的跨调用指导

【来源:dsh-system-prompt/README.zh.md;lib/index.js】

1.2 装配规则

javascript
const sectionDefinitions = [...sectionByName.values()].sort((a, b) => a.order - b.order);

【来源:dsh-system-prompt/lib/index.js:263】

  • order 升序
  • 平局按注册顺序(插件加载产物)—— README 点名这是个"重要陷阱",确定性依赖要用不同 order 值
  • 作用域遮蔽agent.ctx 注册的段只对该 agent 生效,且遮蔽同名全局段
  • complete: true:组装后成为精确的完整提示词;超过一个就拒绝装配

1.3 变量机制

typescript
ctx.systemPrompt.variable(name, provider)

段文本里写 {{name}},变量由 provider 在组装时提供值。agent loop 注册了 modelprovidercwd 三个变量。web-app 的 persona 段就是一个活例子 [实测]:

yaml
system-prompt:
  config:
    persona: >-
      You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.

严格插值:未知引用、已注册但无值的引用、格式错误的 {{…}} 组——都抛异常。README 说:"明确失败胜过交付格式错误的提示词"

1.4 工具顺序

工具 schema 的排列独立于段顺序,由 toolOrder 配置决定:

toolOrderToolSchema.name 列表,必须恰好包含一个 '<unlisted-tools>' 标记,已列工具按列表位置排,未列工具按名称字典序插入该标记位置;缺席则全部按字典序。

字典序用 code-unit 比较,"locale-independent, so the order is identical on every machine"。


二、实测拆解:9927 字符是谁写的

从本机会话日志的 request/header 事件提取完整系统提示词,逐段标注来源 [实测]:

段落来源内容示例
身份dsh-system-promptYou are an AI agent powered by DeepSeek Harness.
checkout 提示dsh-web-app 部署层实现 checkout 位置、GUI URL、HMR 提示
角色persona(web-app patch)You are a coding agent powered by the claude-opus-5 model. Your working directory is …
环境部署层# Environment + OS/Shell 说明
行为准则部署层# Acting with care / # Working faithfully
沟通部署层# Communication(中文默认、file_path:line 格式)
工具使用指南dsh-tool-fs 等各工具包read/write/edit 的完整行为约定
任务追踪dsh-tool-todotodo 的维护规则
目标dsh-tool-goalgoal 工具的使用说明
委派dsh-tool-subagent / workflow / ralph各自的使用判据
工作区策略部署层注入Python workspace policy

怎么验证? 这段系统提示词就在你自己的会话日志里 —— 找到最新的 request/header 事件,读 data.header.system。你能亲眼看到它是"装配"出来的:段与段之间是拼接痕迹,不是一篇手写文档。


三、工作区指令:AGENTS.md 链的加载

dsh-agent-instructions 负责加载 AGENTS.md / CLAUDE.md 链(你正在读的这份仓库规约就是通过它进来的)。

3.1 加载顺序

loader 先读取 $DSH_HOME/AGENTS.md,随后针对项目根目录到 agent.session.header.cwd 的每个目录,先读取每个现有基础候选文件AGENTS.mdCLAUDE.md),再读取每个现有本地 overlay 候选文件AGENTS.local.mdCLAUDE.local.md)。 —— dsh-agent-instructions/README.zh.md

配置要点:

typescript
interface Config {
  dshHome?: string
  projectRootMarkers?: string[]        // 默认 ['.git']
  maxBytes: number                     // 必填,无默认 —— 每个部署必须显式选择提示词预算
  maxSourceBytes?: number              // 默认 1 MiB
  instructionFileCandidates?: string[]      // 默认 ['AGENTS.md', 'CLAUDE.md']
  localInstructionFileCandidates?: string[] // 默认 ['AGENTS.local.md', 'CLAUDE.local.md']
}

maxBytes 必填 —— 这是 dsh 对"指令膨胀"的硬约束:每个部署都必须显式选择预算,超过就按优先级裁剪(先丢较宽泛的,再截断最具体的,并发出可见通知指明被省略的路径)。

同级去重:同一目录里 CLAUDE.md 若只是 AGENTS.md 的字节级复制(去首尾空白后 SHA-1 相同),只渲染一次。

3.2 注入时机与嵌套发现

基线的注入发生在每个会话第一次符合条件的 agent/pre-step(第 5 章那个 waterfall):

当下游决策让非空的第一步批次进入时,插件会将基线折入最终批次、紧随已领取的直接提示词之后。

嵌套文件发现跟随结构化文件系统活动:

观察第一方 readwriteedit 调用成功后产生的不可变 tools/result。每个已接受的 touch 都会检查新达到的后代 scope 以及之前加载的每个 scope。

三种转换:新文件 → 新增;已改变 → 替换;消失或成为重复项 → 移除通知。

为什么不跟 shell cd?因为每次本地 bash 调用都启动新 shell,解析任意 shell 语法也不可靠。

新 scope 的注入模板(实测):

markdown
<system-reminder>
Additional instructions from: packages/app/AGENTS.md

These instructions apply to work under `packages/app`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.

<nested-instructions>
</system-reminder>

安全细节:内容里出现字面 </system-reminder> 会转义,"仓库控制的文本无法关闭插件控制的框架"。


四、压力监测:token-meter 的"重放感知"

上下文快满了怎么办?先要知道"多满"。dsh-token-meter 负责计量:

它从持久日志为每个会话推进一个隔离 fold,因此压缩与其他压力敏感插件可以共享计量,无需依赖 CompactionEngine。 —— dsh-token-meter/README.zh.md

4.1 "重放感知"是什么意思

meter 不是每次重新估算全部上下文,而是沿日志增量折叠

只有当最新成功调用的规范请求 envelope 与已测量 envelope 匹配,且其总量不低于该调用的完整启发式锚点时,才会复用提供方用量;后续成功会替换较早锚点。否则会对当前 envelope 与表层进行完整估算。表层变更保持相对于匹配锚点的带符号值,包括缩减替换后的负 delta。 —— dsh-token-meter/README.zh.md

翻译:提供方报告过真实用量(usage)的位置成为锚点,之后只对锚点以来的增减做启发式计价。压缩造成的 replace 产生负 delta —— 压力读数在压缩落地的瞬间就下降。

4.2 估算启发式

每个 token 按四个字符估算,再加上角色、块与请求 envelope 字段的结构开销。任何配置键都会被拒绝。

这是固定启发式,无配置。模型容量来自适配器的 resolveModelInfo().context

4.3 三层解耦

部署会在 LLM 适配器上配置容量,并在 dsh-compaction-basic 上配置压缩策略。

容量归适配器、计量归 token-meter、策略归 compaction-basic —— 三者独立可替换(第 4 章 seam 思想的又一次体现)。


五、压缩三件套

上下文压力三件套|900

配图说明:token-meter 重放感知计量(提供方 usage 锚点 + 4 字符/token 启发式);三个递进手段 —— ① spill-policy(溢出到文件,模型见首尾预览 + 路径,read 工具跳过)、② tool-result-pruner(裁剪单个结果:head 4096 + marker + tail 1024,surface 上 1→1 遮蔽,幂等)、③ compaction-basic(压力达 80% 触发整段摘要:回放前缀复用 KV cache,单个 user/message 的 replace 遮蔽,八个固定小节)。共同铁律:只追加不删除。

压力高了怎么办?dsh 有三个递进的手段。

5.1 第一件:tool-result-pruner(裁剪单个结果)

配置(默认值):

默认含义
thresholdChars8192合并文本超过此 Unicode 码点数时剪枝
headChars4096保留的开头
tailChars1024保留的末尾

裁剪后插入固定标记:

javascript
const PRUNE_MARKER = "\n\n[... tool result middle pruned ...]\n\n";

"重放安全"的技术含义

每个超出预算的工具结果都会被一个新追加的 tool/result 替换,其携带 { surfaceOp: { op: 'replace', start: originalSeq, end: originalSeq }, sourceEventSeqs: [originalSeq] }原始事件仍可用于持久化、回放和精确日志检查。 —— dsh-compaction-tool-result-pruner/README.zh.md

日志只追加,surface 上做 1→1 遮蔽。第二次扫描不会发出替换(幂等),且头+标记+尾严格小于触发输入。

已知限制:字符预算不是 token 预算(不同提供方 token 密度各异,真正是否缓解压力由 token-meter 判定);剪枝只基于语法(保留头尾,不解释中间哪行语义重要)。

5.2 第二件:spill-policy(溢出到文件)

第 7 章讲过:超长纯文本结果溢出到 ctx.spillStore,返回预览 + 路径。maxInlineBytes 省略即完全禁用(无默认值)。

与 pruner 的分工:pruner 是"截断保留头尾",spill 是"完整存文件只留预览"。pruner 保语义(头尾原文),spill 保完整(可随时 read 回)。

5.3 第三件:compaction-basic(整段压缩)

默认配置:

默认含义
thresholdRatio0.8上下文压力达到窗口 80% 时触发
retainRatio0.16逐字保留的近期尾部比例
maxTokens8192摘要调用的生成上限
compactionRetries1压力仍高时额外尝试
autotrue是否注册步骤边界压力监听

触发流程(源码 compactIfNeeded,要点):

  1. 先查 meter —— 低于阈值的步骤检查绝不剪枝
  2. 若 pruner 可用,先 pruneSession() 再量 —— 剪枝优先于摘要:如果无模型的剪枝就把压力压回安全线,完全不发生摘要 LLM 调用
  3. 仍超阈值 → 从尾部向前累计 token 找保留起点,回退到工具配对平衡点(绝不劈开 tool-call/result 对),头锚定压缩 [surface[0], keepFromIdx-1]
  4. 调用摘要器

摘要调用是一次直接的 ctx.llm.stream(),不是 agent loop 的 step

该调用会逐字回放会话自身的系统提示词、工具与已遮蔽区域消息(包括图片引用),并将压缩指令作为最后一条 user 消息追加,从而复用提供方的热前缀 cache,而非使它失效。 —— dsh-compaction-basic/README.zh.md

这是第 6 章"KV Cache 视角"的正面应用:摘要器故意回放前缀以命中缓存。

摘要的八个固定小节(要求模型"EXACTLY 保留每个小节,顺序不变,空的写 (none),永不丢小节"):

text
## Primary Request and Intent    用户原始与演进目标
## Key Technical Concepts         技术概念
## Files and Code                 文件与代码
## Errors and Fixes               错误与修复
## Pending Jobs                   未完成的明确请求
## Current Work                   检查点时刻进行中的工作
## Next Step                      单一下一步动作
## Critical Context               决策与理由、约束、偏好、开放问题

框定标记

text
<compacted-summary>...</compacted-summary>

前置一段固定的英文前导("This is an automatically generated checkpoint…"),把压缩结果定位为"已确立的背景,不要复述"。

事务五步compaction/* 事件不上 surface):

  1. 追加 compaction/start(仅日志)→ 获取锁
  2. 摘要该范围
  3. 追加 compaction/summary(仅日志,记录范围/遮蔽 seq/token 数)
  4. 追加单个 user/messagesurfaceOp: {op:'replace', start, end} —— 本操作唯一的表层变更
  5. 追加 compaction/end(仅日志)→ 释放锁

如果在 compaction/startcompaction/end 之间崩溃,会留下可检测的遗留锁,而不是虚假声称压缩已完成但表层从未被遮蔽的 compaction/end

收敛校验:拒绝不能缩小源内容的摘要。


六、三条防线合起来

手段时机模型看到数据安全
spill-policy工具结果生成时预览 + 文件路径完整文本在 spill 存储
tool-result-pruner压力检查/溢出时头 + 标记 + 尾原始事件仍在日志
compaction-basic压力达 80% 时摘要检查点被遮蔽事件仍在日志

共同点:只追加,不删除。 三个手段都遵守事件溯源铁律 —— 原始事件永远在日志里,压缩/裁剪只是 surface 层的变化。这就是第 8 章"日志是真源"的红利。


七、动手复核

powershell
# 1. 自己会话的完整系统提示词
# 解压会话日志,找最新 request/header,读 data.header.system

# 2. 压缩默认参数
Select-String -Path "$env:DSH_HOME\profiles\node_modules\@deepseek-ai\dsh-compaction-basic\lib\index.js" `
  -Pattern 'DEFAULT_THRESHOLD_RATIO|DEFAULT_RETAIN_RATIO'

# 3. 摘要八个小节
Select-String -Path "$env:DSH_HOME\profiles\node_modules\@deepseek-ai\dsh-compaction-basic\README.zh.md" `
  -Pattern 'Primary Request|Key Technical|Pending Jobs|Critical Context'

# 4. pruner 默认参数
Select-String -Path "$env:DSH_HOME\profiles\node_modules\@deepseek-ai\dsh-compaction-tool-result-pruner\README.zh.md" `
  -Pattern 'thresholdChars|headChars|tailChars'

# 5. AGENTS.md 加载配置
Select-String -Path "$env:DSH_HOME\profiles\node_modules\@deepseek-ai\dsh-agent-instructions\README.zh.md" `
  -Pattern 'maxBytes|instructionFileCandidates'

八、总结

上下文工程是 dsh 设计密度最高的领域:

  1. 装配section() 按 order 排序、变量 {{name}} 插值、作用域遮蔽 —— 系统提示词是拼出来的
  2. 实测:9927 字符可逐段标注来源,就在你的会话日志里
  3. 工作区指令:AGENTS.md 链 + maxBytes 硬预算 + 嵌套发现 + 变更报告
  4. 计量:token-meter 重放感知,提供方用量打锚点,压缩产生负 delta
  5. 三件套:spill(溢出)/ pruner(裁剪)/ compaction(摘要),递进但不越权
  6. 铁律:只追加不删除,原始事件永在 —— KV Cache 复用是压缩器设计的一等公民

下一章进入安全边界:沙箱与权限 —— fail-closed 的四层防线


  • README-教程总览
  • 第6章-模型调用-provider中立的LLM-seam —— KV Cache 视角的起源
  • 第8章-会话-事件溯源与surface投影 —— replace 操作与压缩的关系
  • 第10章-沙箱与权限-fail-closed的四层防线

本章目录
一、先看目标:9927 字符从哪来二、实测拆解:9927 字符是谁写的三、工作区指令:AGENTS.md 链的加载四、压力监测:token-meter 的"重放感知"五、压缩三件套六、三条防线合起来七、动手复核八、总结Related Documents
苏ICP备2025204887号-2