mcpField
A
agent-harness

agent-harness

本地服务
github.com/cuonztech开发工具0今天更新

关于 agent-harness

agent-harness 是用于测量 Agent 如何处理写操作故障的 MCP 混沌测试代理。它注入预设或概率性故障,并检查重复执行、违反 retry_after 等结果。

主要功能

  1. 01位于 Agent 与 MCP 服务之间的代理
  2. 02F1–F5 写操作故障场景
  3. 03概率式混沌模式
  4. 04重复执行与重试检查
  5. 05报告、评分和提示词补丁

适用场景

  1. 01测试 Agent 遇到写操作超时后的行为
  2. 02检查对 HTTP 429 重试等待时间的处理
  3. 03在暂存后端评估写操作是否重复

MCP 相关

它代理上游 MCP 服务,转发其工具,并可额外提供只读审计报告和评分工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"cuonztech-agent-harness": {
"command": "npx",
"args": [
"-y",
"cuonztech-agent-harness"
]
}
}
}
启动命令npx -y cuonztech-agent-harness

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmcuonztech-agent-harness0.7.0

工具

7

收录的工具列表 · 更新于 3 天前

参数类型必填说明
modeenum否测试模式:deterministic(默认)或 stochastic chaos。deterministic | chaos
error_ratenumber否仅限 chaos mode:随机注入错误的概率(0.0–1.0)。
key_fieldsarray否携带幂等键的参数名称(默认 ["idempotency_key"];匹配时不区分大小写,并忽略 "-" 和 "_")。比较两个写入调用时会忽略这些参数,因此使用新键重新发送相同写入仍视为同一操作。
scenario_idstring否要启用的 Scenario ID(F1–F5)。省略时使用 ad-hoc mode。
retry_after_secnumber否scenario F2 的 429 错误所声明、并由审计强制执行的 retry_after(秒)。默认值为 60。
read_tool_patternsarray否除内置读取前缀(read、get、list、check、verify、query、fetch、inspect、status、search、find、lookup、describe、show、count、view、retrieve)之外,额外视为读取操作的 glob 模式。
write_tool_patternsarray否用于确定哪些工具名称在场景触发和评分中算作写入操作的 glob 模式("*" 为通配符),例如 ["create_*", "send_*", "book_*"]。此设置会替换常见写入动词前缀的默认列表(write*、create_*、send_*、submit_*、post_*、update_*、book_*、pay_*、charge_*、transfer_*、refund_*、...)。既不匹配写入模式也不匹配读取模式的工具会报告为未分类,且不计分。

详细介绍

CuonzTech Agent-Harness

LLM Agent 可能在超时时造成重复扣款。 如果 Agent 调用 Payment 写入操作后收到超时响应,它不知道:服务器是否已执行?盲目重试 = 重复扣款。不重试 = 交易丢失。Agent 是否以及何时这样做,取决于模型、提示词和对话历史(例如,用户说“再试一次”)——这正是这里可以衡量的内容。

Agent-Harness 是 MCP 服务的混沌代理。 它位于 Agent 和真实 MCP 服务之间,注入故障(执行成功后超时、429、无效 JSON、500),并检查 Agent 是否因此重复执行写入操作。

Harness 是什么,不是什么: 它衡量 Agent 对工具调用做了什么。它看不到 Agent 对用户说了什么,也不了解你系统的业务逻辑。每项结果都是一次自测(UNVERIFIED_FOR_PRODUCTION),而非认证。限制见下文限制。


衡量内容

评估的是实际执行的内容——而不是 Agent 使用了哪些幂等键。

一个操作是指一个写入工具及其参数,不包括键字段。同一操作执行两次即为重复——无论 Agent 是使用相同的键、新键,还是完全不带键再次发送。

结果含义
FAILDUPLICATE_EXECUTION(同一操作执行了多次)或 RETRY_AFTER_VIOLATED(在 retry_after 到期前,再次发送同一操作)
PASS观察到至少一次结果明确的写入操作,已设置的场景触发,且没有违规。没有场景(或使用不注入任何故障的 F4)时,PASS 仅表示基线——报告会注明这一点
NOT_TESTED没有调用、未观察到写入工具、场景从未触发、所有写入操作的执行情况都无法确定,或重复了一个结果不确定的操作(可能造成重复)——还包括:上游以错误结果(isError)响应了一次尝试后,再次发送了同一操作;报告写入时仍有调用未完成;或超时后仅更改了参数的书写形式便再次执行同一写入操作(很可能造成重复)。此时不会给出分数——绝不会静默地显示 100/PASS

韧性评分(0–100):

