dsh-agent-harness-audit

by Leeaoyin

0 工作流与自动化github 检测到 manifest package.json#dsh收录于 08-17

用 DeepSeek Harness 审计你的 agent 宿主稳定性

Audits your agent harness stability using deepseek harness.

安装

dsh plugin --profile web add github:Leeaoyin/dsh-agent-harness-audit

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

安装与环境配置指引、插件开发教程见 DSH 中文社区文档 ↗

安装即在你的机器上以你的权限运行第三方代码——它可读写文件、使用凭据、访问网络,DSH 的工具审批不会为插件代码加沙箱。「检测到 manifest」仅代表发现 dsh.bundle / dsh.plugin 清单,不构成兼容性或安全审查;安装前请审阅源码,不熟悉的插件先在不含密钥的环境试用。

README

目录

English | 中文

审计 agent harness 的健壮性——并且拒绝任何拿不出代码证据的发现。

它解决什么问题

Agent 跑不稳,大多数时候不是模型不够聪明,而是循环本身有洞。这些洞有个共同特征:平时不报错,出事时无法复现。

  • 工具执行完了,结果没写回对话。下一次请求带着一个没有答案的调用,被模型服务拒收(通常 400)。这是 agent harness 里出现频率最高的真实故障。
  • 五个并行操作成功了四个,但整批被判定失败,成功的结果被丢弃。
  • 外部动作已经生效之后才超时,重试于是又做一遍——消息发两次,款扣两次。
  • 用户点了停止,子进程还在跑,请求还在发。
  • 请求开头的内容每次都在变,服务端前缀缓存永远命中不了。不报错,只是每次请求都更贵,没人会发现。
  • 线上出了问题,事后没法重建这次运行到底发生了什么。

这些问题共十五类,覆盖六个方面:

State stays self-consistent

检查项 查什么
C1 ★ Tool-call pairing completeness 查工具执行的每一种结束方式(正常返回、报错、超时、取消、提前返回、权限拒绝)是否都写回了结果,以及并行批次里一个失败会不会丢掉其余成功的结果。
C2 History is append-only 查有没有代码在消息写入之后又去修改它,而不是只追加新消息。
C3 Crash and checkpoint semantics 查分成两段的写入过程中如果进程挂掉,重启时读到的是什么,以及这种半截状态能不能被识别出来。

Untrusted input is treated as untrusted

检查项 查什么
C4 ★ Model output parsing 查模型输出是怎么解析的:参数不合法、输出被截断、调用编号重复这些情况是被处理了,还是被当成可信输入。
C5 Path and sandbox boundaries 查模型给出的路径有没有解析后再与工作区根目录比对,而且必须在符号链接解析之后比对。
C6 Secrets and ambient environment 查模型生成的命令继承了什么环境变量,里面有没有密钥。

Failure is a first-class outcome

检查项 查什么
C7 Error taxonomy and retryability 查失败是否带有一组封闭的错误码,并被明确分类为可重试/不可重试/致命,还是只给出一段没有分类的文字。
C8 Partial success 查多个独立结果会不会被合并成一个状态,导致一个失败掩盖或丢弃旁边那些成功。
C9 ★ Idempotency and side-effect safety 查重试包裹的范围里有什么:如果被重试的区间包含写入、执行命令或对外发消息,重试就会重复执行它。

Boundaries can be closed

检查项 查什么
C10 ★ Cancellation propagation 查取消信号有没有真正传到对外请求和派生的子进程,还是只停在循环这一层。
C11 Timeout layering 查一次工具调用上有几层超时以及它们的大小关系 —— 工具预算必须早于底层资源超时。
C12 ★ Loop and budget limits 查轮数、工具调用次数、运行时长、token、委派深度上有没有硬性上限。

Finite resources are accounted for

检查项 查什么
C13 ★ Context management and truncation boundaries 查会话变长时有没有上下文管理,以及截断切在哪里 —— 切开配对的调用与结果会破坏历史。
C14 ★ Prompt prefix determinism 查最新消息之前的所有内容(系统提示、工具定义、历史消息)在两次运行之间是否逐字节一致,这是缓存命中的前提。

