Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# AGENTS.md — Tandem 开发纪律

本文件是协作与代码合入的硬约束。与重构计划(`../TANDEM_REFACTOR_PLAN_CN.md`)冲突时,先改计划再改本文件。
本文件是协作与代码合入的硬约束。此前那份仓库外的重构总计划已删除,
改为 **human in the loop 的开发与重构**:改动前先与维护者确认形态与取舍,
范围与命名以本文件 + `docs/CLUSTER_RUNTIME.md` 为准。

## 仓库定位

主体是 **Tandem harness**(快慢双模 CUA,`src/tandem/`);`src/tandem_eval/` 仅为适配集群 rjob 的测评薄适配层(worker 侧 6 文件 + 开发机侧 rjob.py 生命周期客户端)。
主体是 **Tandem harness**(快慢双模 CUA,`src/tandem/`);`src/tandem_eval/` 仅为适配集群 rjob 的测评薄适配层(worker 侧 6 文件 + 开发机侧 rjob.py / rjob_spec.py / rjob_status.py / config_schema.py)。

## 五条硬纪律(CI/评审强制)

Expand All @@ -24,9 +26,15 @@
## 配置纪律

- `infra/config.json` 是**唯一**配置文件(含密钥 → gitignore;模板 `config.example.json` 提交)。
- **三段结构**:`harness`(慢模型/快模型/路由/预算)、`eval`(集群与基准)、`log`(结果落盘)。
- **按角色命名,不按厂商命名**:大脑 = `harness.slow_model`,小脑 = `harness.fast_model`;
厂商(kimi / jev / kev)只作为条目内部的字段出现。
- 慢模型与快模型都是**命名 API 登记表**(`apis`):登记多套 API,启动命令用
`--slow-model` / `--fast-model` 按名字挑(缺省取 `default`),用于比较"不同大脑 × 不同小脑"。
- 所有值为绝对路径;开发机与 worker 内一致(adpc 和 evobox 两卷挂载点两侧相同)。
- 排序服务 endpoint(JEV/Kev)留空 = 自动从 `services/qwen_jev.json` / `kev.json` 发现文件读取最新地址(worker 侧建 session 时解析)。
- LLM 凭据(kimi 三件套)和 docker registry auth 内联在 config 中,经 job spec 传入 worker(`code_version.json` 中 run_spec 的 secrets 段自动脱敏为 `***`)。
- 快模型条目的 endpoint 留空 = 自动从同条目的 `endpoint_file`(`services/qwen_jev.json` / `kev.json`)发现文件读取最新地址(worker 侧建 session 时解析)。
- 慢模型凭据(base_url/api_key/model)和 docker registry auth 内联在 config 中,经 job spec 传入 worker(`code_version.json` 中 run_spec 的 secrets 段自动脱敏为 `***`)。
- 预算(max_steps / max_wall_seconds)也归 config,命令行只覆盖单项。
- **禁止**新增任何形式的环境档案/占位符展开/多配置文件。

## 开发闭环
Expand All @@ -49,17 +57,17 @@
- 每个 run 的 `code_version.json` 必须记录 git describe、protocol_version、镜像 digest、run spec(secrets 脱敏)。
- OSW2 任务资产经 `OSWORLD_FILE_BASE_URL` 指向本地 `asset_root`(避免外网下载);必须在任务模块 import **前**设置(见 osworld.py `_bootstrap_runtime`)。

## LLM 调用纪律(KimiClient)
## 慢模型调用纪律(SlowModelClient,慢模型客户端)

- `reasoning_effort` 默认 `low`(探测 97→22 token);`off` 映射 `enable_thinking=false`(归零)。
- 空正文(全留在思考通道)→ 下一次重试自动升档(low→medium→high)。
- 叙述文字包裹 JSON → `extract_json_object` 自动抢救(剥围栏 + 首个平衡 `{…}`)。
- 403(TPM 配额限流)→ 指数退避(60/120/240/480s),总计 5 次尝试。
- `finish_reason=length` → 明确报错提示调大 `kimi_max_tokens`(默认 16384)。
- `finish_reason=length` → 明确报错提示调大 `slow_max_tokens`(默认 16384)。

