前言¶
用 DSH(DeepSeek Harness)跑中文工作流时,通常会遇到一个具体的麻烦:模型内部的推理(reasoning)会在长会话里逐渐变成英文,用户回复也跟着漂移。在 ~/.dsh/AGENTS.md 里写「内部推理一律使用中文」往往压不住,原因不在规则丢失,而是注入方式的问题:AGENTS.md 是以 user 角色的 <system-reminder> 注入的,框架文案明确它只是 guidance,不覆盖 system/developer/user 指令,而且只在会话开始注入一次(baseline: true),长对话里被稀释到很远的位置。
下面介绍的插件就是针对这一点做的最小修复:把同一条简体中文策略以 system 角色、系统提示词最后一段的形式追加进去。插件由 xia-sc 维护,分类为「工作流」。这是一次性的 host-only bundle,只做这一件事。
这是什么¶
@xia-sc/dsh-language-policy 是一个 Host-only DSH bundle,在 apply() 里向 ctx.systemPrompt 注册一个 section,把内置的简体中文语言策略追加到系统提示词的最末尾,让模型的内部推理和用户回复保持中文。它不覆盖任何上游配置,纯追加;全局生效,主 agent、Agent Teams 的 teammate、subagent 都会带上。
选择「运行时插件」而不是「在 patch 里覆盖 system-prompt 的 config」是有意为之:后者要复述 personaPrefix,相当于把上游文案复制一份,上游一改就会被旧副本盖回去;本插件只追加,上游怎么改都不受影响。
工作原理¶
插件注册的 section 字段如下:
| 字段 | 值 | 说明 |
|---|---|---|
name |
policy:response-language |
policy: 前缀避免与第一方 section 撞名 |
order |
getSectionOrder('DEPLOYMENT_PERSONA_SUFFIX') + 1 |
跟随上游段落顺序,落在系统提示词最后;取不到时回退 10201 |
interpolate |
false |
策略是纯文本,不参与 {{变量}} 插值,避免装配报错 |
text |
见 index.js 的 DEFAULT_TEXT |
可用 config.text 覆盖 |
插件零依赖,只用 systemPrompt 服务和 Cordis 的 ctx.effect 生命周期,不 import 任何 DSH 包,因此不受 cordis 4.0 → 4.1 升级影响。engines.dsh 声明为 >=0.2.1-rc.5 <0.3.0-0。
安装与启用¶
插件通过 plugin_manager 的 install_bundle 安装,任选其一(都需要 plugin_manager):
install_bundle -> target: <本仓库克隆目录的绝对路径> # 本地目录
install_bundle -> target: @xia-sc/dsh-language-policy # 发布 npm 后
本地目录安装时,plugin_manager 会把该绝对路径以 link: 形式写进 profile 的 dependencies,所以仓库目录不要移动或删除。安装会写 profile 的 package.json(dependencies + dsh.profile.bundles)并立即生效。新开一个会话即可验证;已存在的会话要等下一轮重新装配系统提示词。
配置¶
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
false 时完全不注册 section,用于 A/B 对照 |
text |
内置默认文本 | 覆盖默认语言策略文本 |
要临时关掉,在 profile 的 bundle 行上加 config(cordis.patch.yml 里的注释有示例):
- id: dsh-language-policy
name: '@xia-sc/dsh-language-policy'
config:
enabled: false
验证¶
先做不启动 DSH 的静态检查,再做提示词落地检查。
第一步,静态检查:
node --check index.js
node test/smoke.mjs
test/smoke.mjs 断言三个导出形态、默认文本关键字、section 名/order/interpolate、enabled 与 text 开关、order 回退、patch 只 insert 不 override、manifest 的 locale/icon 资源。
第二步,提示词落地检查。新会话发一句话,然后解压该会话日志,确认系统提示词里出现了策略文本、且上游 personaPrefix 没被动过:
// 用法: node check.mjs %USERPROFILE%\.dsh\sessions\<workspace>\<session-id>\session.v4.jsonl.zstd
import { readFileSync } from 'node:fs'
import { zstdDecompressSync } from 'node:zlib'
const magic = Buffer.from([0x28, 0xb5, 0x2f, 0xfd])
const buf = readFileSync(process.argv[2])
let text = ''
let off = 0
for (;;) {
const idx = buf.indexOf(magic, off)
if (idx < 0) break
try { text += zstdDecompressSync(buf.subarray(idx)).toString('utf8') } catch {}
off = idx + 4
}
const recs = text.split('\n').filter(Boolean).map((line) => JSON.parse(line))
const sys = recs.find((record) => record.type === 'system/message')
const prompt = sys?.data?.message?.content?.[0]?.text ?? ''
console.log({
hasPolicy: prompt.includes('语言策略'),
hasPersona: prompt.includes('You are a coding agent powered by'),
length: prompt.length,
})
README 记录了 2026-11-04 在 DSH desktop 0.2.1-rc.5 上的实测:安装前系统提示词 7785 字符、不含策略;安装后下一轮重新装配后 7974 字符、含策略;差值 189 = 策略文本 187 字符 + 段落分隔符 2 字符,上游提示词逐字未变。策略位于 23 个 section 之后,其后只有 DSH 自身的 runtime-context 快照(按架构总是排在最后)。
效果与已知边界¶
README 给出了一组 A/B 对照(2026-11-04,本机 desktop 0.2.1-rc.5,2 组 × 3 样本):两组唯一差异是系统提示词里那 189 字符的策略段,题目、模型、AGENTS.md 基线、宿主全部相同。
| 组 | 三次样本中文占比 | 均值 | 极差 |
|---|---|---|---|
| 仅 AGENTS.md | 79.2% / 74.3% / 49.2% | 67.5% | 30.0pp |
| AGENTS.md + 插件 | 77.6% / 77.6% / 76.3% | 77.2% | 1.3pp |
结论是:AGENTS.md 本身有效,但长推理会出现英文漂移(一次掉到 49.2%);插件把中文占比稳定在 76–78% 的窄带内,主要收益是压低漂移的尾部风险,而不是把均值拉满。n=3 属小样本,不构成统计显著性结论。
需要明确的边界:
- 不承诺 100% 中文思考:长推理仍可能漂移,提示词只能提高概率。
- 压缩摘要的英文指令硬编码在上游(
compaction-basic的 summarizer),插件改不了。 locale: zh只影响界面,不影响模型提示词;本插件补的就是这一块。- 已知例外(与版本无关):
minimalpreset 的 persona 使用complete: true,会整体覆盖系统提示词,本插件段在该 preset 下不生效;standard/ptc/cordis/cc都是拼装式,正常生效。
兼容性方面,已在 0.2.1-rc.5 和 0.2.2-rc.1 上实测:在 0.2.2-rc.1 上插件行 enabled: true / fiberPhase: active,新会话系统提示词 8012 字符、含策略段,且 DEPLOYMENT_PERSONA_SUFFIX 仍为 10200,order = 10201 依旧落在提示词末尾,section() / getSectionOrder() 的实现与 0.2.1-rc.5 逐行一致。
适用场景与注意¶
适合:以中文为主要工作语言、在 DSH 里跑长会话和深度排障类任务、希望模型内部推理稳定在中文的用户。插件以当前 dsh 进程的权限运行,安装前应检查源码与许可证(MIT),必要时先用 enabled: false 做对照。
插件仓库文件结构:index.js 是本体(name / inject / apply),cordis.patch.yml 只 insert 一行插件行,locale/{en,zh}.json 供 Plugin Manager / Settings 显示,另有 test/smoke.mjs、CHANGELOG.md、OIDC 发布 workflow 和 tools/ 实验脚本。维护者发布走 GitHub Actions + npm Trusted Publishing(OIDC),不需要长期 npm token。
结尾¶
回到最初的问题:这个插件的价值不在「让中文变多」,而在「让英文不偷溜进来」。它只追加 189 个字符,不复制、不覆盖任何上游文案,DSH 升级时受影响面很小。目录页见 https://www.skillhub.cn/plugins/xia-sc/dsh-language-policy,源码与安装方式见 https://github.com/xia-sc/dsh-language-policy 。