The run is observable

检查项 查什么
C15 Trace completeness and replay 查这次运行有没有留下覆盖轮次、工具调用与结果、重试、截断、审批的事件记录,且完整到足以重建。

★ 是七个关键项 —— /harness-audit p1 跑的就是这些。

它怎么工作

三个阶段:侦察定位地标、每条检查一个子智能体、汇总由代码完成

三个阶段,前两个用模型,最后一个不用:

侦察 —— 一个子智能体先定位地标:agent 循环在哪、请求在哪组装、工具在哪派发、历史在哪追加。找不到某类地标是正常结果,不编造。

扇出 —— 每条检查交给一个独立的一次性子智能体,只给它这一条的判据和它依赖的地标。所需地标缺失的检查根本不会派发,直接记为"未覆盖"——让模型在没有目标的情况下自由发挥,是误报的主要来源。

汇总 —— 纯代码,不过模型。去重、按保守原则合并判定、排序、渲染。让模型写总结,正是 suspected 悄悄变成 confirmed、以及没有证据支撑的论断混进报告的途径。

运行中的子智能体面板:每条检查一个条目,各自带 token 消耗和耗时

每条检查一个条目、各自计费。这种隔离正是报告里那份"每维度 token 消耗"能够是实测值而不是估算的原因。

为什么可以信

一条上报要过六道校验,任何一道不过就带着原因退回子智能体

判据本身可以要求模型给出文件行号,但只有工具能拒绝。每条上报必须通过六道校验:

  1. 检查项属于本次运行
  2. 路径是第一方代码——不是 .venv、site-packages、node_modules
  3. 文件在工作区内真实存在(这同时挡住路径穿越)
  4. 行号在文件范围内
  5. 引用的代码确实出现在所声明行号的 ±3 行内
  6. 判定为"可疑"时必须说明人工需要核实什么

第 5 条是关键。它按归一化文本比对(去缩进、压缩空白),不做逐字节比较,所以缩进差异不会造成假拒;但一段"看起来像代码却不在文件里"的内容会被退回,并告诉子智能体重查。实测中它反复生效:被拒后子智能体会重新打开文件、修正引用再提交。

范围约束同理。第一次跑一个 Python 项目时,23 个地标全部落在 .venv/site-packages/ 里——审计的是依赖的框架而不是项目本身。仅靠提示词说明不够,加上工具层拒收之后,同一个项目的运行从 698 秒降到 147 秒,token 从 19 万降到 4.6 万。

实测

预埋 8 个缺陷全部检出,0 误报,单维度 205 秒 / 6.1 万输入 token

拿一份预先埋好缺陷、答案已知的代码验证:七个真实的工具调用配对缺陷、一个受环境变量门控的可疑情况、外加一个刻意写正确的对照模块。

缺陷检出 8 / 8
误报 0 —— 对照模块一次都没被点名
证据拒收 10 次提交拒 2 次,均在重新提交后修正
成本 单维度约 205 秒,输入 6.1 万 / 输出 2.4 万 token

唯一与答案卷不一致的一条,是审计器比答案卷更准:它读到那个环境变量的默认值,判断该路径默认可达,因此报为"确认"而非"可疑"——按判据它是对的。

累计已在 11 / 15 个维度上产出 70 条发现、21 次拒收,消耗约 167 万输入 token。

安装

先装 DeepSeek Harness

这是一个插件,需要有 dsh 才能挂载。装好 Node.js 之后:

npx @deepseek-ai/dsh web

Web UI 默认在 http://127.0.0.1:3080。或者从源码检出运行:

git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

两点需要提前知道:

  • DeepSeek Harness 处于 developer preview,明确会有破坏性变更。 本插件因此把 peer 依赖锁在 ^0.1.0-rc.5。
  • dsh plugin 是转发给 pnpm 的,所以 pnpm 必须在 PATH 上。

再装这个插件

dsh plugin --profile web add dsh-harness-audit

