Files
codex-app-server/docs/marker-tools.md
T
host 3b2fef95fa 任务内标记工具:task-create/task-poll/task-read 移植、wait-threads 接通、新增 task-cancel/task-retry
- 新模块 cloud/marker_tools.py:纯逻辑解析、校验、快照格式化、
  控制门禁、派生代数与直接上限计数
- sandbox 两处解析提取五种标记块;worker 在 5 处投递点与
  _deliver_peer_messages 同款位置执行,报告排成后续消息下一轮回父任务
- wait 每块 cap 到 8 个目标(与服务端 /wait 对齐,spawn 原先的 10 个收紧),
  每轮最多 2 块;取消/重试门禁:同项目、非自己、非 goal、
  派生链或同创建人,另查 create_task / approve_merge 权限,
  查不到时收紧到仅派生链内
- goal 与协调员任务不解析;单测 30 个,cloud 全量 1043 个、4 跳过、0 失败
2026-10-28 10:13:40 +00:00

208 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务内标记工具(marker tools)
适用基线:main d129f7b 上的 feat-marker-tools 分支(本地提交,未推、
未建 PR、未部署)。本批只做四类标记工具,不要把本文档读成
“所有 Codex app 线程工具都已实现”:handoff、worktree 创建状态、
PR 检查诊断、automation、clock-sleep 都不在本批;MCP 真工具路
(同轮函数返回)也不在本批。
## 一句话原理
agent 不用联网、不用凭证。agent 在回合输出里写标记块,本地与
Docker 执行器都经 sandbox 的结果解析提取标记块(与 task-message
同款两处解析),可信的 worker 在 completed 轮次后执行。
异步语义(必须先讲清,否则会被当成函数调用):
- 写块 → 回合结束 → worker 执行 → 报告拼成一条后续消息、
下一轮才回到父任务。不是同轮函数返回。
- 轮询与等待在 worker 执行期间循环查库、占 worker 槽位(与
协调员等齐同款代价);超时或父任务被取消时中止。
- 只有 completed 轮次的块被执行;失败 / 超时 / 取消 / 配额中断
的轮次不执行;父任务执行期间被取消时也不执行。
- 拒绝只记报告与审计、不炸任务、不改目标状态。
## 边界:谁能用、谁不能用
- 能用:普通任务。普通任务当项目经理、自己派生员工、轮询员工、
等一批员工、取消或重试员工,走的就是这批工具。
- 不能用:goal 任务与协调员任务不解析这些标记块(只收不发)。
协调员任务本身不跑模型回合,等齐循环也不会在中途排空后续
消息做判断,所以协调员 PM 不能因此获得任务内工具。
- 容易混淆的一点:协调员的子任务本身是普通任务(不是协调员
任务),它写标记块会被解析;它的派生代数从 0 计(见“派生护栏”)。
## 四个工具的标记格式
### task-create:新建平级主线任务
```text
```task-create
{"name": "test-xxx", "prompt": "...", "model": null,
"effort": null, "executor": "docker"}
```
```
- 新任务是平级主线,不是子代理、不是 fork;不继承父任务历史;
环境基线继承父任务的 environment_id,但 worker 给它独立的
tenant/worktree,不动父任务的 cwd;父任务未提交的改动不会
自动带过去。
- prompt 必填、1-20000 字;name 可选、1-100 字(仅用于派生报告,
tasks 表没有 name 列,不落库、不改 prompt);model 不做白名单;
effort 只能是 low / medium / high / xhigh / max;executor 只能是
local / docker / cloud,未指定时继承父任务。
- 只建普通任务:不通过标记建协调员任务与 goal 任务。
- 每轮最多 5 块,多余的忽略;非法 JSON、prompt 缺失/空串/超长、
name 超长、effort/executor 非法时整块跳过;正文里的行首围栏
会把块截断成非法 JSON 而被跳过(与 task-message 同款非贪婪匹配)。
### task-poll / task-read:按任务 id 轮询阅读
```text
```task-poll
{"task_id": "<目标任务 id>", "interval_seconds": 5,
"timeout_seconds": 300, "chars": 2000}
```
```task-read
{"task_id": "<目标任务 id>", "wait_seconds": 30}
```
```
- task-read 是 task-poll 的别名;read-thread / read_thread 也是
同款别名。不传 wait_seconds 时立即读一次快照;传了则等这么久
再读一次(等价于 interval = timeout = wait_seconds)。
- interval 钳制 1-60 秒,timeout 钳制 0-3600 秒(0 = 立即快照),
chars 钳制 200-8000 字;各类块合计每轮最多 5 块。
- 轮询目标不存在、跨项目、轮询自己时,报告里写清拒绝原因,
不改任何状态。轮询目标是只读的,goal 任务也可以被轮询。
### wait-threads:一次等多个目标任务
```text
```wait-threads
{"task_ids": ["B", "C"], "interval_seconds": 5,
"timeout_seconds": 300, "chars": 2000}
```
```
- wait_threads 是同款写法;task_ids 也接受 thread_ids / threads
两个别名;去重保序。
- 每块最多 8 个目标,每轮最多 2 块。8 这个数是刻意与服务端
POST /tasks/{id}/wait 的“一次最多 8 个线程”对齐的;spawn
分支原先的每块 10 个在本批收紧到 8 个,不要再用 10。
- 并行查库:每轮查一次全部目标,不是逐个串行等超时相加;
全部终态或超时后把每目标快照一起排回父任务。
- 缺失、跨项目、等待自己的目标当场记为已处理(写清原因),
不让它们把整批拖到超时;其余目标继续等到终态或超时。
- 超时口径与服务端 /wait 不同,且是故意的:服务端 /wait 是给
外部调用方持 HTTP 连接等的,上限 120 秒;标记 wait 是 worker
在回合后直连库循环查,不持 HTTP 连接,超时钳制与 task-poll
同款(0-3600 秒),占的是 worker 槽位而不是 HTTP 连接。
两者不要混用、不要互抄上限。
### task-cancel / task-retry:取消 / 重试已存在的任务
```text
```task-cancel
{"task_id": "<目标任务 id>"}
```
```task-retry
{"task_id": "<目标任务 id>"}
```
```
- task_cancel / task_retry 是同款写法;task_id 也接受 to /
to_task_id / thread_id 别名;每种每轮最多 5 块。
- 取消复用 worker 现有的取消路径语义:取消后状态为 interrupted,
正在跑的进程组 / 容器按现有取消路径真杀,不只改库状态。
- 重试与服务端 POST /tasks/{id}/retry 对齐:仅 failed /
interrupted / merge-failed 可重试,重试时清空上一轮
result_text / error_text / actual_model、状态回 queued、
prompt 与模型等原样保留、记审计。重试本身不花模型钱,
重试后的新一轮照常计费。
- 执行顺序:创建 → 取消 → 重试 → 轮询 → 等待。创建在前让同一轮
刚创建的任务立刻能被轮询;控制操作在快照之前让轮询/等待读到
取消或重试后的新状态。
## 门禁(task-cancel / task-retry,缺一不可)
纯函数层(cloud/marker_tools.py check_control_gate)与 worker
权限层(worker.py _deliver_marker_tools)两道,拒绝时只记报告
与审计、不改目标状态:
1. 目标存在;同项目;不是自己。
2. 非 goal 任务(verify_command 非空或 goal_mode 为真都算 goal)。
3. 取消要求目标非终态;重试要求目标仅为 failed / interrupted /
merge-failed。已合并(merged)两者都拒绝;merge-failed 本身
是终态,所以取消拒绝、重试接受。
4. 关系二选一:目标在发送方派生链上(沿 parent_task_id 链最多
32 步能追溯到发送方),或目标 created_by 与发送方 created_by
相同。“同项目猜到 id 就能取消任何人的任务”在此被挡住。
5. 权限层:取消要求发送方创建人有项目 create_task 权限(与
服务端取消口径同款),重试要求有 approve_merge 权限(与
服务端重试口径同款)。权限检查本身抛异常时收紧到仅派生链内
可用,同创建人-only 的目标一律拒绝,并把原因写进报告。
## 派生护栏与费用
- 派生深度上限 3 代(父任务本身是派生任务时逐代计;协调员子任务
的代数从 0 计,见“边界”);单个父任务直接派生上限 20 个
(不含协调员子任务);超限的块跳过。
- 派生任务是独立主线:父任务完成或取消都不自动杀派生任务;
想停派生任务,用 task-cancel 或控制台 / API 显式取消它。
协调员孤儿收口只扫 coordinator_part 非空的协调员子任务,
不扫派生任务。
- 费用与配额:派生任务 created_by 跟父任务走,认领时照常过
key 门与配额门;创建时 worker 先查一次父任务配额,超限时
整轮创建跳过(排队本身不花钱,但不让超限方再批量排队)。
## 诚实边界(与服务端口径不一致处,逐条列出)
- 重试的 goal 拒绝比服务端严:服务端 POST /tasks/{id}/retry
没有单独的 goal 检查,标记重试为了不破坏 goal 循环语义,
明确拒绝 goal 目标。这是标记口径比服务端口径严,不是遗漏。
- 取消的 goal 拒绝同理:服务端取消也没有单独的 goal 检查,
标记取消同样拒绝 goal 目标。
- 服务端重试是双人门(触发人与原任务创建人任一超限即拦,
且触发人须有 approve_merge 权限);标记重试的触发方是发送
方任务本身,其 created_by 与派生任务的 created_by 天然相同
(派生时跟父任务走),所以 worker 侧只查发送方创建人这一道
approve_merge,效果与双人门在派生链内等价;同创建人-only
的目标同样只查这一道,权限查不到时则收紧到仅派生链内。
- wait 的超时口径与服务端 /wait 不同(见上文),数量口径已
对齐到 8 个;不要把标记 wait 的 3600 秒上限套到 HTTP /wait 上。
- 本批没有 MCP 真工具:标记工具永远是异步的(下一轮才拿到
报告),做不到同轮函数返回;想做同轮返回要另搭任务级鉴权的
MCP 服务器,不在本批。
- 签名门残留:worker 的 _code_signature 只签 worker / sandbox /
coordinator / cost / server / host 共 6 个文件,新模块
marker_tools.py 不在签名候选里。本批部署时 worker.py 与
sandbox.py 本身都变了,签名门照常生效;但将来只改
marker_tools.py 的补丁不会单独触发签名门,届时要么把
marker_tools.py 加进签名候选并同步改单测的 6 个断言,要么
接受这一处缺口——本批不顺手改签名,避免动无关安全测试的
预期。
- spawn 分支的未提交半成品没有原样合并进来:本批只移植了
已提交的 task-create / task-poll / task-read 实现与未提交的
wait 解析(收紧到 8 个后接通执行),handoff / worktree /
PR 检查 / automation / clock-sleep 的解析与 send-message-to-thread
别名均未移植;spawn 工作区本身未动。
## 关键实现位置
- cloud/marker_tools.py:纯逻辑(解析、校验、快照格式化、
门禁、派生代数与直接上限计数),单测不需要 worker/server。
- cloud/sandbox.py:本地 _result_from_runner 与 Docker
_parse_host_result 两处提取五种标记块,worker 侧按结果
dict 里的 task_creates / task_polls / wait_threads /
task_cancels / task_retries 执行。
- cloud/worker.py:_deliver_marker_tools(5 处投递点,与
_deliver_peer_messages 同款位置)、_poll_task_snapshot、
_wait_task_targets、_queue_self_followup、_marker_auth。
- cloud/tests/test_marker_tools.py:解析上限与钳制、非法块跳过、
跨项目拒绝、自操作拒绝、已终态拒绝、goal / 已合并拒绝、
重试仅三态且清空旧结果、wait 多目标终态与超时快照、派生
深度与直接上限、权限检查失败收紧。