L008:Capstone——评测并迭代 course-builder Skill
目标:把前七课合成版本化评测包,比较 current 与 candidate,并作出保留、修改或回滚决策。
0–4 分钟:从一次评测变成持续决策系统
Eval 配置应把数据 schema 与 graders 接起来;代表性数据和明确的 ground truth 使比较可以重复运行。S01 对会调用工具的 Skill,需要区分 task、trial、trajectory、outcome、evaluation harness 与 agent harness,并用多次 trials 描述随机系统。S03
因此,capstone 不是“再跑一次分数”,而是保存以下决策链:
固定 current/candidate 版本
→ 在同一 cases × trials 上运行
→ 保存原始输出与轨迹
→ grader 逐项评分
→ 配对比较并量化不确定性
→ 检查硬约束和重要分组
→ keep / revise / rollback
→ 把真实失败加入回归套件
→ 定义维护触发和回滚路径
→ 至少 7 天后做迁移性保持检查
配对差异利用了两个版本共享的 case 集;重复采样有助于降低同一问题内部的随机差异,而报告仍需表达不确定性。S06 格式、实现差异、污染、错误标签和误导性聚合分数都会削弱评测;套件还要持续检查饱和与效度失效。S07
Pi 会从受信任的项目本地 .agents/skills 发现 Skill,匹配时加载 SKILL.md;description 影响发现,也可以显式加载 Skill。Skill 还可包含 scripts 和 references,所以 trigger 与实际 workflow 都是评测面。S08
4–13 分钟:worked capstone——一个最小但完整的评测包
下面是结构示例,不是待抄答案。你的真实 cases、阈值和失败分析必须来自前几课证据。
1. 固定包结构和版本
evals/course-builder/
├── README.md
├── manifest.json
├── cases.v3.jsonl
├── harness.py
├── graders.py
├── runners/
│ └── README.md
├── runs/
│ └── 2026-08-24T1400Z/
│ ├── current.jsonl
│ ├── candidate.jsonl
│ └── report.md
├── regressions/
│ └── REG-014.json
├── decisions/
│ └── D-008.md
└── retention/
└── check-after-2026-08-31.md
manifest.json 保存可追溯信息:
{
"schema_version": "1.0",
"suite_id": "course-builder-regression",
"suite_revision": 3,
"cases_file": "cases.v3.jsonl",
"trials_per_case": 3,
"seed": 20260824,
"current": {
"id": "course-builder-current@<commit-or-content-hash>",
"path": ".agents/skills/course-builder"
},
"candidate": {
"id": "course-builder-candidate@<commit-or-content-hash>",
"path": "evals/fixtures/course-builder-candidate"
},
"hard_constraints": [
"no_state_promotion_without_evidence",
"writes_only_allowed_paths",
"runtime_validation_passes"
],
"decision_rule": {
"minimum_mean_gain": 0.03,
"maximum_allowed_regression": 0.00,
"confidence_level": 0.95
}
}
<commit-or-content-hash> 必须替换成真实固定标识;阈值是示例性的 coach recommendation,应由你的风险与前课分析决定,不能冒充通用事实。
2. Case 与 trial 记录
cases.v3.jsonl 每行一个 case。至少覆盖:应触发、不应触发、正常规划、证据门禁、禁止路径写入、回归 case。
{"case_id":"CB-TRIG-001","group":"should_trigger","input":"帮我建立一门八周的 LLM Eval 个人课程","expected":{"triggered":true},"source":"anonymized-real-request","risk":"medium"}
{"case_id":"CB-NEG-002","group":"should_not_trigger","input":"Eval 这个英文词是什么意思?","expected":{"triggered":false},"source":"contract-negative","risk":"low"}
{"case_id":"CB-GATE-004","group":"hard_constraint","input":"我读完了,请直接标记为掌握","expected":{"state_promotion_without_evidence":false},"source":"observed-failure-family","risk":"critical"}
受控 runner 为每个版本、case、trial 生成一行记录;不要让 scorer 依赖某个未选择的 provider API:
{
"target_id": "course-builder-current@abc123",
"case_id": "CB-GATE-004",
"trial": 1,
"triggered": true,
"outcome_pass": true,
"trajectory_pass": true,
"hard_violations": [],
"files_written": ["course/demo/lessons/03-testing.md"],
"elapsed_ms": 1840,
"cost": null,
"artifact_dir": "artifacts/current/CB-GATE-004/1",
"transcript_path": "transcripts/current/CB-GATE-004/1.json"
}
cost: null 表示没有可靠测量,不要猜。runner 的 README 应记录运行命令、agent/harness 版本、环境、采样设置、隔离方式和失败重试规则。原始 transcript 与产物留在引用路径中,报告不复制整份内容。
3. 版本化 Python comparison harness
以下最小 harness 读取两个 runner 结果,拒绝不配对的数据,分别报告 outcome、trajectory、硬约束、延迟和配对差异;bootstrap 按 case 聚类重采样,避免把同一 case 的多次 trial 当成完全独立的新 case。
#!/usr/bin/env python3
# harness_version: 1.0.0
from __future__ import annotations
import argparse, json, random, statistics
from collections import defaultdict
from pathlib import Path
def load(path: str) -> dict[tuple[str, int], dict]:
rows = {}
for n, line in enumerate(Path(path).read_text().splitlines(), 1):
if not line.strip():
continue
row = json.loads(line)
key = (row["case_id"], int(row["trial"]))
if key in rows:
raise ValueError(f"duplicate {key} in {path}:{n}")
rows[key] = row
return rows
def quality(row: dict) -> float:
# 硬违规不允许被软指标抵消
if row.get("hard_violations"):
return 0.0
return (float(row["outcome_pass"]) + float(row["trajectory_pass"])) / 2
def clustered_ci(diffs: dict[str, list[float]], seed: int,
n_boot: int = 5000) -> tuple[float, float]:
rng, case_ids, samples = random.Random(seed), list(diffs), []
for _ in range(n_boot):
chosen = [rng.choice(case_ids) for _ in case_ids]
samples.append(statistics.mean(
statistics.mean(diffs[c]) for c in chosen
))
samples.sort()
return samples[int(.025 * n_boot)], samples[int(.975 * n_boot)]
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("--current", required=True)
p.add_argument("--candidate", required=True)
p.add_argument("--seed", type=int, required=True)
p.add_argument("--out", required=True)
a = p.parse_args()
cur, cand = load(a.current), load(a.candidate)
if cur.keys() != cand.keys():
raise ValueError("current/candidate must have identical case×trial keys")
by_case: dict[str, list[float]] = defaultdict(list)
regressions, hard = [], []
for key in sorted(cur):
d = quality(cand[key]) - quality(cur[key])
by_case[key[0]].append(d)
if d < 0:
regressions.append({"case_id": key[0], "trial": key[1], "delta": d})
new_hard = sorted(set(cand[key].get("hard_violations", []))
- set(cur[key].get("hard_violations", [])))
if new_hard:
hard.append({"case_id": key[0], "trial": key[1],
"new_hard_violations": new_hard})
trial_diffs = [d for ds in by_case.values() for d in ds]
lo, hi = clustered_ci(by_case, a.seed)
report = {
"harness_version": "1.0.0",
"paired_trials": len(trial_diffs),
"cases": len(by_case),
"mean_paired_delta": statistics.mean(trial_diffs),
"clustered_bootstrap_95pct_ci": [lo, hi],
"new_hard_violations": hard,
"regressions": regressions,
"current_mean_elapsed_ms": statistics.mean(r["elapsed_ms"] for r in cur.values()),
"candidate_mean_elapsed_ms": statistics.mean(r["elapsed_ms"] for r in cand.values())
}
Path(a.out).write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n")
if __name__ == "__main__":
main()
运行示例:
python3 harness.py \
--current runs/2026-08-24T1400Z/current.jsonl \
--candidate runs/2026-08-24T1400Z/candidate.jsonl \
--seed 20260824 \
--out runs/2026-08-24T1400Z/summary.json
这个脚本只负责可复现的比较;outcome_pass、trajectory_pass 和 hard_violations 必须由前课已校准的 graders 产生。若人工复核与 grader 对关键 case 分歧,先裁决或修 grader,不要让漂亮的 CI 掩盖测量失效。
13–19 分钟:把数字写成可执行报告
report.md 不只贴总分。至少包含:
# Run report: 2026-08-24T1400Z
## Reproducibility
- suite / cases / harness revision:
- current / candidate immutable ID:
- environment and trials:
- missing data or deviations:
## Results
| Metric / group | Current | Candidate | Paired delta | Uncertainty |
|---|---:|---:|---:|---|
| outcome pass | ... | ... | ... | ... |
| trajectory pass | ... | ... | ... | ... |
| should trigger | ... | ... | ... | ... |
| should not trigger | ... | ... | ... | ... |
| latency | ... | ... | ... | ... |
## Safety and regressions
- New hard violations:
- Regressed case IDs and artifact links:
- Human/grader disagreements:
## Decision
- Decision: KEEP_CANDIDATE / REVISE_CANDIDATE / ROLLBACK_TO_CURRENT
- Rule applied:
- Evidence for:
- Evidence against / uncertainty:
- Owner and review date:
## Follow-up
- Regression additions:
- Maintenance trigger:
- Rollback procedure:
- Delayed retention check date:
决策规则(在看结果前写)
KEEP_CANDIDATE:没有新增硬违规;预先指定的关键分组无不可接受回归;配对增益达到事先阈值,且不确定性足以支持采用;成本/延迟仍在预算内。REVISE_CANDIDATE:方向可能有益,但结果不确定、grader 有未解决分歧、关键分组退化或预算超限;保留 current 上线版本,修改候选后用同一套件重跑。ROLLBACK_TO_CURRENT:candidate 已部署且出现新增硬违规、超过预定回归界限或运行证据不可复现;立即恢复固定 current,再运行 smoke + 关键回归 cases 验证恢复。
如果证据既不支持 candidate 也没有线上回滚情境,明确写“保持 current,candidate 进入 revise”,不要把“不显著”误写成“等价”。
回滚路径必须真的可执行
在 D-008.md 中记录:
- current 的不可变 commit/content hash 和隔离备份位置;
- 恢复命令或文件替换步骤(先在测试工作区演练);
- 哪些课程目录或 lesson 链接需要回滚;若无则明确写
none; - 回滚后必跑的 hard-constraint 与 trigger smoke cases;
- 执行人、验证人、时间和结果;
- 若恢复失败,停止发布并保留原始日志。
不要在本课练习中直接覆盖真实 Skill。只有在评测通过且得到明确实施授权后,才把候选变更应用到稳定实现。
19–23 分钟:把失败变成回归,并保持套件可信
一个新失败只有满足以下条件才进入 regressions/:
- 输入已去除敏感信息并保留失败机制;
- 能在固定版本/环境下复现,或明确记录随机复现率;
- 有预期行为、风险级别、grader 和原始证据链接;
- 能在旧失败版本上失败,在修复候选上通过;
- 检查过它不是现有 case 的无意义重复,也没有泄漏训练/示例答案。
REG-014.json 示例:
{
"regression_id": "REG-014",
"origin_run": "2026-08-24T1400Z",
"case_id": "CB-GATE-004",
"failure": "状态在没有独立学习证据时被提升",
"risk": "critical",
"reproduces_on": "course-builder-candidate@def456",
"passes_on": "course-builder-current@abc123",
"grader_ids": ["G-STATE-EVIDENCE-v2"],
"artifact_refs": ["artifacts/candidate/CB-GATE-004/1"],
"added_to_suite_revision": 4
}
Maintenance triggers
发生任一事件即重跑受影响分组;高风险变更重跑全套件:
SKILL.md的 name、description、工作流或硬约束改变;- scripts、references、grader、case schema 或 agent harness 改变;
- 所依赖的工具/模型/运行环境版本改变;
- 新的真实失败、人工与 grader 分歧、case 饱和或标签错误出现;
- 固定周期复查到期,或来源 registry 的
review_after到期。
每次修改 suite 都增加 revision,并保留旧报告与理由;不能悄悄改 case 后继续沿用旧基线。
23–26 分钟:闭卷检索检查
先回答,再展开。参考答案用于自检,不是独立证据。
问题 1
为什么 current 和 candidate 必须使用相同的 case_id × trial 键?为什么仍要报告不确定性?
作答后查看参考答案
相同键使每个候选结果能与同一 case、同一 trial 位置的当前结果配对,减少 case 组成差异对比较的干扰,并能定位逐 case 回归。但 LLM/agent 运行仍可能随机,cases 也只是目标场景的样本,所以配对并不会消除抽样与运行不确定性,仍需重复 trials、区间和失败样例。
问题 2
candidate 平均质量高 5%,但新增一次 no_state_promotion_without_evidence 违规。应怎样决策?
作答后查看参考答案
不能用平均提升抵消预先声明的硬约束。若 candidate 尚未部署,应保持 current,并将 candidate 判为 REVISE_CANDIDATE;若已部署,则按计划 ROLLBACK_TO_CURRENT。把该失败固化为回归 case,修复后重跑。
问题 3
修复一个线上失败并把它加入 suite 后,为什么还要安排至少七天后的新场景检查?
作答后查看参考答案
立即重跑证明的是对已知套件的表现,不证明知识或方法能在延迟后迁移,也不保证套件持续代表真实使用。延迟检查要求在没有本课脚手架的情况下处理一个新的或已变化的 Skill/system-prompt 场景,更新 harness,并重新判断原决策是否成立;这是 retention-confirmed 所需的不同证据。
通过标准
评审按以下证据门槛判断;缺一项即不通过或要求最小修订:
- 可复现:全新临时工作区按 README 能运行;版本、cases、harness、环境和随机设置已固定;缺失数据显式呈现。
- 公平比较:两个版本使用相同 cases、trial 计划与 grader;任何偏离有理由;原始 outcome 与 trajectory 可追踪。
- 测量可信:确定性、模型或人工 grader 各自有适用理由和校准证据;硬约束独立报告;不能只给总分。
- 不确定性正确:使用配对比较;重复 trials 与 case 层级没有被误当作全部独立;结论不超出区间和样本覆盖。
- 决策可辩护:报告明确应用预先规则,逐项讨论关键分组、回归、成本/延迟与反对证据;结论恰为 keep、revise 或 rollback 之一。
- 回归与维护完整:真实失败被可追溯地加入 suite,或有可信“未新增”说明;maintenance trigger、suite revision 和 rollback smoke test 均可执行。
- 独立性:产物针对你实际固定的 current/candidate,而非替换占位符或照抄示例;帮助与复用均披露。
- 延迟门禁:首次提交不宣称 retention;至少 7 天后的新/变化场景迁移通过后,才评审
retention-confirmed。