2026年8月13日 1 分钟阅读

别只看 Agent 最后的回答:用 Dynobox 把工具调用、文件改动和命令变成可重复的验收

tinyash 0 条评论

让编码 Agent “修好一个问题”之后,最容易得到的证据是一段自然语言总结。但对工程流程而言,这种总结并不充分:它有没有读到正确的配置文件?是否真的运行过测试?有没有在不该改动的文件中留下副作用?不同 CLI 或模型在同一任务下是否稳定?

Dynobox 是一个 Apache-2.0 许可、面向编码 Agent 与技能工作流的开源测试运行器。它的核心不是让另一个模型给答案打分,而是把可观察行为写成断言:工具调用、Shell 命令、文件产物、HTTP 请求、会话文本与最终回答都可以成为验收对象。当前它支持在本地调用已安装且完成认证的 Claude Code、Codex 与 OpenCode;每个场景在新的临时工作目录中执行,方便隔离测试夹具和产物。

这使它特别适合测试“多步骤 Agent 是否按约束完成任务”,而不是只判断回答看起来是否合理。

先定义任务的可观察契约

传统单元测试通常断言函数输入输出;Agent 工作流还需要说明过程边界。假设团队想验证一个 Agent 是否会检查 package.json、不修改文件、并在最终回答里说明测试脚本存在,那么验收目标至少包括三层:执行了 cat package.json,没有调用编辑工具,回答中提到了 test。

Dynobox 把这些要求放进名为 dyno 的配置。配置可用 TypeScript、JavaScript 或 YAML 编写;CLI 默认发现当前目录下的 *.dyno.{mjs,js,ts,mts,yaml,yml} 文件。下面的 YAML 示例使用公开文档中支持的断言类型,测试一个只读检查任务:

name: package-script-check
harnesses:
  - claude-code
scenarios:
  - name: 检查测试脚本但不改文件
    setup:
      - |
        cat > package.json <<'JSON'
        {"scripts":{"test":"vitest run"}}
        JSON
    prompt: >-
      Use cat package.json and tell me whether a test script exists.
    assertions:
      - type: command.called
        executable: cat
        command:
          args: [package.json]
      - type: tool.notCalled
        tool: edit_file
      - type: artifact.unchanged
        path: package.json
      - type: finalMessage.contains
        text: test

运行方式很直接:

npm install -g dynobox


dynobox run package-script.dyno.yaml

这里值得注意的是,setup、JavaScript/TypeScript 配置以及验证命令都是真实执行的本机代码。临时目录只用于分隔每个 job 的文件,并不是安全沙箱;不能把来源不明的 dyno 当作无害测试数据运行。这也是为什么好的 dyno 应该像测试代码一样受代码审查,而不是让 Agent 在 CI 中随意生成后立即执行。

把“回答对了”拆成过程、产物与结果

Dynobox 的断言可以覆盖三类常见失败。第一类是路径错误:Agent 虽然回答正确,却绕过了你希望它读取的项目配置;command.calledcommand.notCalled 可以约束可见的 Shell 行为。第二类是副作用错误:任务本应只分析,却编辑了 lockfile 或配置;artifact.unchangedartifact.existsartifact.contains 可以检查最终文件状态。

第三类是 Agent 特有的过程要求。例如要求它引用某份技能说明、避免某个工具、先执行步骤 A 再执行步骤 B,分别可以用技能、工具和顺序断言表达。对 HTTP 请求,Dynobox 可在场景需要时启动按 job 隔离的本地代理,记录遵循其代理和 CA 配置的子进程流量。文档也明确说明:harness 自带的 Web 工具,或自行管理网络栈的客户端,未必会被这个代理捕获。因此 HTTP 断言是可观察证据,不应被误解为完整的网络隔离或审计系统。

如果想比较多个 Agent,不需要复制同一份场景。可以在运行时覆盖 harness,并重复执行多次:

npx dynobox run package-script.dyno.yaml \
  --harness claude-code,codex,opencode \
  --iterations 5

重复次数属于运行参数,不必写死在 dyno 中。这样得到的不是“某模型能力分数”,而是具体任务在指定环境、指定提示和指定验收条件下的通过率。它有助于发现 CLI 升级、提示词漂移、工具权限变化或偶发失败,但不应被外推成一般性的模型排行榜。

