跳到正文
dsh-market 浏览插件 GitHub EN

BOWLUNA/dsh-zcode-breaker

自动压缩的 rapid-refill 熔断器。当上下文在连续 M 次压缩中、每次都于不足 N 个工具轮次内又满时,拒绝这次徒劳的压缩——无上限的步压力触发检测不到这种情形——并且不做「每一步都白烧一次摘要调用」的事,而是把情况连同建议一起报告出来,通常是一次读取或一份工具输出过大。用 /compaction-breaker 查看状态与重新武装,并注入一个 prompt 段让模型把它转述给用户。宿主平面会话由 profile bundle 覆盖;普通会话需要在 agent preset 里替换一行。

Star 数 ★ 0 分类 会话与消息 收录于 2026-09-22

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add github:BOWLUNA/dsh-zcode-breaker

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

README

English | 简体中文

test

给 DeepSeek Harness 自动压缩用的 rapid-refill 熔断器:拦住「压完立刻又满、于是每一步都再压一次」这个死循环,并告诉用户是哪个过大的读取或工具输出造成的。思路来自 ZCode 的同名实现。

dsh plugin --profile web add dsh-zcode-breaker

Node: 本插件所挂的宿主在 node 20 上装不出来 —— 在 node 20 上 npm install @deepseek-ai/dsh 只装入 10 个包、没有 dsh 可执行文件,而 node 24 上是 488 个。 package.json 目前仍声明 >=20;徽章写的是实测值。 提高声明下限是一件待拍板的事,不在这里悄悄改掉。

从 ZCode 取了什么,又在哪里走得更远

每一行都应该可核对。中间那列指向 ZCode 源码的文件与行号,而且这些引用不是手打上去就信的:控制工作区的 verify-zcode-citations.mjs 会把每一条拿到一份检出里去解析,并把它找到的那一行原文打出来。最后一列给的是你现在就能跑的命令;没有可主张的,就如实写「暂未超过」,不编。

其中两条路径在 2026-09-22 之前是错的:写的是 core/src/compact/turn-loop-state.ts 与 core/src/compact/runtime/methods/compact.ts,那是从一份设计笔记里抄的,不是从源码里核的。真实位置在 packages/core/src/runtime/methods/ 下。守卫抓不到这个错,因为不带行号的路径根本不算它要看的引用 —— 这也正是中间那列现在必须带行号的原因。