## JEV 排序服务纪律
## 快模型排序服务纪律

- `/rank` 要求 `image_path` 是**服务宿主机可读的文件路径**——截图暂存到 evobox 中转目录(`jev_image_stage_write/read`),payload 携带 JEV 宿主机视角路径。
- `/rank` 要求 `image_path` 是**服务宿主机可读的文件路径**——截图暂存到 evobox 中转目录(`harness.fast_model.image_stage.write/read`),payload 携带服务宿主机视角路径。
- 暂存按内容哈希命名去重(`step_00_<sha16>.png`),断点无害。
- endpoint 地址随闲时 GPU 重新部署变化 → 留空走自动发现文件(含 `ready`/`status` 就绪位检查)。

Expand All @@ -82,9 +90,11 @@ src/tandem/ harness 主体(组件、协议、CLI)
src/tandem/protocol/ 消息层协议:schema/ 真源、validate/ 校验、samples/ golden
src/tandem_eval/ worker 侧 6 文件(entry/env/runner/recorder/scoring/manifest)
+ 开发机侧 rjob.py / rjob_spec.py / rjob_status.py 生命周期客户端
+ config_schema.py(config.json 的校验与 API 选取)
tests/ protocol/ harness/ eval/ demo/ 四组
infra/worker.sh rjob worker 入口(mock 即跑;真实基准走共享 env.sh+docker)
infra/config.json 唯一配置文件(含密钥 → gitignore;模板 config.example.json 提交)
infra/config.json 唯一配置文件(harness/eval/log 三段;含密钥 → gitignore;
模板 config.example.json 提交)
scripts/ 批量运行脚本(run_osw2_20x5.sh 等)
tools/ render_summary.py(demo 占位;完整 studio 后续平移)
demo/ 轨迹回放与对比站点(adapter/metrics/frames/render + player/)
Expand All @@ -97,5 +107,6 @@ docs/ RESULT_DIR_CONTRACT.md(eval↔demo 唯一接口)

- 唯一落盘位置 `rjob_logs/<run_id>/`;`obs_stage/`(episode 内工作区)结束即清。
- `events.jsonl` 必须包含 planner/gui(ranking)/cli/verified/terminal 全部事件(增量排水,游标推进)。
- `llm_calls.jsonl` 只记 Kimi 调用(3 种 role);JEV/Kev 排序的输入输出记在 `events.jsonl` 的 gui 事件 `ranking` 字段。
- secrets 段(api_key/registry auth)在 `code_version.json` 的 run_spec 副本中全部脱敏为 `***`。
- `llm_calls.jsonl` 只记慢模型调用(3 种 role);快模型排序的输入输出记在 `events.jsonl` 的 gui 事件 `ranking` 字段。
- secrets 段(api_key/registry auth)在 `code_version.json` 的 run_spec 副本中全部脱敏为 `***`;
模型身份(`harness.models`:慢模型名 + 快模型名)留在 secrets 之外,保证 run 可归因。
25 changes: 25 additions & 0 deletions DEVIATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,28 @@
24. 【重构】**rjob.py 拆成三块**(原 322 行超单模块 300 行纪律):
rjob.py(生命周期客户端)/ rjob_spec.py(job spec 与 submit 参数)/
rjob_status.py(`rjob get` 状态解析);旧的 `tandem_eval.rjob` 导入路径保持可用。

## 配置与命名重构(2026-10-22,human in the loop)

