# Harness² 融合说明 状态:方法层(`cloud/harness2.py` + `cloud/tests/test_harness2.py` + `examples/harness2_demo.py`)与接线层(`cloud/harness2_tasks.py` + `cloud/tests/test_harness2_tasks.py` + `examples/harness2_run.py`) 均已落地,在独立分支 `feat-harness2`(已 rebase 到生产 #133 的 c757810)上,不碰主线与生产,**未合并、未部署**。控制台评审页 与 `auto_apply` 开关仍是下一步,见文末。 ## 上游是什么 Google Research 的 harness2(Apache 2.0,arXiv:2610.14224):模型权重 不动,只优化模型周围的 harness——系统提示、guardrails、记忆、工具 指引、子代理、技能、脚本、插件。两层递归: - 任务层:Propose–Probe–Compose。对同一任务提 INV(通用做法)与 DOM (领域做法)两类候选 harness,每个候选真实跑一次(探针),合成器 只把有行为证据支撑的改动合成终版;没有标准答案,靠行为对比。 - 经验层:采纳后反思出一条有证据的经验(cue / lesson / evidence), 连同被采纳 harness 的完整树存进分领域经验库,供后续任务参考。 上游报告:单步改进在三个专业 benchmark 上最高 +11.5 / +4.5 / +11.8 个点;k=5 并行递归在 WorkBuddy 上到 80.8。这些数字是上游 benchmark 上的,不是本项目的承诺——本项目没有 benchmark,接线后 必须先在自己的重复任务集上跑 base 对照,才能谈收益。 ## 融合映射:照搬 / 改写 / 不引 | 上游东西 | 本项目做法 | |---|---| | c1–c8 可编辑树(codex surface) | 照搬:AGENTS.md(8000 字符上限)、`.agents/skills/*/SKILL.md`、`.agents/skills/*/scripts/*`、`sub_agents/*/AGENT.md`、`scripts/*.py`、`.codex/hooks.json`;白名单外一律不读不写;校验含技能 description、触发行、脚本 compile、脚本引用存在、钩子 JSON 与事件名 | | Propose–Probe–Compose,parallel / sequential | 照搬语义,改写实现:`run_parallel` / `run_sequential`,提议被拒给一次按反馈修正的机会,再拒回落到当前 harness;探针失败记为死探针,不炸整轮 | | 匿名选择(只比产物) | 照搬:终版产物按任务+k+模式定种子洗牌匿名化,选择器看不到 harness 来源;选失败回落到已交付终版;k=1 无选择器时 trivial 直出 | | 经验库(分领域、整树 + EXPERIENCE.md + INDEX.md) | 照搬存储格式,改写生效语义:入库 ≠ 自动生效(见下) | | Ledger(会话/探针账本)与成本 | 改写:成本汇总进 `ModeResult.total_cost_usd`,由调用方接现有 `cloud/cost.py` / 租户配额计费;另有 `budget_usd` 超支门:超支后不再调用编辑器/探针,剩余阶段回落,且该轮不入库 | | 提示词 | 改写短版(中文项目语境下的英文任务提示,结构同上游,措辞自写),不抄上游长提示 | | Benchmark 适配器(JobBench / LAB / WorkBuddy)、评分器、rubric | 不引。任务就是一个普通 codex 任务,delivered 由 adapter 定义(如产物存在且非空) | | Proposer 通道(Claude CLI / OpenCode)、Vertex 配置 | 不引。编辑器/选择器/反思器是调用方注入的协议,真实接线时是 codex 任务(见下) | | 上游执行默认:网络不受限、Codex sandbox 禁用 | 明确不引,见安全一节 | | 上游 ThreadPool 并行探针 | 不引。核心循环串行跑探针;并行是调用方在 adapter 层把每个探针发成独立 codex 任务来实现,避免核心循环抢同一个 Worker/DB | ## 安全偏差(刻意与上游不同,勿“纠回”上游) 1. **执行面与本模块解耦。** `cloud/harness2.py` 不跑任何命令、不拼 任何 codex exec 参数。真实 adapter 必须走 Worker 已有的 Docker / gVisor 沙箱、租户隔离与 egress 白名单,绝不加 `--dangerously-bypass-approvals-and-sandbox`。上游 README 明写其 Codex 执行是 sandbox 禁用、网络不受限——那是 benchmark 实验的 默认,不是生产服务的默认。 2. **采纳 ≠ 自动铺开。** 多租户下,一个编辑器写出的 AGENTS.md 若 自动改写别人的任务,是质量与越权双重风险。所以 `improve_task` 的 `auto_apply` 默认关:库只是“待评审的经验”,提议器可读, 但任何任务的 base 永远是项目自己的 base harness;只有项目显式 开启 auto_apply 后,调用方才在下一轮的 `base_harness()` 里取库 中最新采纳树。这个开关的服务端接线(项目设置 + 控制台评审页) 是下一步。 3. **预算是硬门。** k 硬上限 3;parallel 一轮最多 10 次执行 (1 base + 2k 候选 + k 终版),sequential 同样最多 10 次。 `budget_usd` 超支后不再花钱,且该轮不入库——超支轮次的“经验” 是不完整证据,不配进库。 4. **编辑器写出的东西先校验后采纳。** 校验失败的提议给一次修正 机会,再失败就回落,绝不把坏树(空 AGENTS.md、坏钩子、不编译 的脚本、无触发行的技能)铺到探针里。 ## 接口(调用方视角) ```python from cloud.harness2 import improve_task, Library from pathlib import Path result = improve_task(adapter, editor, selector=selector, reflector=reflector, work_dir=Path("runs/h2-task-1"), k=1, mode="parallel", library=Library(Path("runs/library"), domain="accounting"), budget_usd=5.0) result.to_dict() # 阶段、回落、选择、经验、入库位置、总成本 ``` - `TaskAdapter`:`task_id / domain / prompt` 属性;`base_harness()`; `execute(label, harness) -> Rollout`(失败也返回 Rollout,不抛); 可选 `validate_tree()` 做项目特有校验。 - `Editor.edit(mission, context_dir, label) -> EditResult`:读 `context_dir` 下的 `task.md / rollouts / deliverables / harness / library`,改 `context/harness/`,返回改完的树(`tree=None` 时 框架自动从 `context/harness/` 读回——真实 codex 任务接线就用这条 路);`notes` 是编辑器最终消息里 `` 块的解析结果,由调用 方在构造 `EditResult` 时填好。 - `Selector.select(mission, context_dir) -> (id, why, cost)`:读 `context_dir/candidates/.md`(匿名产物),返回 choice id。 - `Reflector.reflect(mission, context_dir) -> dict`:返回含 cue / lesson / evidence(可含 confidence / edits)的经验,三项不全 返回 `{}` 即表示无可入库经验。 证据上下文由 `build_context` 生成:证据以文件形式给模型,prompt 只写指针,不把轨迹截断后塞进 prompt(截断过的轨迹会编造缺陷, 这是上游明确反对的做法)。 ## 接线层(已落地,`cloud/harness2_tasks.py`) 只用控制面已有的 API,不新增任何服务端端点:`POST /uploads` → `POST /tasks`(`upload_id` 绑定 + `idempotency_key` 幂等)→ `GET /tasks/{id}` 轮询终态 → `GET /tasks/{id}/trace` 读轨迹 → `GET /tasks/{id}/cost` 读 `usd_micros` → 超时 `POST /tasks/{id}/cancel`。 认证是 `X-API-Key` 请求头;密钥只由调用方持有,接线层不读、不存、 不打印密钥。 - **探针**:bundle = 源文件 + harness 树,Worker 派发时经 `host.py --seed-dir` 拷进 worktree,harness 文件覆盖仓库同名文件 ——同一任务在不同 harness 下跑。prompt = 原任务 prompt + 交付脚注。 - **结果读取**:控制面返回的 `result_text` 是一层 JSON 信封 (task / thread_id / cwd / status / text / usage),agent 的最终 回复在 `text` 字段里。`run_task` 是四路共用的 choke point, 在此用 `unwrap_result_text` 拆信封;直接拿信封当回复会让 行首锚定的文件块解析永远看不见块(2026-10-29 三轮真实轮次 的真根因:编辑明明发了块,却被判成无改动/格式错误)。 - **编辑**:bundle = `context/` 前缀的证据树;agent 在 worktree 的 `context/harness/` 里改文件,最终回复用文件块(`=== HARNESS FILE: 路径 ===` … `=== END HARNESS FILE ===`)只输出改动文件,没改的 省略即继承,外加 `` 块;接线层解析后落到 fallback 树上再 走核心校验。格式契约(2026-10-29 两轮真实轮次后收紧):只在 `` 里声称改动、却不发文件块,不再算“无需改动”——无改动 必须用显式 `理由` 标记;文件块与标记同 时出现、路径越界、块未闭合都算格式错误,接线层用独立幂等键 带反馈重试一次(`.fmt2`),两次都错才如实回落,绝不静默采纳。 - **选择 / 反思**:同款 bundle + 最终回复解析(` ```choice ` + ``;`` + ``),不读 worktree 文件。 - **幂等**:`idempotency_key = h2-{run_tag}-{role}-{label}`,同一 run_tag 重跑复用已有任务(断点续跑);想从头重跑必须换 run_tag, 换了 harness 内容却沿用旧 run_tag 时旧结果会被复用、白跑一轮。 - **成本与审批**:接线层不拼任何 codex 参数、不改任务默认值; model / effort / approval / environment_id / executor 全由调用方 透传,缺省走 Worker 默认值;任务走与普通任务完全相同的执行器、 沙箱、租户隔离、配额门与审计;单任务超时默认 3600 秒,超时先 取消再记失败/回落;长任务走 #133 的自动续跑。 - **接线层最大的适用边界**:探针交付物 = 最终回复文本。文件型 产物(代码 diff、报告文件)目前只能靠 agent 在最终回复里复述/ 摘要:worktree 快照 API 只给 diff 截断(20000 字)与未跟踪文件 列表、不给完整文件内容,跨主机还读不到。文件产物评审需要新增 文件读取端点,另行评审,不在本层假装已解决。 用真实任务跑一轮(先 dry-run 看计划,确认后加 `--yes`): ```bash export CODEX_API_KEY='...' # 密钥只走环境变量,不接受命令行参数 python3 examples/harness2_run.py --base-url http://127.0.0.1:8765 \ --project-id <测试项目> --prompt-file task.md \ --base-harness-dir ./my-base-harness --source-dir ./task-source \ --mode parallel --k 1 --budget 5 --output runs/h2-real-1 # 计划无误后,加 --yes 重跑;同一 run_tag 可断点续跑 ``` ## 仍未做的事(需另行评审) 1. **人工关口**:采纳的 harness 先在控制台以“待评审经验”展示, 项目负责人点“采纳为 base”后才影响后续任务;`auto_apply` 开关 默认关,开启需项目负责人显式操作并记审计。目前 `run_improvement_via_tasks` 的 `auto_apply` 恒为 False,库只是 待评审经验。 2. **文件产物评审**:新增读取已终态任务 worktree 文件的端点(权限、 跨主机、大小上限都要定),或让探针任务把产物文件内容写进最终 回复的约定成为正式契约——前者是服务端改动,后者是提示词契约, 各有利弊,需评审。 3. **效果验证**:先在 3–5 个重复性任务(如固定格式的对账、周报 生成)上跑 base 对照:同一任务、同一模型,base 产物 vs 改进后 产物由人工或固定检查脚本判定,记录执行次数与成本;没有对照 就不宣称收益。 ## 演示与测试(无模型、无网络) ```bash python3 examples/harness2_demo.py --mode parallel # 应输出 Total: 85.15 并入库 python3 examples/harness2_demo.py --mode sequential python3 -m unittest discover -s cloud/tests -p "test_harness2*.py" -v # 25 项 ``` 接线层单测用进程内假客户端(探针真跑 bundle 里的脚本、编辑按 文件块格式回改动)跑通完整轮次,并对本地假服务端实测了 HTTP 客户端(建任务、轮询、成本、轨迹、密钥头、multipart 与服务端 解析器往返)。真实任务链路尚未实测:第一次真实运行请用测试项目, 并先跑 `examples/harness2_run.py` 不带 `--yes` 的计划模式。 演示账本 100.25 / -20.10 / 5.00:base 脚本只加正数得 105.25, 改进后带符号和为 85.15;演示用的编辑器/选择器/反思器是确定性的, 只为证明循环本身,真实收益必须用第 4 步的对照来量。