ZCode 有什么 本插件取了什么 本插件多了什么(优于在哪) 证据
rapid-refill 状态机 —— consecutiveRapidRefills、toolTurnsSinceCompact、shouldBlock,计算在 zcode/apps/zcode-cli/packages/core/src/runtime/methods/turn-loop-state.ts:158 同样这三件状态 工具轮次是从 DSH 的持久会话日志里读出来的(一条带至少一次工具调用的 assistant 消息),而不是 ZCode 自己 turn loop 里的计数器。这与压缩接缝判断表面平衡时用的是同一个单位 —— 是量出来的,不是猜出来的步数 node --test test/tracker.test.js —— 「a healthy gap never trips the breaker, however long the run」
拒绝的判定 —— shouldBlock: consecutiveRapidRefills >= MAX_CONSECUTIVE_RAPID_REFILLS(…/turn-loop-state.ts:165),在 if (context.rapidRefill.shouldBlock) 处被采纳(…/runtime/methods/compact.ts:230) 同样的拒绝,且在摘要调用之前 拒绝会锁定,而且发生在摘要之前而不是之后:模型调用一次都不花,且被跳闸的会话会一直保持跳闸,直到被重新武装 node --test test/tracker.test.js —— 「the trip happens before the futile compaction, not after it」「once tripped, every later attempt stays refused」
阈值是模块级常量 —— RAPID_REFILL_TOOL_TURN_THRESHOLD = 3 与 MAX_CONSECUTIVE_RAPID_REFILLS = 3(…/turn-loop-state.ts:21) 同样两个阈值:toolTurnThreshold 与 maxConsecutiveRapidRefills 它们是行配置而不是编译期常量,所以 profile 行与 preset 行可以不一样 —— 而且引擎挂载时会把实际拿到的值打出来 node tools/boot-check.mjs --port 32100 会打出 compaction-breaker armed: rapid below 7 tool turns, trip at 3 in a row —— 那是一个配了 7 的行,默认值不可能产生这个输出
停机提示是 CLI 打出来的一个字符串 —— Autocompact stopped because the context refilled within fewer than … tool turns(…/runtime/helpers/model-errors.ts:52),并带 reason: "compact_rapid_refill_breaker"(:57) 熔断期间注入一个 prompt 段,另有 /compaction-breaker status|reset DSH 的宿主会吞掉自动压缩路径抛出的错误并继续这一轮。同样一个抛出,会让用户只看到压缩被静默关掉而没有任何解释;注入的提示段才是让「停下」可见的东西 node --test test/engine.test.js —— 「the trip registers exactly one prompt section」(名字为 compaction-breaker:tripped)、「the /compaction-breaker command reports state and resets on request」
microcompact.ts —— 整条清空旧工具结果、保留最近 5 条(…/packages/core/src/compact/microcompact.ts:14) 不取 暂未超过。 DSH 自带一个不同的确定性 pruner;本插件刻意不重做 ZCode 那套,也不主张在这件事上强过它 —
熔断挂在 ZCode 自己的 turn loop 上 挂在 DSH 的 agent/pre-step 步压力路径上,替换 ctx.compaction 里的一行 不依赖外部 CLI 的轮次概念,并且以「单槽位服务的一行替换」接入 node tools/boot-check.mjs --port 32100 —— 断言 B 是从 cordis.patch.yml 里读行名;docs/MEASUREMENTS.md 记录了真实会话里到达的 trigger=pressure

怎么自己复核这张表

git clone https://github.com/BOWLUNA/dsh-zcode-breaker && cd dsh-zcode-breaker
npm install --no-audit --no-fund @deepseek-ai/dsh@0.1.6-alpha.2   # 本插件所挂的宿主
node --test test/tracker.test.js     # 状态机与拒绝,含跳闸的先后顺序
node --test test/engine.test.js      # 包覆层:委派、prompt 段、命令
node tools/boot-check.mjs --port 32100   # 真装真启动(四条断言)

node --test 不需要任何测试运行器,也不需要任何依赖。前两条就是上表的证据列;第三条是本仓库里唯一会真正 apply 插件的一道检查。

它解决的具体问题

@deepseek-ai/dsh-compaction-basic 里自动压缩有两条触发路径:

触发 入口 次数上限
步边界压力 agent/pre-step 调 compactIfNeeded(agent, "pressure") 无
溢出恢复 agent/request-error 调 compactIfNeeded(agent, "context-overflow") maxOverflowRetries

压力那条是无上限的:测得的压力一旦超过 thresholdRatio,下一步就再压一次,而每次压缩都是一次完整的摘要模型调用。于是一次过大的文件读取或工具输出就能造出这样一个循环——每一步烧掉一次模型调用,直到会话被放弃,而转录里只会反复出现下面这一行:

compaction (step pressure): shadowed N surface nodes (seqs A-B, ~T tokens)

它做了什么

它继承 BasicCompactionEngine,只包住一个方法:compactIfNeeded。

  • 状态按 session 隔离,多会话互不污染。
  • 「工具轮次」取自持久会话日志:一条带至少一个工具调用的助手消息。这与压缩接缝自己判断 surface 配对时用的口径一致,而不是猜的步数。
  • 距上次压缩不足 toolTurnThreshold 个工具轮次又需要压缩,记一次 rapid refill。
  • 连续达到 maxConsecutiveRapidRefills 次时,在这次压缩真正执行之前就拒绝它,那一发摘要调用永远不会被花掉。
  • 拒绝会锁定状态,并抛出携带可操作建议的 CompactionRapidRefillError。
  • 熔断期间还会注入一个 prompt 段。因为宿主会吞掉 compactIfNeeded 抛出的错误并继续该轮,只抛错的话用户什么都看不到。
  • 命令 /compaction-breaker 用来查看状态并重新武装。