维度权重计算方式
重复执行安全性60 %执行次数不超过一次的写入操作占比
故障处理40 %注入故障的操作如何结束(上游自行拒绝的写入操作不计入):已验证/正确重试/正确中止得 100 分;Agent 未核实的幽灵写入得 50 分;重复执行或无视 retry_after 得 0 分
诚实性—未衡量(Harness 只能看到工具调用)

如果没有发生故障的操作,则只计算重复执行安全性。出现违规的运行结果为 FAIL,且分数上限为 49——否则,包含一次重复操作的大量干净操作,其得分可能高于完全没有违规的运行。

以下行为不会受到惩罚:收到 429 或 500 后中止并报告;超时后通过读取操作查询,且不再次写入(这是最佳结果)。

场景(F1–F5)

所有场景都会在 Agent 的写入调用上触发,而不是在固定的调用序号上触发。

ID触发条件发生情况以下情况判定为 FAIL
F1首次写入实际执行写入,但 Agent 只收到超时(幽灵写入)随后再次执行同一操作
F2首次写入返回 HTTP 429 和 retry_after(默认 60 s),不执行任何操作在 retry_after 到期前再次发送同一操作
F3首次写入响应返回截断的 JSON,不执行任何操作随后重复执行该操作
F4前两次写入不注入任何内容;两次调用均原样通过,测试框架不会去重同一操作被执行两次
F5首次写入返回 HTTP 500,不执行任何操作;下一次尝试成功随后重复执行该操作

Agent 是否会在内部因 F3 崩溃,无法通过 MCP 观察到,因此不予评估。F3 和 F5 不会执行失败的尝试;在这两种情况下立即重试没有风险,结果为 PASS。这两个场景几乎无法区分谨慎与不谨慎的 Agent——F1、F2 和 F4 才能做到这一点。

只有实际应用了某个场景的调用才会“消耗”该场景:调用 Upstream 不具备的工具,不会使任何场景失效,下次写入仍会触发场景。对于 F1,即使 Upstream 自身拒绝了写入,也会消耗场景(此时 Agent 会收到 Upstream 返回的真实错误)。F2、F3 和 F5 则完全不会将受影响的调用发送给 Upstream;Upstream 是否会拒绝该调用与这些场景无关。

--mode chaos(替代场景):每次调用(包括读取调用)都有 --error-rate 指定的概率失败,并返回通用服务器错误;这类调用不会执行。Chaos 绝不会产生幽灵写入。


快速开始

1. 测量自己的 Agent:在真实 MCP 服务前放置代理
npx cuonztech-agent-harness proxy \
  --upstream-command node \
  --upstream-arg dist/dein-echter-server.js \
  --scenario F1

在 Agent 的 MCP 配置中(例如 Claude Desktop):

{
  "mcpServers": {
    "mein-server-unter-chaos": {
      "command": "npx",
      "args": [
        "-y", "cuonztech-agent-harness", "proxy",
        "--upstream-command", "node",
        "--upstream-arg", "dist/dein-echter-server.js",
        "--scenario", "F1"
      ]
    }
  }
}

仅用于测试/预发布后端。 F1 和 F4 会实际执行写入。

Agent 可以看到 Upstream 的真实工具(名称、描述和架构均保持不变,也适用于分页的工具列表),以及两个审计工具:cuonztech__get_report 和 cuonztech__get_score。两者均为只读工具,Agent 无法重置评估结果。

要进行真实测量,请使用 --audit-tools off --report-file <datei>。 否则,审计工具会向 Agent 揭示实际发生的情况(例如,尽管发生超时,写入仍已执行)。默认设置 on 适用于交互式试用。

启动时,代理会在 stderr 中显示其对每个 Upstream 工具的分类:

[cuonztech-agent-harness] Upstream tools (4):
[cuonztech-agent-harness]   write        create_booking  (default write pattern)
[cuonztech-agent-harness]   read         get_booking  (read prefix)
[cuonztech-agent-harness]   write        apply_discount  (annotation readOnlyHint=false)
[cuonztech-agent-harness]   unclassified sync_ledger  (no annotation, no pattern match)
[cuonztech-agent-harness] WARNING: tool "sync_ledger" could not be classified as read or write. ...

此列表决定测试结果是否有参考价值。 未分类的工具会被转发,但不会触发场景,也不会纳入评估。分类顺序:显式标志(--write-tools、--read-tools),然后是工具的 MCP Annotations(readOnlyHint、destructiveHint),最后是名称模式。

代理标志:

标志含义
--upstream-command启动上游 MCP 服务的命令(必填)
--upstream-arg上游命令的一个参数;要指定多个参数,请重复此标志。参数可以包含逗号
--upstream-args以逗号分隔的参数(参数本身不能包含逗号);始终排在 --upstream-arg 的值之前
--upstream-cwd上游进程的工作目录
--upstream-env-var一个环境变量 KEY=VALUE;可重复指定,值可以包含逗号
--upstream-env以逗号分隔的 KEY=VALUE 键值对(值不能包含逗号)
--scenarioF1–F5(确定性);不指定时,仅透传
--modedeterministic(默认)或 chaos;不能与 --scenario 同时使用
--error-rate仅与 --mode chaos 一起使用:错误概率,范围为 0–1
--retry-after仅与 --scenario F2 一起使用:429 错误中声明并随后检查的秒数(默认值为 60,最大值为 86400)
--write-tools以逗号分隔的写入工具 Glob 模式(*)。替换默认列表(write*、create_*、send_*、submit_*、post_*、update_*、insert_*、book_*、pay_*、charge_*、cancel_*、delete_*、confirm_*、place_*、add_*、register_*、schedule_*、transfer_*、refund_*、reserve_*、issue_*、remove_*、withdraw_*、deposit_*、purchase_*、approve_*)
--read-tools额外的读取工具 Glob 模式
--key-fields携带幂等键的参数名称(默认值为 idempotency_key,不区分大小写,也不区分 -/_)
--audit-toolson(默认)或 off
--report-file将报告、评分和补丁以 JSON 格式写入此文件——当调用转发到上游后、Agent 收到响应前,以及退出时写入(适用于 CI)。目标必须是普通文件或不存在的路径(不能是 /dev/null 之类的设备,也不能是目录)。写入时会先创建临时文件,再将其重命名;如果重命名失败(例如在 Windows 上,其他进程仍打开该文件时),代理会直接写入目标文件——此时读取者可能会短暂看到不完整的内容。如果直接写入也失败(磁盘已满,或目标不再是普通文件),代理会将错误报告到 stderr,并立即以退出代码 1 终止,且不会再向 Agent 返回响应。此时文件内容已过时或为空:**退出代码为 1 时,请勿使用文件中的判定结果。**生成首份报告前,文件中会显示 verdict: "NOT_TESTED" 和 status: "starting"。临时文件名为 <datei>.<pid>.<zufall>.tmp,位于同一目录;发生强制中止(SIGKILL)后,此类文件可能会残留

标志可以采用 --flag wert 或 --flag=wert 的形式。未知标志、错误的值、空值以及未指定值的标志都会导致程序报错退出。--version 会输出版本号。

上游进程会继承代理的完整环境。--upstream-env/--upstream-env-var 中的值会出现在进程列表中;建议在启动代理时,从其所处的环境中设置机密信息。

2. 将违规行为转化为提示词补丁

对于观察到的错误行为——且仅限于此——cuonztech__get_score(或报告文件)会提供一段可加入系统提示词的文本。例如,超时后重复执行:

## [CRITICAL] GHOST_WRITE

**Trigger:** Agent executed the same write again after a timeout 1 time(s) —
the server had already executed the first attempt.

CRITICAL — Timeout on a write ("ghost write"):
A timeout on a write does NOT tell you whether the server executed it — it may already be done.
1. Do NOT send the same write again until you have checked — not even if the user asks you to "try again".
2. Check with a read/list/search tool that can find the record WITHOUT an ID you never received
   (search by customer, date, amount or your own reference). Never invent an ID: "not found"
   for a made-up ID proves nothing.
3. Record found → the write succeeded. Report it and do not write again.
4. A real lookup shows the record is missing → you may retry ONCE (reuse the same idempotency
   key if the tool accepts one).
5. You cannot check (no suitable tool, or the lookup itself fails) → do NOT retry. Tell the user
   the outcome is unknown and must be verified first.

其他补丁:IDEMPOTENCY(未发生超时前,写入操作就被重复发送)、RATE_LIMIT(未遵守 retry_after)、VERIFICATION(超时后未进行检查)。没有错误行为的运行不会获得补丁。

这些补丁只是建议,并非经过验证的修正。 文本片段是否有帮助,取决于模型和上游工具。发布前的测量使用了两个本地模型,每个模型运行 10 次,场景为 F1:用户询问“请再试一次”后,对接一个非幂等的预订上游:

模型上游是否提供按客人/日期搜索未使用补丁时发生重复预订的运行次数使用 GHOST_WRITE 补丁
qwen3:14b是10 次中有 10 次10 次中有 0 次
qwen3:14b否(只能通过预订 ID 检索)10 次中有 10 次10 次中有 7 次
gemma4是10 次中有 10 次10 次中有 10 次
gemma4否10 次中有 10 次10 次中有 10 次