从本地验收过渡到 CI 时,先收紧信任边界

本地可用并不代表能安全地直接放进 CI。Dynobox 的 CI 文档强调,dyno 的 setup 和 verification 命令会执行,调试日志与 JSON 报告还可能包含提示、会话记录、工具事件、stderr、命令输出与源文件内容,并且不做脱敏。若工作流签出来自不受信任分支的 dyno,同时注入模型密钥,等于让该分支的代码有机会借 Agent 执行环境读取密钥或敏感上下文。

因此推荐分两段落地。第一段在开发者机器或受保护分支上,为高风险 Agent 任务写小而明确的 dyno:每个场景只验证一个行为契约,夹具只保留必要文件,断言优先检查命令和产物。第二段才接入 CI:将需要模型凭据的执行限制在受保护分支、经过环境审批的手动触发,或不运行 PR 作者自带 dyno 的基线修订;checkout 使用最小 contents: read 权限并关闭持久化凭据。

文档给出的 CI 模式目前是参考实现,而不是打包好的 GitHub Action:让一个 job 执行 npx dynobox run skills --debug --reporter json,由 dyno 自己定义 harness 矩阵。这样本地与 CI 使用相同的场景文件,避免工作流再维护一份容易漂移的模型列表。需要上传调试产物时,也应把它们当作可能含敏感信息的构建产物,限制保存时长和可见范围。

适用边界:验证约束,不替代代码质量判断

Dynobox 最有价值的场景是:团队已经知道“成功”应留下哪些可检查证据,但过去只能靠人工翻终端记录确认。比如一个修复 Agent 必须先读取迁移说明、只修改指定目录、运行目标测试,并在最后报告中解释风险;这些都能拆成独立、可复跑的断言。

它不替代现有测试套件、静态检查、人工代码审查或生产监控。一次 command.called 只能证明某条命令被调用,不能证明测试覆盖了真实风险;文件未变也不能证明设计正确。更务实的做法是把 Dynobox 放在这些机制之间:它负责验证 Agent 是否以约定方式工作,项目原有的测试与审查继续验证代码本身是否值得合并。

从一条场景开始,避免把评测变成另一个脆弱系统

第一次引入时,不必急着为每条 Agent 提示都写 dyno。挑一个返工代价高、但验收边界清晰的任务即可,例如“只允许修改某个迁移目录”“必须先读兼容性文件再生成补丁”“不得调用网络工具”“必须保留既有配置”。先把最不能退让的两三项写成断言,再在真实失败案例出现后补充规则。断言过于宽泛时,测试会允许错误行为通过;断言过于依赖某个 CLI 的显示文本或偶发执行细节时,又会让合理的实现被误判。因此要优先约束结果和必要过程,而不是要求 Agent 复刻某一段完整终端输出。

场景文件同样需要版本化。将夹具、提示、断言与被测代码一起提交,给变更写明原因:是业务规则改变、工具接口升级,还是发现了此前未覆盖的失败模式。对于跨 harness 的比较,先确保每个运行时的权限模式、可用工具和测试数据一致;否则通过率差异可能来自环境,而不是模型行为。调试时可使用 --verbose--debug--reporter json 读取记录,但应把调试输出留在受控位置,尤其不要默认把完整 transcript 上传到外部系统。

还应把“失败”分类处理。断言失败可能说明 Agent 违反契约;harness 启动失败可能只是认证、PATH 或临时网络问题;验证命令失败则可能是夹具本身或项目基线已损坏。把这些情况都当成模型质量问题,会让指标失去解释力。实际落地时,可先记录每次失败的证据类型和复现条件,再决定是调整提示、增加断言、修复环境,还是把任务交回人工处理。这样 Dynobox 输出才会成为工程反馈,而非又一个只报红绿的黑盒。

当团队开始把 Agent 当作工程流水线的一部分,最终回答只是一个展示层信号。把读取、调用、改动和验证转换成显式契约,才能把“它说完成了”推进为“这次执行留下了能复查的证据”。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。