跳到主要内容

第 4 章:设计一个可评测的 Agent 任务

“给你一些服务日志,找出故障并写一份报告。”这句话适合交代工作,却不适合直接成为 Benchmark。哪个目录里有日志?“故障”指最早异常、直接失败点,还是可修复的根因?报告写到哪里、采用什么格式?如果两个候选原因都说得通,怎样完成?这些问题若只藏在测试代码里,低分测到的可能是猜题能力;若把正确组件名和关键事件写进示例,满分又可能只是复制答案。

本章把贯穿项目的第一项业务需求——“分析服务日志并生成结构化故障报告”——收紧为一个可评测的 Harbor Task。重点不是先写 Dockerfile 或 Verifier,而是建立测量契约:要测哪种能力,Agent 看见什么,必须留下什么结果,哪些事实应公开成接口,哪些答案必须只留在评分侧。后续第 5~7 章再实现环境、Solution 和 Verifier。

读完本章,读者应当能够:

  • 把业务场景拆成能力目标、场景包装与控制变量;
  • 编写不依赖隐藏假设、又不泄露标准答案的 instruction.md
  • 定义输入、输出、完成条件与边界情况,并把它们映射到可观察事实;
  • 识别答案泄露、关键词捷径、测试篡改和“靠猜常见根因”等失败设计;
  • 用 Harbor v0.18.0 初始化并静态加载一个单步 Task;
  • 使用可执行的评审清单决定任务能否进入环境实现阶段。

4.1 先定义能力,不要先写故事

一个 Task 同时承载两种东西:能力目标是希望从 Agent 行为中推断的能力;场景包装是让这种能力有具体输入和语境的题面。二者相关但不等价。

以本章任务为例,服务名叫 checkout-api、日志位于 /workspace/input/,都属于包装。真正要测的是:Agent 能否跨文件关联事件、排除干扰项、找出一条解释失败现象的主要因果链,并以机器可读结构引用证据。把服务名换成 billing-worker,目标能力不应改变。反过来,若只把日志从 100 行扩充到 10 万行,任务或许更慢,却不一定增加因果诊断难度。

先写一张能力—证据表:

能力维度Agent 必须产生的可观察事实不应冒充该能力的代理指标
结构化读取report.json 能按公开契约解析文件存在、字数很多
因果诊断root_cause 与作者侧事实一致出现“数据库”“超时”等常见词
证据归因引用的 event_id 确实存在,并共同支撑结论复制整段日志或只引用最后一条 ERROR
影响判断唯一受影响的 request_id 集合正确简单统计 ERROR 行数
输入完整性分析前后输入日志内容一致声称“只读分析”

这里刻意没有把“报告文笔好”列入首个核心能力。摘要和建议仍可保留,便于人读;但若首版 Verifier 只能确认字段非空,就不能把该结果宣传为对写作质量的测量。第 11、12 章再为主观质量增加独立评分维度。

4.1.1 明确本章的测量边界

本章 Task 的能力目标可以写成一句可证伪的声明:

给定一组格式已知、证据充分且包含干扰项的本地服务日志,Agent 能识别唯一的主要根因,引用跨文件事件证据,计算受影响请求集合,并生成符合公开结构的 JSON 报告。

这句话同时排除了若干内容:不要求修复服务,不连接生产环境,不从互联网查故障案例,不评价服务恢复,不把工具调用次数作为成功条件,也不处理多个同等根因。它们不是不重要,而是会把首个 Task 变成几个能力的混合测量。

“唯一主要根因”和“证据充分”是任务作者必须保证的前置条件,不能留给 Agent 猜。若两个独立根因都能解释全部失败,测试却只接受一个,问题在 Task;若某个输入文件偶尔缺失,应先归为环境构建失败,而不是让 Agent 因不可见证据得 0 分。

4.1.2 从完成条件反推输入

先列出不可妥协的完成条件,再反推输入应提供什么:

  1. 输出必须是 /workspace/report.json 中的单个 UTF-8 JSON 对象;
  2. root_cause.componentroot_cause.category 能与作者侧事实比较;
  3. 每条证据通过“文件名 + 全局唯一 event_id”引用,而不依赖易变的行号;
  4. affected_request_ids 去重并排序,使相同语义有稳定表示;
  5. 输入日志在运行前后内容一致。

于是输入日志至少要采用明确编码,并为事件提供稳定 ID;用于计算影响的记录要有 request_id;构成因果链的时间戳需要同一时区;分类与因果关系还要有不依赖自然语言猜测的公开表示。所有这些字段必须在题面中解释。否则,Verifier 对它们的检查就是隐藏要求。

4.2 把 instruction 写成公开接口