25. 【重构】**配置 schema 角色化**:`infra/config.json` 从平铺键(`kimi` / `jev_endpoint` /
`kev_endpoint` / `jev_endpoint_file` / `kev_endpoint_file` / `osw1_root` …)改为三段结构
——`harness`(慢模型/快模型/路由/预算)、`eval`(集群与基准)、`log`(结果落盘);
`_comment` 等说明字段删除(说明属于文档,不属于配置)。
慢模型与快模型都改为**命名 API 登记表**(`harness.slow_model.apis` /
`harness.fast_model.apis`),启动命令用 `--slow-model <名字>` / `--fast-model <名字>` 挑,
缺省取各自的 `default`——为"不同大脑 × 不同小脑"的对比实验留出入口。
CLI 旧参数 `--selector jev|kev` → `--fast-model <名字>`,`--kimi-max-tokens` /
`--kimi-reasoning` → `--slow-max-tokens` / `--slow-reasoning`。
默认行为不变(jev + kimi-k3);但单题预算从 rjob_spec 硬编码的 15/900 改为
config 里的 `harness.budget`(30/1800,与批量脚本实际用值一致)。
26. 【重构】**SessionConfig 字段角色化**:`kimi_max_tokens` / `kimi_reasoning_effort` /
`kimi_timeout_seconds` / `kimi_direct_api` → `slow_*`;`jev_timeout_seconds` →
`fast_timeout_seconds`;`selector_backend` → `fast_backend`。
`KimiClient` 类名当时保留(后改名为 `SlowModelClient`,慢模型客户端),审计日志的 provider 字段
由 `"kimi"` 改为 `"slow_model"`,worker 侧审计文件名 `kimi_http.jsonl` →
`slow_model_http.jsonl`。
27. 【重构】**开发机侧新增 `config_schema.py`**:config 的校验与 API 选取独立成模块
(worker 侧仍是 6 文件封顶;rjob.py 不再背着 schema 知识)。
`demo/` 同步角色化:run 文档 `selector_backend` → `fast_model`(新增 `slow_model` 名),
排序调用计数 `jev_calls` → `fast_calls`、`jev_latency_ms_total` → `fast_latency_ms_total`
(这两个是 demo 层自算字段,不在结果目录契约里)。
40 changes: 26 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
│
▼
┌─────────────────────────────────────────────┐
│慢模型 planner(Kimi) ← 理解与规划 │
│慢模型 planner(大脑) ← 理解与规划 │
│ current_goal / expected_observation │
│ route / action_proposals / done │
└─────────────────────────────────────────────┘
Expand All @@ -39,7 +39,7 @@
│ Receipt:returncode / stdout / stderr
▼
┌─────────────────────────────────────────────┐
│慢模型 verifier(Kimi) ← 独立验证 │
│慢模型 verifier(大脑) ← 独立验证 │
│ outcome · state_changed · evidence │
│ 逐条需求核验 requirement_checks │
└─────────────────────────────────────────────┘
Expand All @@ -52,10 +52,11 @@

## 快慢双模:分工与边界

| | 慢模型(Kimi) | 快模型(System One) |
| | 慢模型(大脑) | 快模型(小脑) |
|---|---|---|
| 角色 | 需求抽取 / 一步规划 / 独立验证 | 在候选空间里排序选择 |
| 实现 | `planner.py`、`verifier.py`、`providers.py`(KimiClient) | `selector.py`(JEV)、`selector_kev.py`(Kev) |
| 实现 | `planner.py`、`verifier.py`、`providers.py`(`SlowModelClient`,OpenAI 兼容方言) | `selector.py`(JEV)、`selector_kev.py`(Kev) |
| 当前默认 | `minimax-m3.1-flash`(可切 `kimi-k3`) | `jev`(可切 `kev`) |
| 输入 | 指令 + 截图 + a11y 树 + 历史动作 + 需求清单 | 当前目标 + 候选动作(自带上下文描述)+ 截图(仅 JEV 需要) |
| 输出 | 结构化 plan:当前目标 / 预期观察 / 路由 / 动作提案 | 排序结果:winner + operation/action 两级分数 + margin + 延迟 |
| 代价 | 每步一次 LLM 调用,慢且贵 | 单次 HTTP 前向,快且便宜 |
Expand Down Expand Up @@ -177,9 +178,9 @@ from tandem.config import SessionConfig
from tandem.session import TandemSession