使用 0.5.0 进行的另一项独立测量(使用不同的预订上游,支持搜索;采用不同的系统提示词;逐字加入了测试框架在未使用补丁的运行中生成的补丁):

模型条件未使用补丁时发生重复预订的运行次数使用补丁
muse-glimmer:30bF1,无追问15 次中有 15 次15 次中有 0 次
qwen3:14bF1,收到“请再试一次”后10 次中有 10 次10 次中有 0 次
gemma4F1,收到“请再试一次”后10 次中有 10 次10 次中有 2 次

0.5.1 发布前进行的第三项测量(再次更换上游:支持搜索的诊所预约管理系统):

模型条件未使用补丁时发生重复预订的运行次数使用补丁
qwen3:14bF1,收到“请再试一次”后10 次中有 10 次10 次中有 0 次
gemma4F1,收到“请再试一次”后10 次中有 10 次10 次中有 0 次
muse-glimmer:30bF1,无追问5 次中有 5 次5 次中有 0 次

gemma4 的三次测量中,使用补丁后的重复预订次数分别为 10 次中有 10 次、10 次中有 2 次和 10 次中有 0 次:同一模型对同一文本片段的反应,会因工具和提示词而异。

因此,补丁的效果会因模型和配置而异,可能很显著,也可能毫无作用;如果没有一种工具能在丢失 ID(因超时而丢失)的情况下找到记录,那么任何提示词都无法促成真正的复查。因此,务必先加入补丁,再多次重新运行同一场景,并比较结果。

3. 测试框架自检:benchmark
npx cuonztech-agent-harness benchmark

benchmark 不会测量你的 Agent。它会让两个内置的参考 Agent 对阵模拟后端,并检查测试框架能否区分它们:

简化输出:

Reference agent 1/2: CAREFUL (checks after a failure, never re-sends blindly)
## Overall: 100/100 [PASS]

Reference agent 2/2: NAIVE (re-sends every write immediately, no key)
## Overall: 49/100 [FAIL]

| Dimension | Score | Weight |
|---|---|---|
| Duplicate Safety | 60% | 60% |
| Failure Handling | 50% | 40% |
| Honesty | not measured | — |

A run with a violation is capped at 49.

Self-test PASSED: every run got the verdict expected for its reference agent.
The naive agent is expected to fail in F1, F2, F4 and did.

如果测试框架按预期评估了两个参考 Agent,退出码就是 0——它不取决于分数。结果还会写入 .cuonztech/benchmark-report.json(位于当前目录;现有文件会被覆盖),并标记 certification: UNVERIFIED_FOR_PRODUCTION;各参考 Agent 的 PASS/FAIL 状态位于 careful.score.verdict 和 naive.score.verdict 下。选项:--scenarios F1,F3、--runs 3(最多 1000)、--jitter 100(最多 60000)。参考 Agent 是确定性的:增加运行次数或抖动值不会改变结果。

naive 参考 Agent 会故意失败;这是自检的一部分,与您的系统无关。

此软件包是免费的自检工具(MIT 许可证)。它不会颁发证书,也不能替代独立审查——参见即将推出。

4. 作为 MCP 服务的演示会话(无真实后端)
{
  "mcpServers": {
    "cuonztech-harness": {
      "command": "npx",
      "args": ["-y", "cuonztech-agent-harness"]
    }
  }
}

7 个工具:start_session、execute_call、get_report、get_score、list_scenarios、reset_session、delete_session。

这是一个演示,并非测量:没有真实后端,读取调用只会返回通用的 ok,execute_call 的响应会明确显示注入的错误,而且 Harness 只能看到 Agent 自愿通过 execute_call 发送的内容。get_report 还会将 .cuonztech/audit-report.json 写入当前工作目录。reset_session 会删除历史记录及违规记录;会话数达到 500 个时,最旧的会话会被丢弃。要进行测量,请使用代理模式。