Harbor 单步 Task 从 instruction.md 读取给 Agent 的任务文本;task.toml 保存配置与元数据。v0.18.0 的 Task 加载时解析 TOML,再读取单步指令;有效目录还需要 task.tomlenvironment/instruction.md,默认共享 Verifier 场景还要有与目标操作系统匹配的测试入口。1 这说明“目录可加载”和“题目设计正确”是两层验收:前者能由代码检查,后者仍要做语义评审。

一份合格 instruction 至少回答五个问题:目标是什么、输入在哪里且如何解释、输出写到哪里且格式怎样、什么算完成、哪些动作禁止。Harbor v0.18.0 随附的 create-task 指南也要求明确目标、输出和约束,同时只描述完成状态,不泄露测试实现。2

4.2.1 输入契约

本任务把 /workspace/input/*.log 定义为 UTF-8 JSON Lines。每行是独立 JSON 对象,具有 timestampevent_idcomponentlevelmessage;与请求关联的记录还具有 request_idevent_typeoutcome。事件若由另一个事件直接导致,就含有 caused_by_event_id,其值是直接原因的 event_id。每个 ERRORFATAL 事件还含有 failure_codeevent_id 在全部输入文件中唯一。

timestamp 仅接受 RFC 3339 UTC 子集 YYYY-MM-DDTHH:MM:SS[.fraction]ZT/Z 大写,YYYY 四位,Gregorian 日期合法,HH0023MM/SS0059,小数秒省略或含一位以上数字。禁止数值偏移、空格、缺秒、逗号小数、小写 t/zsecond = 60;环境和 Verifier 均排除 leap second。RFC 3339 §5.6~5.7 的一般语法容纳小数秒,并在规定条件下容纳小写字母与 60;这是 Task 明示的更窄子集,不是声称 RFC 3339 全面禁止 603

对每个 request_id,将 request.completed 按解析后的 UTC 时刻、同刻再按 event_id 排序,不得比较原始时间戳;末项为终态,failure 纳入目标集合 Tsuccess 排除,即使此前失败过。

failure_code 采用穷举词表,每个代码只映射到一个类别,因此不再需要把“DNS 故障算依赖还是网络”之类的优先级藏进 Verifier:

root_cause.category允许映射到该类别的 failure_code
networkdns_failureroute_failureconnection_failureconnection_resettls_transport_failure
resourcecpu_exhaustedmemory_exhausteddisk_exhaustedfile_descriptor_exhaustedthread_exhaustedconnection_pool_exhaustedquota_exhausted
configurationmissing_settinginvalid_settingdeployment_misconfiguration
dependencydependency_unavailabledependency_errorinvalid_upstream_response
applicationunhandled_exceptioncode_defectdata_processing_error

从任一 T 中的事件出发,反复沿 caused_by_event_id 回溯,直到遇到没有该字段的事件;得到的完整序列叫该目标的因果路径。作者侧必须保证:所有引用都存在;图中没有环;每条目标路径都终止;每个目标事件都有父事件,且 T 中任意两事件不存在祖先关系;所有目标路径的无父事件是同一个 ERRORFATAL 事件;该事件具有合法 failure_code;路径并集跨越至少两个文件。这个共同无父事件就是唯一主要根因,不再使用无法机械判断的“最早可操作”措辞。

这些信息是接口,不是答案。公开日志格式不会告诉 Agent 哪个组件有错;反而能避免把解析格式、时区和去重规则变成无意的谜题。作者侧还要保证:

  • 目标路径符合上述唯一性与跨文件约束;
  • 有真实但不构成根因的 WARNING/ERROR 干扰项;
  • 不包含真实或合成凭据、个人信息或可访问生产系统的标识;
  • 不存在 expected.json、备份文件、注释或镜像层残留直接给出答案。

最后一项尤其容易误解。日志当然必须含有推导答案所需的证据;“不泄露答案”指的是不另外暴露已归纳好的结论、Gold patch、预期输出或评分规则,而不是删掉可解线索。

这里的因果指针与故障代码是首个教学 Task 的受控标注,不代表真实日志天然具有完整因果图,也不把开放世界中的根因诊断简化为普遍适用的唯一语义。它牺牲一部分写实性,换取首版评分契约的公开、唯一和可复现;后续若移除这些标注,就必须引入经过独立复核的人工 Gold 与相应的不确定性处理,不能沿用本章的精确判定承诺。

本版也不测脱敏。既然输入不含真实或合成秘密,Verifier 就不应临时发明“疑似凭据”正则。未来若要加入安全处理能力,应先在输入中定义显式的 sensitive 标记,声明值均为合成数据,再公开固定占位符、禁止原样出现的字段范围和逐值比较规则;在那之前,它只是环境数据治理要求,不是评分项。

4.2.2 输出契约

首版报告使用下列字段。公开字段、类型和枚举是契约;具体组件、类别、事件 ID 与请求 ID 是实例答案,不出现在 instruction、示例或元数据中。

路径类型与约束作用
schema_version字符串,固定为 "1.0"区分报告契约版本
incident_summary非空字符串面向人的简要现象
root_cause.event_id字符串,精确等于共同无父事件的 event_id唯一指明根因事件
root_cause.component区分大小写,逐字等于根因事件的 component 字符串避免别名与规范化猜测
root_cause.category由根因事件的 failure_code 按公开表唯一映射稳定分类
root_cause.explanation非空字符串解释因果联系,不要求固定措辞
evidence全部目标因果路径的事件并集每项精确包含 fileevent_idrole
impact.affected_request_idsTrequest_id 的精确去重集合,按字典序排列最终成功的请求明确排除
recommended_actions1~3 个非空字符串记录建议;首版只验结构,不声称测量质量

evidence 不是“至少给几条”的开放集合,而是路径并集的唯一规范化表示:每个事件恰好出现一次,既不能遗漏,也不能加入路径外事件;file 必须逐字等于包含该事件的输入文件基本名;role 对共同无父事件取 root,对 T 中的事件取 target-failure,对其余严格中间事件取 causal-link。数组沿用上面的比较规则:先按解析后的 timestamp 时刻升序,时刻相同再按 event_id 字典序。输入保证目标事件都有父事件,所以三种角色不会冲突。

为避免“宽松解析器接受、严格 Verifier 拒绝”,键集合也公开固定:顶层只能有 schema_versionincident_summaryroot_causeevidenceimpactrecommended_actionsroot_cause 只能有 event_idcomponentcategoryexplanationimpact 只能有 affected_request_ids;证据项只能有 fileevent_idrole。任何层级都不接受未知字段。JSON 对象键本就必须唯一;两个证据项也不得引用同一 event_id。摘要、解释和建议仍只检查类型、非空与数量,不把措辞质量算入首版核心分数。

完成条件既不能只写“生成 JSON”,也不应规定“先用 jq,再用 Python”。前者不足以验收语义,后者把实现过程误当结果。Agent 可以使用 rgjq、Python 或其他可用工具;只要产物满足同一终态,方法不应影响正确性。

4.2.3 处理边界与隐藏假设

Harbor 的任务分析 rubric 会把“测试期待具体参数、格式或结构,而 instruction 没有精确定义”以及“测试检查需要 Agent 自行猜测的行为”视为任务规范问题。4 写完 instruction 后,可逐项问:

模糊问题本任务的显式决定
“全部日志”包括什么?只读取 /workspace/input/ 直属目录中的 *.log
时间戳接受什么格式?仅上述 UTC 子集;大写 T/Z、合法日历、固定范围、拒绝 leap second
时间顺序怎样比较?解析后比较 UTC 时刻,同刻再按 event_id;不按原字符串或文件顺序
重试怎样计数?每个请求只看最后一条完成记录;最终成功排除,最终失败计一次
什么是“主要根因”?从每个目标失败沿 caused_by_event_id 回溯得到的共同无父事件
类别冲突怎样解决?failure_code 穷举且只属于一个类别,按公开映射表确定
哪些事件是证据?恰好是所有目标因果路径的并集,不多不少
行号变化怎么办?使用稳定 event_id,不要求物理行号
可以修服务或改日志吗?不可以;这是只读分析 Task
证据不足怎么办?不作为本任务实例;作者应判定环境/数据不合格
建议写得多是否分更高?不会;首版只检查 1~3 项与非空

这个定义没有声称“最早出现的日志就是根因”。例如,某请求超时之前总会出现进程启动事件,但只有被因果指针纳入目标路径的事件才参与 Gold。环境设计者仍要检查指针是否忠实表达了题目故事;若两个独立评审者会为同一目标建立不同的父边,就应修改输入标注,而不是让 Verifier 猜自然语言因果。

4.3 最小可执行设计

下面只初始化并静态评审 Task,不运行 Agent,也不声称任务已经可解。Harbor v0.18.0 的公开 CLI 注册了 harbor task init [NAME],支持 -p/--tasks-dir--description 与可重复的 --author;随附教程使用同一子命令,该版本测试确认默认会生成 instruction、配置、Dockerfile、pytest 测试入口和 Solution 模板。5

~/harbor-lab 运行。前置条件是第 3 章已安装 harbor==0.18.0,目标目录尚不存在。命令会创建 ~/harbor-lab/tasks/log-incident-report/;成功时打印 Task initialized,目录冲突或名称不合法时应先清理设计而非覆盖已有任务。

mkdir -p ~/harbor-lab/tasks
cd ~/harbor-lab
harbor --version
harbor task init llmkb/log-incident-report \
-p tasks \
--description \
"Diagnose a service incident from local logs and write a structured report." \
--author "Book Team"
find tasks/log-incident-report -maxdepth 3 -type f | sort

在 v0.18.0 上,初始化结果包括:

log-incident-report/
├── .gitignore
├── README.md
├── instruction.md
├── task.toml
├── environment/
│ └── Dockerfile
├── solution/
│ └── solve.sh
└── tests/
├── test.sh
└── test_outputs.py

Harbor 官方 Cookbook 的 simple-task 也展示了 instruction、环境、测试和 Solution 四层最小结构。它是独立仓库、没有随 Harbor v0.18.0 标签锁定;本章核对的固定提交仍使用兼容旧名 version = "1.0",而 v0.18.0 初始化器实际输出 schema_version = "1.3"。因此下面以 v0.18.0 模型和实测 CLI 输出为准,不逐字复制 Cookbook 配置。67

task.toml 收紧为:

schema_version = "1.3"

[task]
name = "llmkb/log-incident-report"
description = "Diagnose a service incident from local logs and write a structured report."
authors = [{ name = "Book Team" }]
keywords = ["log-analysis", "incident-response", "json", "causal-diagnosis"]

[metadata]
difficulty = "medium"
difficulty_explanation = "Teaching hypothesis: requires cross-file event correlation and distractor rejection."
category = "system-administration"
tags = ["log-analysis", "incident-response", "structured-output"]
capability_target = "causal-log-analysis"

[verifier]
timeout_sec = 120.0

[agent]
timeout_sec = 600.0

[environment]
build_timeout_sec = 600.0

[task] 是包信息:名称必须采用 org/name,并可包含描述、作者和关键词。[metadata] 在 v0.18.0 的 Task 模型中是任意字典;Viewer 会专门读取其中字符串类型的 difficultycategory 和字符串列表 tags 作为筛选项。8 difficulty_explanationcapability_target 是本书的审计约定,不是 Harbor 自动校准出来的结论。更不能在元数据中写 root_cause = "...",因为元数据会随 Task 分发。

instruction.md 替换为下面的完整公开契约:

# 分析服务日志并生成结构化故障报告

`/workspace/input/` 直属目录中的 `.log` 文件包含完成本次诊断所需的全部证据。
这些文件采用 UTF-8 JSON Lines:每行都有 `timestamp``event_id`
`component``level``message`;与请求相关的记录另有 `request_id`
`event_type``outcome`。有直接原因的事件另有 `caused_by_event_id`,其值为
直接原因的 `event_id`;每个 `ERROR``FATAL` 事件另有 `failure_code`
`event_id` 在所有输入文件中唯一。

`timestamp` 仅接受 `YYYY-MM-DDTHH:MM:SS[.fraction]Z``T/Z` 大写,`YYYY`
四位,Gregorian 日期合法,`HH``00``23``MM/SS``00``59`
小数秒省略或含一位以上数字。拒绝数值偏移、空格、缺秒、逗号小数、小写
`t/z` 与 leap second `second = 60`;输入不生成 leap second。

每个 `request_id``request.completed` 按解析后的 UTC 时刻、同刻再按
`event_id` 排序,不比较原始字符串。末项为终态;最终 `failure` 才是目标失败,
最终 `success` 不受影响。

从每个目标失败事件开始反复沿 `caused_by_event_id` 回溯。输入保证引用存在、
图无环,而且所有路径终止于同一个没有 `caused_by_event_id``ERROR`
`FATAL` 事件;这个共同无父事件就是唯一主要根因。输入还保证每个目标事件
都有父事件,任意两个目标事件不存在祖先关系,且所有目标路径的事件并集
来自至少两个文件。

根因类别由根因事件的 `failure_code` 唯一确定:

- `network``dns_failure``route_failure``connection_failure`
`connection_reset``tls_transport_failure`
- `resource``cpu_exhausted``memory_exhausted``disk_exhausted`
`file_descriptor_exhausted``thread_exhausted`
`connection_pool_exhausted``quota_exhausted`
- `configuration``missing_setting``invalid_setting`
`deployment_misconfiguration`
- `dependency``dependency_unavailable``dependency_error`
`invalid_upstream_response`
- `application``unhandled_exception``code_defect`
`data_processing_error`

把报告写到 `/workspace/report.json`。报告必须是单个 JSON 对象,不要使用
Markdown 代码围栏。顶层只能包含 `schema_version``incident_summary`
`root_cause``evidence``impact``recommended_actions`,任何层级均不接受
未知字段。

报告必须包含:

- `schema_version`:字符串 `"1.0"`
- `incident_summary`:非空字符串;
- `root_cause`:只能包含 `event_id``component``category``explanation`
`event_id` 必须等于共同无父事件的 ID;`component` 必须区分大小写并逐字
等于该事件的 `component``category` 按上表映射;`explanation` 为非空字符串;
- `evidence`:恰好包含全部目标路径的事件并集,每个事件一次。每项只能包含
`file``event_id``role``file` 是事件所在输入文件的基本名;共同无父事件的
`role``root`,目标失败事件为 `target-failure`,其余中间事件为
`causal-link`。数组按解析后的时刻、同刻再按 `event_id` 排序;不得缺项、
加项或重复;
- `impact`:只能包含 `affected_request_ids`。该数组必须精确等于所有目标失败
事件中 `request_id` 的去重集合,并按字典序排列;
- `recommended_actions`:1~3 个非空字符串。

只读分析输入,不要修改 `/workspace/input/`,不要启动或修复服务。
无需使用外部信息。

注意:上面的类别枚举、字段名和排序规则是公开接口,不是答案泄露。组件名、正确类别、关键 event_id、受影响请求集合和因果解释才是实例答案。本章不会给出这些值。

注意:instruction 中“无需使用外部信息”描述的是任务语义,不会自动切断网络;网络策略由配置字段而不是自然语言指令实施。9 本章的配置骨架尚未宣称网络受限;真正的阶段网络策略和 Provider 边界留到第 8 章。在那之前,不要把该骨架当成已经抵抗外部查找的评测环境。

最后做一次无 Docker、无模型费用的静态加载。下面从 Task 根目录运行;若本机未缓存包,uvx 会访问包索引。它固定 Python 3.13,与第 3 章的安装策略一致。预期只打印 Task 名、schema 版本和 static design gate: PASS

cd ~/harbor-lab/tasks/log-incident-report
uvx --python 3.13 --from "harbor==0.18.0" python - <<'PY'
from pathlib import Path

from harbor.models.task.task import Task

task = Task(".")
assert task.name == "llmkb/log-incident-report"
assert task.config.schema_version == "1.3"
assert task.config.metadata["capability_target"] == "causal-log-analysis"

instruction = Path("instruction.md").read_text()
for required in (
"/workspace/input/",
"/workspace/report.json",
"event_id",
"caused_by_event_id",
"failure_code",
"YYYY-MM-DDTHH:MM:SS[.fraction]Z",
"leap second",
):
assert required in instruction, f"missing public contract: {required}"
for leaked_interface in ("/solution", "/tests", "reward.txt"):
assert leaked_interface not in instruction, f"grading interface leaked: {leaked_interface}"

print(task.name, task.config.schema_version)
print("static design gate: PASS")
PY

这个检查只能证明目录和 TOML 可被 v0.18.0 加载,且几个公开接口存在;模板测试甚至还没有实现本任务的语义。它不能证明日志可解、答案未藏在镜像里、Verifier 无漏洞或难度合适。把静态加载写成“Task 已通过评测”会混淆设计门禁与运行验收。

4.4 答案泄露与任务捷径

Agent 的高 Reward 只有在“解决任务”是最便宜的可靠路径时才有解释力。Harbor v0.18.0 自带的 Adapter 评审规则把威胁压缩成两个不变量:在整个 Trial 生命周期中,Agent 既不能读取 Ground Truth,也不能直接或间接影响权威通过信号;检查范围包括 instruction、Dockerfile、运行时镜像、测试执行链和 Reward 写入。10

4.4.1 泄露面

逐面检查比只搜索 answer 更可靠:

泄露面本任务的防线
instruction 示例只给结构和枚举,不给正确组件、类别、事件或请求 ID
Task 名称/描述/元数据使用中性 log-incident-report,不编码根因或上游实例 ID
环境文件不放 expected.json、Gold patch、带答案注释的生成脚本或备份
镜像历史不先 COPY 答案再删除;删除后的层仍可能保留内容
Solution不复制进 Agent 镜像,不从 instruction 引用
testsAgent 主调用期间不暴露断言与期望值;全生命周期也不信任 Agent 可写的同名路径
网络/来源标识不暴露可搜索到原题的 URL、Benchmark 名或原始 task ID

在 Task 根目录可先做启发式搜索:

rg -n -i \
'gold|expected|answer|root.?cause|reward\.txt|/tests|/solution|https?://' \
instruction.md task.toml environment

预期不是机械地“零匹配”:root_cause 作为公开字段可以出现。每个匹配都必须人工判断它是接口名、证据,还是已经归纳好的答案;二进制、镜像层和构建缓存还需要后续单独审计。

4.4.2 把防篡改边界扩展到整个 Trial

“Agent 调用结束后才上传测试”只能减少直接读取,不能独自证明评分安全。v0.18.0 的单步 Trial 有两种 Verifier 环境模式:11

模式v0.18.0 的阶段关系设计时必须保留的威胁判断
shared(默认)Agent 运行、日志与 Artifact 收集完成后,Verifier 复用同一个 Agent 环境;测试在执行前上传,环境到验证结束后才停止Agent 主调用通常看不到稍后上传的测试,但它已控制该环境,可能留下后台进程、路径、符号链接、工具或环境状态
separate收集待传 Artifact 后先停止 Agent 环境,再在新的 Verifier 环境中评分;测试构建进评分镜像,不在运行时上传隔离了同一环境中的持续进程与大部分路径污染,但转移的 /logs/artifacts/ 和显式 Artifact 仍完全由 Agent 控制

因此,任务设计记录必须写明选用 shared 还是 separate,并逐项回答下列问题;本章只定义门禁,第 7 章才实现和攻击具体 Verifier:

  • 权威断言、期望值、评分脚本与 Reward 在所有阶段是否都位于 Agent 不可写的可信边界中?Reward 是否由评分流程在每条路径上覆盖写入,而不是接受预存值?
  • shared 模式是否考虑 Agent 退出后仍存活的后台进程,以及预创建 /tests/logs/verifier、父目录或符号链接对后续上传和写入的影响?不能把“Agent 的前台命令已返回”当成环境已净化。
  • Verifier 是否会 sourceexec、导入或以其他方式执行 Agent 可写文件?PATH、解释器、Shell 和辅助工具能否被 Agent 放置的同名程序遮蔽?权威执行链必须来自可信路径。
  • 所有 Agent 产物是否都按敌对输入处理,并在解析前检查预期文件类型、路径、符号链接、大小与格式?即使使用 separate,也不能执行、导入或信任转移进来的 Artifact。

高隔离需求应优先进入 separate 设计,因为它缩小了共享状态和后台进程的攻击面;但“分离”不是“可信”。它只改变执行边界,不改变 Agent 产物的信任级别。反过来,若保留 shared,设计评审必须给出完整执行链的可信来源,而不能只写“tests 在 Agent 阶段不可见”。

4.4.3 不泄露仍可能太容易

关键词捷径。 若某行直接写 root_cause=cache,其余日志只是装饰,Task 测到的是字符串提取。应让症状、依赖错误和上游原因分布在不同文件,并加入真实但无关的高等级日志;正确证据必须构成因果链。

先验猜测。 若 Dataset 中每个事故的答案都是数据库连接问题,完全不读日志也可能得高分。实例化时应平衡根因类别,并让 event_id 与受影响请求集合每题不同;不得用同一静态报告通过多题。

过程伪装。 要求 Agent 输出“我已检查全部日志”不能证明它真的检查过。Verifier 应检查终态事实与引用证据,而不是相信自述或强制某条命令序列。

评分篡改。 把测试放进 /workspace,允许 Agent 预写一个 Verifier 只在缺失时才生成的 Reward,或让评分脚本从 Agent 可写路径加载辅助程序,都会把“影响评分”变成捷径。具体隔离、净化和覆写规则留到第 7 章,但设计评审现在就要标记权威评分材料、执行链与 Agent 可写状态的交集。

捷径也不要定义得过宽。Agent 用一条 jq 管道完成关联,只要结果来自输入事实,就是高效解法;任务不应为了“看起来更 Agentic”强迫它多轮调用工具。需要防的是绕过目标能力的路径,而不是短路径本身。

4.5 难度与元数据是实验假设

本章把 difficulty = "medium" 标为教学性暂定。这不是实测分级,也不是 Harbor 自动推导的属性。难度属于“Task × Agent/Model × 工具与预算”的关系:同一日志题对带 JSON 查询工具的 Agent 和只会基础 Shell 的 Agent 并不等价。

更可靠的难度旋钮直接作用于目标能力:

  • 关联跨度:证据来自一个文件还是多个组件;
  • 因果深度:从目标失败到共同根因需要沿多少层直接原因指针回溯;
  • 干扰强度:有多少语义合理但与目标请求无关的异常;
  • 影响计算:是否涉及重试、重复记录与部分成功;
  • 表达自由度:只选类别,还是还需解释与引用证据。

单纯增加日志体积、使用生僻缩写、压缩超时时间或藏起格式说明,通常增加的是扫描成本、领域猜测或运气。它们会让任务更难,却不一定让目标能力的测量更有效。

正式标级前应做三类试跑:Oracle 证明至少一条路径可解;人工按公开 instruction 独立求解并记录分歧;多个目标 Agent 重复运行,观察成功、失败和错误类型。若所有 Agent 都稳定满分,应先检查捷径再考虑增加因果深度;若全部失败,应先检查隐藏假设、不可解输入和预算,而不是立即标成 hard

元数据应支持检索和分层,而不是存答案。本书约定:keywords 放稳定的领域和形式词;category 取一个主类;tags 表达可多选的技能或工件;capability_target 用于审计“这题声称测什么”;difficulty_explanation 记录分级依据。团队还应维护受控词表,避免 log-analysislogsobservability 被随意当成同一标签。

4.6 一个会误判 Agent 的失败设计

考虑下面这条 instruction:

分析 /logs 下的文件,找到问题并写一份详细报告。报告要正确、完整、专业。

假设隐藏测试却要求 /workspace/report.json,要求字段 cause_type 等于某个枚举,并按请求 ID 排序。这个 Task 同时有四个缺陷:输入路径与真实路径不一致;输出位置和结构未公开;“详细、专业”没有操作定义;测试接受的类别词表只存在于评分侧。即使 Agent 找到正确根因,也可能因为写成 Markdown 或使用同义词得 0 分。

另一个常见“修复”是把完整期望 JSON 放进 /workspace/example-report.json,让 Agent 照格式写。这消除了格式猜测,却把正确组件、证据 ID 和影响集合一并泄露。正确做法是公开 schema、类型、枚举和排序规则,只把本实例的事实值保留在不可见的评分数据中。

还有一种反例看似严谨:日志最后一行写着 FATAL root cause: dependency unavailable,Verifier 只检查报告中含 dependency。这会稳定通过,却没有测跨文件关联。应把验收追溯回能力表:至少检查正确组件、类别、跨文件证据 ID 和受影响请求集合,才能排除只抄一行的策略。

4.7 进入实现前的验收清单

下面是本章的设计门禁。先执行静态命令,再由一名没有看过 Gold 的评审者只阅读 Agent 可见材料。任一“否”都应回到设计,而不是靠更复杂的 Verifier 掩盖。

能力与契约

  • 能力目标用“给定输入,产生可观察结果”的句式表达,而不是复述业务故事;
  • 每个核心评分项都能映射到目标能力,没有仅因“方便测试”而加入的要求;
  • instruction 明确输入范围、编码、字段语义、时区、输出路径、格式和完成条件;
  • timestamp 语法已公开,环境和 Verifier 使用同一子集;
  • 测试将检查的字段、类型、枚举、排序和去重规则都已公开;
  • 根因事件、组件、类别、受影响集合和证据并集都能从公开字段唯一计算;
  • 没有把特定工具、命令顺序或自述过程误作正确性条件;
  • 不评价的能力已经列为非目标,例如修复服务、脱敏和建议质量。

边界与可解性

  • 因果引用存在且无环,所有目标路径收敛到唯一无父事件,并跨越至少两份文件;
  • 时间按解析时刻、同刻再按 event_id 比较;其余边界处理已明确;
  • “证据不足”“多个同等根因”等不在范围内的实例不会混入首版 Dataset;
  • 独立评审者仅凭公开材料能描述同一完成状态,并无法提出第二个同样合理的 Gold;
  • 环境缺文件、编码错误等构建问题不会被当成 Agent 能力失败。

泄露、捷径与难度

  • instruction、名称、元数据、URL、文件名、注释和构建产物不暴露实例答案;
  • Agent 在整个 Trial 生命周期中看不到 Solution、Ground Truth 与期望输出,也不能直接或间接控制权威通过信号;
  • 设计记录明确选择 sharedseparate,并按该模式审计完整执行链;
  • shared 模式已覆盖后台进程、预建评分路径、符号链接、PATH/解释器遮蔽和 Agent 可写代码执行;
  • separate 模式仍把所有转移 Artifact 视为敌对数据,不执行、不导入、不直接信任;
  • 静态关键词、固定类别先验或同一报告不能跨实例取巧;
  • 最短合法解仍必须读取并关联任务证据;
  • 难度来自关联、因果、干扰和影响计算,而不是纯体积、隐含规则或苛刻超时;
  • medium 明确标为待试跑校准的假设,没有伪造成功率或人工耗时。

v0.18.0 静态验收

  • harbor --version 精确输出 0.18.0
  • Task(".") 能加载目录,schema_version1.3
  • task.name 为合法 org/name,描述、关键词和作者不含 Gold;
  • [metadata]difficultycategorytags 类型适合 Viewer 筛选;
  • 启发式泄露搜索的每个匹配均已人工判读;
  • 评审记录明确写着“只通过设计/静态门禁,尚未完成环境、Oracle 和 Verifier 验收”。

4.8 本章小结

  • 可评测 Task 从能力目标开始;服务名、文件名和故事只是场景包装。
  • instruction 是公开接口:输入、输出、完成条件和边界规则必须与后续测试一致。
  • 首个教学 Task 用受控因果指针和故障代码换取唯一判定;这不代表真实日志分析具有相同的开放世界语义。
  • 公开 schema 不等于泄露答案;实例的正确事实、Gold 和评分实现必须隔离。
  • 防评分篡改必须覆盖整个 Trial;separate 能缩小共享状态风险,却不会让 Agent Artifact 自动可信。
  • 任务捷径包括关键词提取、类别先验、评分篡改和可搜索的来源标识;高效合法工具使用不是捷径。
  • 难度标签是等待试跑验证的实验假设,应由目标能力的认知操作决定。
  • harbor task initTask(".") 只能完成结构和静态加载验收,不能证明任务可解或评分可靠。

4.9 练习

  1. 把“分析日志并写报告”改写成一条可证伪的能力声明,另列出三个场景包装变量和三个控制变量;解释为何日志行数不是能力本身。
  2. 为本章输出契约增加 timeline 字段。定义元素类型、排序、重复事件和时区规则,但不要给出任何本实例的正确事件 ID。
  3. 设计一份含两个看似合理根因的教学性日志概要。说明它为什么会使现有 Task 不公平,并给出两种修订方式:修改输入,或扩展输出契约。
  4. 对一个现有 Task 执行本章的泄露搜索与人工评审,分别找出一个真实泄露、一个误报和一个 rg 无法发现的镜像层风险。
  5. 设计 easymediumhard 三个日志实例,只改变关联跨度、因果深度和干扰强度;写出校准计划,不预先虚构任何 Agent 成功率。

参考资料

Footnotes

  1. Harbor Framework Team,TaskTaskPaths,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/task.py#L35-L145https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/paths.py#L11-L138,访问于 2026-07-16。

  2. Harbor Framework Team,create-task Skill,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/skills/create-task/SKILL.md#L42-L58,访问于 2026-07-16。

  3. G. Klyne、C. Newman,RFC 3339《Date and Time on the Internet: Timestamps》§5.6–5.7,RFC Editor,https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6https://www.rfc-editor.org/rfc/rfc3339.html#section-5.7,访问于 2026-07-16;本 Task 明示采用更窄子集。

  4. Harbor Framework Team,analyze-rubric.toml,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/analyze/prompts/analyze-rubric.toml#L1-L9,访问于 2026-07-16。

  5. Harbor Framework Team,harbor task init CLI、初始化实现、Task Tutorial 与初始化测试,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/tasks.py#L50-L131https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/init.py#L95-L163https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/tasks/task-tutorial.mdx#L18-L75https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/tests/unit/cli/test_init.py#L40-L50,访问于 2026-07-16。

  6. Harbor Framework Team,simple-task,Harbor Cookbook 提交 e093c9a860b988d9d74901010ddddb9c7f124f92https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/task.toml#L1https://github.com/harbor-framework/harbor-cookbook/tree/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task,访问于 2026-07-16。该独立仓库样例未随 Harbor v0.18.0 标签锁定,本章只用作结构参考。

  7. Harbor Framework Team,TaskConfig,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/config.py#L790-L822,访问于 2026-07-16。

  8. Harbor Framework Team,PackageInfoTaskConfig 与 Viewer Task 筛选实现,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/config.py#L278-L317https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/config.py#L790-L815https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/viewer/server.py#L462-L540,访问于 2026-07-16。

  9. Harbor Framework Team,Task Structure 的 Network policy,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/tasks/index.mdx#L132-L145,访问于 2026-07-16。

  10. Harbor Framework Team,Adapter Review 的 Benchmark vulnerability check,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/adapter_review.py#L614-L654,访问于 2026-07-16。

  11. Harbor Framework Team,单步 Trial 顺序、共享/分离 Verifier 实现、测试上传流程与 Task 文档,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/trial/single_step.py#L38-L55https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/trial/trial.py#L498-L581https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/verifier/verifier.py#L138-L202https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/tasks/index.mdx#L495-L590,访问于 2026-07-16。