npm i dsh-harness-audit 只会下载包,不会激活插件——dsh plugin add 才会把它装进 profile,并让 package.json 里的 dsh.bundle 声明把配置层挂上。验证:

dsh --profile web --dump-config

应该能看到一段 # == dsh-harness-audit。卸载用包名:

dsh plugin --profile web remove dsh-harness-audit

用法

/harness-audit          # 弹出维度选择器,每项都有人话说明
/harness-audit C1       # 单个维度
/harness-audit C1,C9    # 多个
/harness-audit p1       # 七个关键项
/harness-audit all      # 全部十五项

维度选择器:十五个选项,每个都有一行说明它查什么

命令立即返回,审计作为后台任务运行,不阻塞会话。跑完时 agent 会被唤醒并汇报。中途想看进度,让它调 job_output <id>。

命令立刻返回并说明启动了哪些维度,agent 一句话确认

无法解析的输入会被拒绝,不会被静默改判——早期版本只认 --checks,结果 --check c2 落到了配置默认值上,审计了另一个维度却一声不吭。

报告

每次运行产出两份文件,按本地时间和覆盖维度命名:

.harness-audit/report-2026-08-17_095736-C1.md
.harness-audit/report-2026-08-17_095736-C1.json

结构固定,两次运行可以直接对比。其中两节不可省略:

  • 未覆盖 —— 因地标缺失而没跑的检查会明确列出。没有这一节,读者会把"没查"当成"查过没问题"。
  • 成本 —— 每条检查的 token 消耗,以及证据拒收次数。拒收率高说明子智能体在编造,该改的是提示词,不是放宽校验。

配置

字段 默认 含义
checks [] 要跑的检查项 id。空表示"询问",绝不表示"全审"。
priorityFloor 2 只跑优先级不高于此值的检查(1 = 仅关键项)。显式点名的检查不受此限制。
concurrency 3 同时进行的检查数。1 是受支持的最省也最好调试的路径。
subagentProvider spawn 子智能体提供方名称。
maxTokensPerCheck 120000 仅供参考,见下方限制。
outputDir .harness-audit 报告目录,相对工作区。
announceOnStart true 启动时在对话里发一条通知,让全新会话有轮次可以承载任务 UI。
background true 作为后台任务运行。false 则阻塞命令直到跑完。
useLsp true 尝试 LSP 导航,不可用时降级为文本检索。
crossCheckAnalysis false 预留,尚未产出。
language auto 输出语言。auto 依次尝试 harness 语言设置、宿主语言、英文。
excludePaths 依赖目录 被判为超出范围的目录名。设为 [] 可以刻意审计依赖里的框架。

已知限制

  • maxTokensPerCheck 只是建议值。 子智能体 seam 没有整轮 token 上限(AgentOptions.maxTokens 限制的是单次响应),所以这个预算只是告知子智能体并记入元信息,报告里给出的是实测消耗。它不被强制。
  • crossCheckAnalysis 尚未实现。 真做的时候,它的输出必须单独成节并标明是未经证据校验的模型推断。
  • LSP 可用性是按扩展名的。 seam 没有能力查询接口,只能靠 query() 抛出的 LspError 观测,所以探测发生在侦察之后、用检测到的主语言进行,结果只对该语言有效。
  • 成本归账需要本地子智能体提供方。 消耗是从子会话事件折叠出来的;远程提供方不发布本地子会话,其用量会读作 0。
  • 全新会话里,首个轮次出现之前看不到任何东西。 命令不产生轮次,而对话区的 UI 槽位是严格会话绑定的,任务指示器没有地方挂。announceOnStart 就是为此存在的。

它不做什么

  • 不发明判据。 十五条检查的标准写死在 src/criteria.ts 里,子智能体拿到的是原文;插件自己的名称、分组、菜单文案都是导航用的,从不代替判据。
  • 不替你下结论。 suspected 就是 suspected,报告会告诉你人工需要核实什么。
  • 不搭测试套件。 把发现变成回归测试是另一件事,这里只做审计。

许可

MIT

原始 README: https://github.com/Leeaoyin/dsh-agent-harness-audit/blob/main/README.zh.md ↗