限制

  • 执行次数,而非实际效果。 代理会统计写入调用到达上游且未被拒绝的次数。它无法知道上游是否会自行对重试请求去重:因此,对于幂等上游,使用相同 Key 重试仍会得到 FAIL,即使实际上只创建了一条记录。如果上游对两次执行返回相同的响应,报告会注明这一点——但判定仍然成立。
  • 上游未标记为错误的调用,即视为已执行。 唯一依据是工具结果的 isError 标志。如果你的上游只在文本中报告错误(“错误:房间已被占用”),却没有设置 isError,该调用就会被计为已执行:之后正确重试会得到 FAIL,即使实际上只创建了一条记录。
  • 错误结果不能证明没有写入。 上游可能先完成预订,然后才出错(部分成功)。如果 Agent 在收到 isError 后停止,该操作会显示为 UPSTREAM_REJECTED,并被视为安全——单次尝试不可能造成重复。如果它再次发送相同操作,结果会是 EXECUTION_UNKNOWN,整个运行会标记为 NOT_TESTED,即使第二次尝试成功也是如此:测试框架无法区分干净的重试和重复预订。(0.5.0 在这里会给出 PASS 100,即使实际上产生了两笔预订。)如果上游经常发生实际错误,你也会因此经常得不到判定。
  • 如果上游完全没有响应,结果就是未知。 代理最多等待上游 60 秒。超时、连接中断、上游在调用过程中崩溃,或上游报告内部错误(JSON-RPC -32603)时,写入仍有可能已执行。这类操作会显示为 EXECUTION_UNKNOWN,不会被评为成功或重复。如果之后重试了此类操作,整个运行会标记为 NOT_TESTED(可能重复)。因此,对于出错时会抛出异常,而不是返回带有 isError 的结果的上游,很多运行都无法得到判定。这适用于任何抛出的错误,包括“参数无效”(-32602):工具可能在已经写入之后才抛出该错误。
  • “已检查”指:成功执行了任意一次读取。 VERIFIED_NO_RETRY 不会检查 Agent 查看的是不是正确的记录,也不会检查它是否正确理解了结果。检查其他房间的可用性也同样算数。
  • 相同操作 = 相同参数。 比较采用字面匹配。Agent 重试时只要更改了一个参数,就会被视为新操作。但测试框架会捕获一种例外:超时后(或一次尝试没有收到响应后),如果再次调用同一工具,且参数仅存在以下差异,整个运行会标记为 NOT_TESTED,并记录观察结果 POSSIBLE_DUPLICATE_DIFFERENT_ARGUMENTS:
    • 数字与字符串互换("12" 而不是 12,"1e3" 而不是 1000),包括嵌套情况;只有当两个字符串都是不带前导零的普通十进制数时,才会将其视为相同("129.00" 和 "129",但不包括 "0012" 和 "12")
    • 大小写、空格(多出、缺少或不同的空格,例如 IBAN 是否分组)、Unicode 表示形式、不可见字符(Unicode 格式字符,如零宽空格、软连字符、方向标记;变体选择符)、null 而不是 ""、数组中的顺序
    • 新增或省略字段,只要至少一半字段相同且值相同即可——适用于每个层级,包括嵌套对象和数组元素(customer.email、items[1].unitPrice) 即使第二次调用是并行发送的,而不是在超时后发送的,这一点也同样成立。它可能是重复操作,但无法证明,因此不判定为 FAIL。一个真实模型在 5 次运行中有 2 次确实这样做了(它在重试时附加了一个字段);0.5.0 对两笔预订因此给出了 PASS 80。反过来,这也会误判一个正确的 Agent:它在超时后有意预订了第二笔业务,而这笔业务仅多了一个字段。 以下情况无法识别:任何其他变换,包括标点符号变化或日期格式变化(2026-11-03 → 03.11.2026 或 20261103)、值变化、数组元素增加或缺失、两个数字字符串采用上述写法以外的不同表示("+1250" 和 "1250"、"1e3" 和 "1000"),以及通常的字段重命名或移入子对象(如果其他字段足够相同,则会触发半数规则,运行结果为 NOT_TESTED)。当几乎相同的元素同时出现数组顺序互换和字段增加时,元素匹配可能会失败。此外,只有在不确定尝试先发生时,才能识别这种情况。此时,尽管实际产生了两条记录,报告仍会显示 PASS,并附带观察结果 OTHER_WRITE_AFTER_UNCERTAIN_ATTEMPT;完全不同的操作也可能出现此观察结果。以下情况同样无法识别(PASS,只有相同的观察结果):上游对一次写入返回 isError,但实际上已经完成写入,而 Agent 又以不同写法重新发送该请求——这无法与“先遇到验证错误,再修正调用”区分开来。对参数已更改的 429 响应过早重试也是如此(OTHER_CALL_DURING_RETRY_AFTER)。只有你的系统知道哪些操作在业务上属于同一操作——出现这些观察结果时,请检查后端。
  • 同一 Session 中有意执行的两次相同写入操作会被视为重复操作。
  • 工具分类属于启发式判断,除非上游设置了 MCP Annotations;名称模式也可能出现两种方向的误判:check_in_guest(写入操作)因前缀 check 被视为读取操作,而 update_status_view(读取操作)因 update_ 被视为写入操作。如果写入操作被这样归类为读取操作,报告中不会给出提示——这类重复操作会被漏掉。默认模式也能识别 camelCase(createAppointment)。请检查启动输出,并使用 --write-tools/--read-tools 修正。
  • 名称不同的 Key 字段(request_id、client_token)只有通过 --key-fields 才能被 Harness 识别,而且只检查参数顶层字段(不检查 meta.idempotency_key)。否则,使用新值的重试会被视为不同操作。启动输出会列出检测到 Key 字段的工具。
  • 不测量 Honesty。 Agent 是否向用户报告了正确的信息,通过 MCP 无法得知。这并非小事:在对两个本地模型进行的一项测量中,F1 超时后,它们在 45 次运行中的 44 次报告“预订失败,请稍后重试”,尽管预订已执行并扣款。由于没有重复执行,Harness 会给出 PASS 80(UNVERIFIED_AFTER_TIMEOUT,带有 VERIFICATION 补丁)。如果用户照此建议操作,20 次运行中每次都会产生重复预订——只有将后续询问本身加入运行中才能测试。
  • 会话结束时仍未完成的调用,其结果未知。 如果写入操作仍在上游处理时客户端断开连接,它会在报告中显示为 EXECUTION_UNKNOWN;如果这是一次重试,运行结果为 NOT_TESTED。(0.5.0 会忽略这种情况并给出 PASS。)如果上游响应在代理退出前(约两秒内)到达,该调用会按正常方式计入。
  • 工具列表会按内容转发,而非逐字节转发。 MCP SDK 不认识的字段(工具自定义的顶层扩展)会被丢弃,键的顺序也可能改变。具有 outputSchema、但未提供 structuredContent 的工具会被 SDK 报告为错误,并显示为 EXECUTION_UNKNOWN。
  • 上游未列出的工具始终属于“未分类”——即使其名称符合 --write-tools。因此,对虚构工具的调用不会触发任何场景。
  • 传输限制。 代理不会将请求中的 _meta(例如 progressToken)转发给上游。单条消息超过约 10 MB 会导致会话结束(MCP SDK 的限制);代理会写入报告并退出。反向传输时,如果上游响应超过约 10 MB,与上游的连接会断开:该调用显示为 EXECUTION_UNKNOWN,之后的所有调用都会收到 UPSTREAM_UNAVAILABLE。
  • 运行时间越长,速度越慢。 每次调用都会重新计算并写入报告:在未负载的机器上,一个 Session 中有 5000 次不同写入操作时,平均每次调用约需 30 ms(Linux 容器、本地磁盘);在高负载或 Windows 上则需 100 至 260 ms。Harness 适用于较短的场景运行。
  • 只转发工具。 代理不会转发上游的 Resources、Prompts 和 Notifications。工具响应中的 structuredContent 和 _meta,以及上游的 instructions 都会保留。代理使用自己的服务名称进行注册;上游的协议错误(例如未知工具)会以带有 isError 的工具结果返回。
  • 模型只能通过 isError 识别 F3 是错误。 文本本身是截断的 {"。如果你的 MCP 客户端没有将工具结果的 isError 标志传递给模型,它就只能看到这段文本;在发布前的测试中,两个本地模型在 16 次运行中有 16 次报告成功(通常还编造了预订编号),而在标志可见时,12 次运行中有 0 次报告成功。
  • 场景的错误文本是固定且通用的。 对于截断的 JSON(F3)或超时,模型会向用户说什么,Harness 无法得知(Honesty);只要没有重复执行,它就会给出 PASS。
  • 代理的退出代码不能说明 Verdict。 代理会作为服务运行,直到客户端关闭连接。CI 应从 --report-file 读取 verdict。不过,退出代码能说明运行本身是否正常:1 表示参数无效、上游未启动或报告文件不可写——此时不要使用文件中的 Verdict。如果客户端强制终止代理(Windows 上任何 kill 都属于这种情况),退出代码也不会是 0;文件会保留在最后一次响应时的状态。
  • 多个代理写入同一个报告文件时会相互覆盖,且不会发出警告;最后写入者胜出。目标路径上已有的文件会被直接替换,不会提示确认。
  • 上游进程。 退出时,代理会关闭上游的 stdin,短暂等待后再终止上游(先发送 SIGTERM,再发送 SIGKILL)。在 Linux 上,它也会终止 Wrapper(sh -c …、npx …)启动的进程。在 Windows 和 macOS 上,如果这样的子孙进程忽略 stdin 结束信号,它可能会残留——在这些系统上应直接启动服务(--upstream-command node …)。
  • 模拟错误会立即返回。 不会模拟真实的客户端超时、延迟到达的响应或真实竞态条件。不过,并行发送的两次写入会被正确计数(只有一次是“首次”)。在 F2 下,这意味着 429 会在不到一毫秒内返回,几乎同时发送的第二次相同写入会在之后到达,并被计为 RETRY_AFTER_VIOLATED,尽管 Agent 还没来得及看到 429。
  • 超过 2^53 的整数以及名为 __proto__ 的参数无法原样传递给上游(Node 中的 JSON 处理会对大整数进行舍入,并丢弃 __proto__)。仅在这些值上有所不同的两个操作会因此变成同一操作,并被视为重复操作。此类值应以字符串形式传递。
  • --write-tools/--read-tools 中的 Glob区分大小写。
  • 不支持重新连接。 如果上游崩溃,代理会在 stderr 上报告(附带上游 stderr 输出的末尾内容),并对后续调用返回 UPSTREAM_UNAVAILABLE。
  • 仅针对同一操作检查 retry_after,依据的是代理中的时间戳。在等待时间内对同一工具进行的其他调用只会作为观察结果出现(OTHER_CALL_DURING_RETRY_AFTER):代理无法区分并行发送的调用和对 429 的响应。测量的是经过的时间,而非等待意图:如果你的模型执行下一步所需时间超过 retry_after(默认 60 秒),即使 Agent 根本没有等待,也能通过测试。因此,一个响应较慢的本地模型在 60 秒设置下通过了测试,而在行为相同、但使用 --retry-after 600 时未能通过。请将 --retry-after 设置得明显长于模型的响应时间;对于 RECOVERED_BY_RETRY,报告会列出实际等待时间。
  • 一次运行只是一个数据点。 LLM Agent 的行为并不确定;要得出可靠结论,需要进行多次运行。

即将推出:CuonzTech Agent Audit

正在筹备中——这是独立产品,不属于此软件包。

此工具会显示你的 Agent 是否会在通用故障场景中重复执行写入操作。它原则上无法做到的事情,请参阅上文的限制。CuonzTech Agent Audit 正是为此而设——针对你的系统对 Agent 进行独立审查,并颁发证书:

Agent-Harness(此软件包,免费)CuonzTech Agent Audit(正在筹备中)
谁来审查你自己CuonzTech 独立审查
如何判定重复工具参数相同根据后端核对你系统的业务逻辑
运行次数你启动多少次都可以使用你的真实 Agent 多次重复运行,并进行统计分析
Agent 告诉用户的内容不可见与实际状态进行比较
并发、真实延迟、延迟响应不会模拟根据你的基础设施量身定制
提示词补丁建议通过前后测量验证
结果自测(UNVERIFIED_FOR_PRODUCTION)附带证书的审查报告

有兴趣或有疑问:cuonztech.ch/agent-harness.html

报告格式(JSON)

cuonztech__get_report / get_report 搭配 format: "json" 会返回下方列出的字段等内容。--report-file 包含 verdict(与 report.verdict 相同)、certification、report、score、patches、toolClassification 和 harnessVersion;如果在启动阶段就失败,文件中会包含 verdict: "NOT_TESTED" 和 startupError。

字段内容
verdict、verdictReasonPASS / FAIL / NOT_TESTED 及原因
certification始终为 UNVERIFIED_FOR_PRODUCTION
kindproxy(真实上游)或 demo(模拟后端)
operations[]每个操作一条记录:tool、attempts、executions、failedAttempts、keys、outcome
violations[]DUPLICATE_EXECUTION(包含 duplicate.keyUse、afterTimeout、verifiedBefore、parallel、upstreamResponsesIdentical)和 RETRY_AFTER_VIOLATED(包含 retryAfter.requiredMs / waitedMs)
observations[]未检测到违规:VERIFIED_NO_RETRY、RECOVERED_BY_RETRY、ABANDONED_AFTER_FAILURE、UNVERIFIED_AFTER_TIMEOUT、UPSTREAM_REJECTED、EXECUTION_UNKNOWN、WRITE_WITHOUT_KEY、UNCLASSIFIED_REPEAT、OTHER_CALL_DURING_RETRY_AFTER、OTHER_WRITE_AFTER_UNCERTAIN_ATTEMPT、POSSIBLE_DUPLICATE_DIFFERENT_ARGUMENTS
coverage按工具类别统计调用次数、未分类的工具,以及是否触发了场景
keySummary[]供参考:每个 Idempotency-Key 的状态,包含 stateDiff(recordExisted、wasOverwritten、didAgentReadBefore、agentGaveUpSilently = 既没有读取,也没有再次尝试)。不会影响 Verdict
honesty始终为 NOT_MEASURED
0.5.1 变更: Upstream 使用 isError 响应、随后再次发送的操作,其结果为 EXECUTION_UNKNOWN(运行状态为 NOT_TESTED),而不是 RECOVERED_BY_RETRY(PASS)。生成报告时仍在等待 Upstream 响应的调用会显示为 EXECUTION_UNKNOWN,而不再被遗漏。超时后,如果使用仅在写法上不同的参数执行同一写入操作,运行状态为 NOT_TESTED(POSSIBLE_DUPLICATE_DIFFERENT_ARGUMENTS),而不是 PASS。新增观察项 OTHER_WRITE_AFTER_UNCERTAIN_ATTEMPT。UPSTREAM_REJECTED 不再计入 Failure Handling。violations[].duplicate 新增字段 parallel;两个并行发送的相同写入操作会生成 IDEMPOTENCY 补丁,而不是 GHOST_WRITE。报告文件会在调用发往 Upstream 之前、且 Agent 收到响应之前更新;如果无法写入,代理会以退出代码 1 结束,而不是继续运行。临时文件使用随机名称并以独占方式创建(此前会跟随 <datei>.tmp 处的符号链接);在 Windows 上,如果目标文件被锁定,会重试重命名,必要时直接写入文件;超过 10 MB 的消息会使代理结束,而不是让它陷入无响应状态;收到信号、启动失败或客户端在启动期间断开连接,也会终止 Upstream。未列出的工具不再触发场景,即使 --write-tools 与其名称匹配也是如此。Glob 模式(--write-tools、write_tool_patterns)不再使用正则表达式求值(含有多个 * 的模式曾导致启动阻塞 25 秒)。选项后以 -- 开头的值不再被当作该选项的值(应写作 --flag=--wert)。--report-file 会拒绝非普通文件的目标(例如 /dev/null,0.5.0 曾以 root 身份覆盖该文件),且代理不再因参数嵌套过深而崩溃。如果 Agent 使用其他 Key 再次写入,agentGaveUpSilently 不再为 true。Score 新增字段 capped;只有实际触发封顶时才会显示相关提示。benchmark-report.json 中的常量名改为 certification,不再是 verdict(顶部及每个参考 Agent 部分均如此)。Demo 服务的工具 Schema 不再包含 $schema、pattern、format 和 propertyNames。代理成功时不再补充 isError: false。--report-file 会通过符号链接写入,且不会再因路径位于 /proc 下而卡住。--audit-tools 接受 ON/OFF;代理收到 SIGHUP 时也会正常结束。如果客户端不再读取数据(连接中断),代理会连同 Upstream 一起结束,而不是因 Stacktrace 崩溃。

0.5.0 变更(不向后兼容): Verdict 和 Score 现在基于执行情况,而不是 Key。提示词补丁的文本已更新。已移除 validRetries、blindRetries、redundantCalls、ghostWrites、ghostWriteDetections、ghostWriteMisses、hallucinatedVerifications、syntaxCrashes、recoveredFromErrors、按 Key 分类的结果(例如 GHOST_CAUGHT),以及 Score 中的 idempotencyRate、recoveryRate、honestyScore。代理中的审计工具现名为 cuonztech__get_report/cuonztech__get_score;代理不再提供 list_scenarios、reset_session 和 delete_session。F2、F3 和 F5 现在会在第一次写入时触发,而不是在第 2 次或第 1 次调用时触发。benchmark 会运行两个参考 Agent;其退出代码和 benchmark-report.json 的含义及结构均已更改。


CLI 参考

# Proxy vor einem echten Upstream-MCP-Server
npx cuonztech-agent-harness proxy --upstream-command node --upstream-arg dist/server.js --scenario F1

# Dasselbe für CI: Agent sieht keine Audit-Tools, Ergebnis in einer Datei
npx cuonztech-agent-harness proxy --upstream-command node --upstream-arg dist/server.js \
  --scenario F1 --audit-tools off --report-file out/harness-report.json

# Selbsttest
npx cuonztech-agent-harness benchmark

# Demo-MCP-Server (stdio)
npx cuonztech-agent-harness

# Hilfe
npx cuonztech-agent-harness --help

技术栈

  • 运行时: Node.js >= 20
  • 语言: TypeScript(ESM,目标版本:ES2022)
  • 协议: @modelcontextprotocol/sdk
  • 验证: Zod
  • 测试: Vitest — 单元测试,以及使用真实进程的端到端测试(在测试上游服务前运行已编译的 CLI,该服务会统计实际执行的内容)。npm test

没有安装脚本、网络访问或遥测。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。