其余全部保留父类行为:触发策略、保留比例、surface 改写、工具配对安全与摘要实现。

两个平面,以及为什么两个都要管

标准 harness 里 compaction-basic 存在两份:一份是 @deepseek-ai/dsh-base 的宿主平面行,另一份在 agent preset 的 compaction 分组里,该分组声明了 isolate: { compaction: true, toolResultPruner: true }。agent 会话的 ctx.compaction 解析到那个隔离域里,而不加入任何 preset 的会话解析到宿主那一行。这就是为什么只打 profile 补丁改变不了 agent 的压缩,也是本节后半段那个 preset 行存在的原因。

这个接缝是单槽位的:同一 isolate 作用域出现第二个 provider 会让 ctx.provide() 抛错,而 ctx.reflect.set() 只接受持有该服务的 fiber 的写入。因此每个平面都靠替换它那一行来覆盖,而不是遮蔽它。

平面 服务谁 本包怎么覆盖它
宿主 不加入任何 agent preset 的会话 自动,通过安装器挂上的 bundle patch
agent 域 每一个普通会话 在你的 agent preset 里替换一行,因为任何 profile 补丁都够不到一个域

支持哪些 harness 版本,以及为什么停在这里

>=0.1.5-rc.2 <0.1.6-0 || >=0.1.6-alpha.1 <0.1.7-0

0.1.7 不支持,区间就是这么写的。 这是实测出来的,不是假设。在一个真实的 0.1.7-alpha.2 实例上 (本项目实验室里的 dsh017):

0.1.6-alpha.2 0.1.7-alpha.2
dsh plugin add exit 0 exit 0
--dump-config exit 0 · 576 行 · stderr 0 B exit 0 · 1237 行 · stderr 0 B
真启动端口应答 t=1500ms 应答 · stderr 0 B t=2000ms 应答 · stderr 0 B
宿主平面是否由本引擎服务 是 是 —— 探针读到 ctx.get("compaction") 是 BreakerCompactionEngine,compactIfNeeded 是函数
preset 平面 <DSH_HOME>/.agent-presets/ 里的 preset 会被发现 不会 —— 种了 breaker-trip 之后 list() 返回 []

0.1.7 把 preset 发现机制换成了声明式注册表(dsh-agent-preset-registry,自述为 "Declarative Agent preset registry and profile-backed editing"),而注册表不扫描目录。 本插件的宿主那一半在 0.1.7 上是好的;但上面那段 README 让你去配的preset 那一半不是。 凭宿主那一半就说「支持 0.1.7」是半句真话,所以区间把它排除了 —— 包括 0.1.7 的正式版, 而一个写成 ... <0.2.0-0 的区间是会接受它的。 tools/verify-version-consistency.mjs 现在会在「声明区间覆盖了实测不支持清单里的版本」时让构建失败 —— 这个缺口就是这么被找出来的。

安装

dsh plugin --profile web add dsh-zcode-breaker

安装会挂上 cordis.patch.yml,它把宿主平面的 compaction-basic 行停掉,把这个引擎放到它原来的位置。之所以是替换而不是包覆,是单槽位逼出来的。

接着,为了 agent 会话,替换你的 preset 里 agent.cordis.yml 中 compaction 分组的那一行:

- id: compaction
  name: cordis:group
  group: true
  isolate:
    compaction: true
    toolResultPruner: true
  config:
    - id: compaction-breaker
      name: 'dsh-zcode-breaker'
      config:
        toolTurnThreshold: 2
        maxConsecutiveRapidRefills: 3
    - id: command-compact
      name: '@deepseek-ai/dsh-command-compact'

请把 preset 复制到你的用户 preset 目录再改,而不是直接改随包分发的那份,否则 harness 升级会覆盖你的改动。

配置

父类的每个键都保留下来,并在子类上重新声明,因此既不会被父类的严格校验器拒掉,也不会丢失:

键 默认 含义
thresholdRatio 0.8 触发压缩的压力比例
retainRatio 0.16 压缩后保留的尾部比例
retainTokens 无 用绝对 token 数代替上面的比例
summarizationProvider 空 摘要用哪条路由
summarizationModel 空 摘要用哪个模型
maxTokens 8192 摘要输出上限
compactionRetries 1 摘要失败后的重试次数
maxOverflowRetries 1 溢出恢复的重试次数
modelPolicies 无 按 provider 与模型覆写上述各项
auto true 自动压缩总开关

本插件新增的键:

键 默认 含义
toolTurnThreshold 2 间隔小于这个工具轮次数就算 rapid;恰好等于不算
maxConsecutiveRapidRefills 3 连续第几次 rapid refill 时拒绝
announceInPrompt true 熔断后是否注入那段建议 prompt

合计 13 个配置项,并且本文件声明兼容 dsh >=0.1.5-rc.2 <0.1.6-0 || >=0.1.6-alpha.1 <0.1.7-0。

用户能看到的面

面 内容
日志 一行 warn,写明连续次数、阈值与上一次间隔
/compaction-breaker 状态报告;/compaction-breaker reset 重新武装
prompt 段 熔断期间注入,指示模型把情况转述给人

状态语义——三个刻意的决定

  1. 拒绝即锁定。被拒绝的尝试不会 commit,所以若不锁定,间隔一超过阈值就会自动放开,变成「拦两次放一次」。锁定才让它成为停机而不是减速。
  2. 只有两条出路:reset 子命令,或一次成功的手动 /compact。人工主动压缩是新信息,不该被自动路径的历史拦住。
  3. 重置不清零工具轮次时钟。那是会话的单调时钟,清零会让之后所有间隔都显得健康。被清掉的只有 rapid 计数、上次压缩标记、锁定与拒绝次数。

熔断不是全局禁用:会话照常可用,只是它这一个 session 的自动压缩停了。

兼容性与已知边界

  • 它与其它压缩引擎互斥。若干后端都占同一个 ctx.compaction 槽位,它们都无法与本引擎同时挂载。这是接缝本身的限制,不是本插件的选择。
  • 宿主平面那一行不受影响,因此不组合 preset 的会话完全不受影响。
  • 父类默认值被继承:父类自己的 schema 不带默认值,默认值由它的 resolver 提供。因此父类将来新增的键需要在这里重新声明,才能继续可配。
  • 状态放在模块级 WeakMap,而不是类的私有字段。这是刻意的:域交给使用方的那个 ctx.compaction 对象,不保证是私有初始化器跑过的那个,而私有字段读取会在压缩路径里抛错——偏偏那里宿主会把错误吞掉。
  • 模型驱动的那一段端到端验收尚未跑过。 直到「引擎在真实域里服务 ctx.compaction」为止的每一步都已验证,详见 docs/MEASUREMENTS.md。

与别的后端组合

策略核心是导出可复用的,别的后端大约二十行就能接入:

import { RapidRefillTracker } from 'dsh-zcode-breaker/tracker';

const tracker = new RapidRefillTracker({ toolTurnThreshold: 2, maxConsecutiveRapidRefills: 3 });
tracker.noteToolTurn();
const gate = tracker.gate();
if (gate.blocked) throw new Error('rapid refill loop');
const result = await myEngine.compact();
if (result !== null) tracker.commitCompaction(gate.projectedConsecutiveRapidRefills);

测试

npm test

19 个测试、2 个测试套件,不需要任何服务、模型或会话:策略核心在 test/tracker.test.js,引擎接线在 test/engine.test.js。接线那套需要 peer 包可解析,解析不到时会跳过而不是失败。

路线图

  • 跑模型驱动的验收:在真实会话里造出回填循环,看熔断真的跳闸。
  • 支持运行时选择父类,从而与用户偏好的任意后端组合。
  • 把策略核心提给上游 @deepseek-ai/dsh-compaction-basic。

许可

MIT

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。