components = build_default(
client=kimi_client, # providers.KimiClient
client=slow_client, # providers.SlowModelClient(慢模型客户端)
endpoint="http://<排序服务>/rank",
config=SessionConfig(selector_backend="jev"),
config=SessionConfig(fast_backend="jev"),
)
session = TandemSession(components)
session.begin("把 FYP 答辩安排逐条加进日历", None) # None → 慢模型抽取需求
Expand All @@ -193,6 +194,10 @@ tandem-eval rjob run --run-id <name> --benchmark osworld2 --tasks <task-id> \
--repo . --config infra/config.json --poll 60 --timeout 3600
```

换大脑 / 换小脑只改启动参数:`infra/config.json` 里登记多套 API(`harness.slow_model.apis` /
`harness.fast_model.apis`),运行时用 `--slow-model <名字> --fast-model <名字>` 挑,
用来比较"不同大脑 × 不同小脑"的性能差距。

跑完**自动删除 rjob**——无论成功、失败、超时还是中途异常,都不在集群上留残留;
要保留现场(事后查 `rjob logs`)加 `--keep`。

Expand All @@ -207,19 +212,26 @@ tandem-eval rjob run --run-id <name> --benchmark osworld2 --tasks <task-id> \
| `allow_cli` | `True` | 是否允许 planner 走 CLI 路由 |
| `route_policy` | `"auto"` | `auto` / `gui` / `cli` |
| `cli_timeout_seconds` | `90` | CLI 超时(协议上限 120) |
| `jev_timeout_seconds` | `300` | 排序服务超时(jev 与 kev 共用) |
| `kimi_max_tokens` | `16384` | 慢模型单次响应上限 |
| `kimi_timeout_seconds` | `120` | 慢模型超时 |
| `kimi_direct_api` | `False` | 直连 API(不经代理) |
| `kimi_reasoning_effort` | `"low"` | `off`(归零思考)/ `low` / `medium` / `high` |
| `selector_backend` | `"jev"` | `jev`(`/rank`,需截图)/ `kev`(System One,纯文本) |
| `fast_timeout_seconds` | `300` | 快模型排序服务超时(jev 与 kev 共用) |
| `slow_max_tokens` | `16384` | 慢模型单次响应上限 |
| `slow_timeout_seconds` | `120` | 慢模型超时 |
| `slow_direct_api` | `False` | 直连 API(不经代理) |
| `slow_reasoning_effort` | `"low"` | `off`(归零思考)/ `low` / `medium` / `high` |
| `fast_backend` | `"jev"` | `jev`(`/rank`,需截图)/ `kev`(System One,纯文本) |

**换慢模型厂商**:在 `infra/config.json` 的 `harness.slow_model.apis` 里登记多套 API,
启动时用 `--slow-model <名字>` 挑(缺省取 `default`),不改代码。

> 厂商方言有差异(2026-10-23 实测):Kimi 认 `enable_thinking=false` 关思考;
> **MiniMax 忽略该字段**(仍返回 `reasoning_content`),只有 `reasoning_effort` 生效
> (`low`/`medium`/`high`)——想少思考就把 `reasoning_effort` 调低,别指望 `off`。

慢模型的调用纪律(`providers.py`):

- 思考强度默认 `low`(实测 97 → 22 token);空正文(全留在思考通道)→ 下一次重试自动升档;
- 叙述文字包裹 JSON → 自动剥围栏取首个平衡 `{…}` 抢救;
- 403(TPM 配额限流)→ 指数退避 60/120/240/480s,总计 5 次尝试;
- `finish_reason=length` → 明确报错,提示调大 `kimi_max_tokens`。
- `finish_reason=length` → 明确报错,提示调大 `slow_max_tokens`。

## 排序后端

Expand Down Expand Up @@ -267,7 +279,7 @@ python demo/build_site.py --runs-root rjob_logs --out demo/site --benchmark oswo
python -m http.server 8899 --directory demo/site # 本地预览
```

披露口径:未出分如实标注、不折算 0 分、不计入均分;JEV 分数标注 softmax 非校准概率;
披露口径:未出分如实标注、不折算 0 分、不计入均分;排序分数标注 softmax 非校准概率;
成本只用 token 与调用次数表达,不臆造货币单价;截图标明是离散帧非录屏。

详见 [demo/README.md](demo/README.md)。
Expand Down
16 changes: 8 additions & 8 deletions demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,27 @@ site/
### 总览页

- 每个 run 一张卡(已出分 / 未出分 / 均分 / 总耗时 / LLM 调用数 / 输入输出与思考
token / JEV 排序次数 / 失败分类);
token / 快模型排序次数 / 失败分类);
- **成本 × 准确率散点**:横轴 token 用量(平方根刻度),纵轴官方评分,气泡半径 ∝
耗时;绿=有分、红=零分,未出分的题单独排在底部一排(不计入均分);
- **任务缩略图网格**(无帧的题显示"未进入决策步"占位);
- 逐题对比表——点表头按分数、耗时、token 排序。

### 对比页(`compare.html`)

任选两个 run 并排:逐题的分数 / token / 耗时 / 步数 / LLM 调用 / JEV 排序,以及
任选两个 run 并排:逐题的分数 / token / 耗时 / 步数 / LLM 调用 / 快模型排序,以及
Δ差值(绿=更优、红=更差)。当前数据里 jev 与 kev 在任务 001 上可直接对照。

### 任务页

- **读数带**:官方评分、token 用量、真实用时、步数与 JEV 排序次数;
- **读数带**:官方评分、token 用量、真实用时、步数与快模型排序次数;
- 左侧执行前 / 执行后双帧对照;
- **需求清单 · 逐条核验**:从结构化字段(`verification.requirement_checks` +
`task_requirements` 抽取结果)得到每条需求的终态结论与"在第几步被核验",
点条目直接跳到那一步;
- 右侧是这一步的完整模型操作链:
1. **规划**:当前目标、预期观察、路由、任务状态、动作提案;
2. **候选与排序**:JEV 两阶段(操作级 → 目标级)softmax 分数与相对强度条、
2. **候选与排序**:快模型两阶段(操作级 → 目标级)softmax 分数与相对强度条、
margin、排序延迟;
3. **编译后动作**:kind / action_type / 参数 / 目标 id / 描述;CLI 路由附命令全文;
4. **执行回执与独立验证**:verifier 裁决、状态变化、证据、逐条需求核验、
Expand All @@ -84,7 +84,7 @@ site/
终止前的模型调用次数与 role 分布;
- **调用数也是 0 ⇒ 失败发生在模型被调用之前**(环境 / 依赖 / 配置),
与「模型跑了但没做对」是两回事;
- 右侧「执行摘要」列出状态、评分口径、决策步、JEV 次数、调用分布、用时。
- 右侧「执行摘要」列出状态、评分口径、决策步、快模型次数、调用分布、用时。

例:任务 017 的模型调用是 `task_requirements × 1 + plan_step × 3`(规划连续三次
非法 JSON 后抛 ValueError);任务 020 是 0 次调用(环境问题)。
Expand Down Expand Up @@ -116,7 +116,7 @@ site/

| 泳道 | 区间 | 数据来源 |
|------|------|----------|
| `plan` | 上一步结束 → Kimi 规划调用结束 | `llm_calls[].time`(响应落盘时刻) |
| `plan` | 上一步结束 → 慢模型规划调用结束 | `llm_calls[].time`(响应落盘时刻) |
| `rank` | 规划结束 → +排序延迟 | `ranking.latency_ms`(服务端实测) |
| `act` | 排序结束 → 本步观察采集时刻 | `traj[].wall_time` |
| `verify` | 观察时刻 → 验证调用结束 | `llm_calls[].time` |
Expand All @@ -128,8 +128,8 @@ LLM 调用按事件顺序归属步骤(实测序列恒为 `plan* → 观察 →

1. 只搬运**模型公开输出**,不生成任何虚构的"思考字幕";
2. `failed_unscored` 一律显示「未出分」,**不折算 0 分、不计入均分**;
3. JEV `/rank` 分数始终标注 **softmax,非校准概率**;
4. 成本以 **token 用量 + 调用次数** 表达(成本代理指标),本仓库没有 Kimi 计价信息,
3. 排序服务分数始终标注 **softmax,非校准概率**;
4. 成本以 **token 用量 + 调用次数** 表达(成本代理指标),本仓库没有慢模型计价信息,
不臆造货币单价;
5. 截图为**逐步离散帧**,非连续录屏;初始观察帧与终态动作后的帧不随结果目录保留,
页面如实标注缺失原因;
Expand Down
Loading
Loading