将任务委托给另一个代理 CLI
在后台执行一个外部代理 CLI 上的任务,并返回标准化结果。
驱动:delegate.mjs,位于本技能目录中(需要 Node ≥ 18,无依赖)。
行为由 contracts/ 中的三个合约定义 —— 在修改驱动之前请先阅读它们。
start 立即返回一个 run_id;collect 获取结果。不会阻塞任何操作。
首先解析驱动和运行目录
本技能的安装位置决定了 delegate.mjs 的路径,运行产物默认存放在驱动旁边。
每会话设置一次这两个路径,然后在每个命令中使用 $DELEGATE:
# Claude Code 插件安装 (agent-tools@sanduq)
DELEGATE="$CLAUDE_PLUGIN_ROOT/skills/delegate-task/delegate.mjs"
# npx skills install,或者将副本提交到仓库中
DELEGATE=".claude/skills/delegate-task/delegate.mjs"
# 将运行产物保留在正在处理的项目目录中,而不是驱动旁边
export DELEGATE_RUNS_DIR="$PWD/.delegate/runs"
对于插件安装,DELEGATE_RUNS_DIR 是必需的:否则,每个项目的产物都会堆积在共享的插件目录中,其中包含你的任务文本和 CLI 的原始输出。将 .delegate/ 添加到项目的 .gitignore 中。每个与运行交互的命令 —— start、status、collect、list、prune —— 必须看到相同的值。
首先检查可用项
node "$DELEGATE" doctor
doctor 探测每个 CLI 的版本,因此可以区分未安装和已安装但有故障 —— 并打印每个 CLI 实际上请求了什么 --sandbox。
| CLI | id | 层级 | 恢复 | --sandbox 请求 |
|---|---|---|---|---|
| Claude Code | claude | 已验证 | 已验证 | --permission-mode plan |
| OpenAI Codex | codex | 已验证 | 已验证 | -s read-only(CLI 强制) |
| OpenCode | opencode | 已验证 | 已验证 | --agent plan |
| GitHub Copilot | copilot | 实验性 | 实验性 | --mode plan |
| Pi | pi | 实验性 | 实验性 | --no-approve --tools read,grep,find,ls |
建议优先使用 claude、codex 或 opencode。
运行委托
node "$DELEGATE" start \
--harness codex \
--cwd /path/to/workspace \
--timeout 600 \
--task "为 parse_config() 添加输入验证,并编写一个覆盖错误输入路径的测试。"
输出 {"run_id": "codex-…", "state": "running", ...} 并返回。然后:
node "$DELEGATE" collect codex-… --wait 300
--wait N 最多轮询 N 秒。如果没有此选项,collect 在运行仍在继续时退出码为 3。
其他命令:
node "$DELEGATE" status <run_id> # starting|running|stalled|adrift|lost|done
node "$DELEGATE" collect <run_id> --json # 机器可读的结果——解析此结果
node "$DELEGATE" list # 所有运行,按时间顺序从旧到新
node "$DELEGATE" prune --keep 20 --yes # 删除旧的运行产物
node "$DELEGATE" contract # 机器可读的标志、字段、退出码
读取结果
Status SUCCESSFUL
Because harness_telemetry
Changes
measured greeting.txt
claimed greeting.txt
Permission requested=bypass applied=codex: --dangerously-bypass-approvals-and-sandbox
三个字段承载了大部分含义:
status——successful/failed/abandoned。status_provenance.primary—— 为什么会出现那种状态,用一个词表示:timeout、spawn_error、signal、exit_code、parse_error、telemetry_incomplete、harness_telemetry、self_report、head_moved、index_moved、relay_aborted、supervisor_lost、legacy。 切勿在不知道是哪个词产生了该状态的情况下报告状态。evidence列出每个为真的观察结果,而不仅仅是胜出的那个。dirty_paths_changed—— git 测量的内容,与之相对的是files_claimed,即委托声称的内容。files_mismatch表示存在分歧。
权限
默认为完全绕过。 委托在无人值守的情况下运行任意命令。
--sandbox 请求每个 CLI 的真实只读模式(见上表),并启用触发器:
read_only_violation 为 true / false / null。null 表示未知 —— git
不可用或指纹覆盖不完整。它永远不会被折叠为 false。
false 并不意味着“没有发生写入”。 它意味着未检测到最终的增量变化。它无法看到写入-然后-恢复、任何被 gitignore 忽略的内容,或任何仓库外部的写入。
true 并不意味着委托执行了写入 —— 同一工作树中的并发编辑看起来是一样的。
信封告诉委托不要提交,但这只是策略,而非强制。强制手段是测量:如果在运行窗口期间 HEAD 移动了,
结果将被强制为需要审查(head_moved),除非传入了 --allow-commit。
继续运行
node "$DELEGATE" start --harness codex --cwd <same dir> --resume <run_id> \
--task "修复是对的,但要使用真实的固定装置而不是模拟。"
只发送增量。Resume 会获得一个新的 run_id,并设置了 parent_run_id,如果 CLI、
工作目录或父会话 ID 不一致,则会拒绝执行。
恢复的派发会重复执行每条策略规则,而不是假设父节点仍然遵守它们。
标志
| 标志 | 用途 |
|---|---|
--cwd DIR | 工作目录 —— 始终明确设置它 |
--model M | 覆盖模型 |
--timeout SEC | 默认 1800 秒;预先验证;到期时进程树将被终止 → abandoned |
--deliverable TEXT | 要返回的内容(“一个统一的差异,不要提交”) |
--constraint TEXT | 可重复的额外规则 |
--sandbox | 请求 CLI 的只读模式 |
--allow-commit | 允许提交,并停止将 HEAD 移动视为需要审查 |
--resume <run_id> | 使用增量任务继续已完成的运行会话 |
--clean-env / --keep-env N | 仅向 CLI 提供运行时基础变量;重复 --keep-env 为每个认证所需的变量 |
--raw | 逐字发送任务(失去状态/摘要;与 --resume 不兼容) |
注意事项
- 退出码为 0 并不意味着任务已完成 —— 直到此次重构之前,非零退出码也并不意味着失败。适配器接收了退出码,但无人读取,因此一个“成功形状”的事件加上退出码 1 被发布为
successful。已修复;由test/run.mjs的REPAIR-D1用例约束。始终读取status_provenance,切勿单独依赖exit_code。 - 不同 CLI 的 token 字段含义不同。 Claude 的
input_tokens/cache_creation/cache_read是互斥的 —— 需要求和(一个探测报告input_tokens: 2,而实际请求发送了 58,065)。Codex 的input_tokens包含cached_input_tokens—— 减去即可得到新的计数。使用input_total/input_fresh;usage_raw保留 CLI 自己的数字用于审计,但从不显示为总计。 - 不同 CLI 的总计不可比较。 OpenCode 每个步骤发射一个
step_finish事件,驱动将其累加,因此每个步骤重新发送的上下文都会被重复计数。相同简单任务:66,341 (codex),117,014 (claude),426,523 (opencode)。并非效率排名。 - 委托给
claude有大约 58k token 的基础消耗。claude -p在读取任务之前会加载 CLAUDE.md 和每个发现的技能。 - Codex 继承你的 MCP 服务器。 如果 codex 感觉慢,请读取
runs/<run_id>/stderr.log—— 延迟通常来自 MCP,而非模型。 - Codex 在成功运行时也会发射
item.type: "error"事件。 只有turn.failed或缺失的turn.completed才算数。 - 完全绕过确实是不带沙箱的。 一个 OpenCode 委托曾经在其
--cwd之外写入文件。使用--sandbox或临时目录。 - 并非所有 CLI 都报告每个字段。 Copilot 完全不发射 token 计数(
fidelity: "unavailable"—— 零表示未知,而非免费)。Codex 在其 JSONL 中既不发射成本也不发射模型,因此cost_usd和model本质上是 null,而不是解析失败 ——cost_reported/model_reported会指出哪些报告了,打印的结果显示为“此 CLI 未报告”。Claude 两者都报告;OpenCode 报告成本。 - 任务文本永远不会到达 shell。 每个 CLI 被解析为真正的可执行文件,并以
shell:false派生。 pi会在其自己的存储中持久化一个会话。 故意不传递--no-session—— 没有会话就无法恢复。会话存放在~/.pi/agent/sessions/<cwd-slug>/,以工作目录为键;prune不会影响它们。Pi 的认证是基于文件的(~/.pi/agent/auth.json),因此--clean-env不会破坏它。- 其监督进程被终止的运行是
lost,而非卡住。collect从日志和文件中综合出一个终端结果。其 git 差异为null:没有保留运行开始时的基线,报告false将是一种保证。 - 运行 id 会经过验证。 它们必须匹配
<harness>-<14 digits>-<6 hex>,并且必须直接位于运行目录下。调用者提供的 id 不能遍历出该目录。 - 一个已死亡但 CLI 仍在运行的监督进程是
adrift,而非lost。collect拒绝发布结果,因为此时写入结果将快照一个仍在进行中的运行。等待子进程完成,或杀死status命名的 pid。prune也不会删除 adrift 的运行。 DELEGATE_BIN_<HARNESS>覆盖二进制文件的查找路径 —— 例如DELEGATE_BIN_CODEX=/opt/codex/bin/codex。这也是测试套件如何端到端驱动伪造的 CLI 而不花费 token 的方法。
故障排除
| 症状 | 原因 / 修复 |
|---|---|
<bin> not found on PATH | 该 CLI 未安装;运行 doctor |
found <path> but could not resolve what it launches | CLI 安装在一个非 npm 的 .cmd 包装器后面,本驱动无法解码 |
doctor 显示 BROKEN: probe timed out | 通常是冷启动;在得出任何结论之前重新运行 doctor |
supervisor failed to start (exit 5) | 分离的监督进程从未确认;检查 runs/<id>/journal.jsonl。运行被最终确定为 failed,带有 containment_evidence: "the harness never launched";如果日志中只包含 created 和 terminal 且监督进程 pid 已消失,则没有代理运行过 |
run <id> still executing (exit 3) | 添加 --wait <seconds> |
Refusing to publish … still running (exit 6) | 运行处于 adrift 状态:监督进程已死但 CLI 仍在运行。等待,或杀死它命名的 pid |
another process is finalising this run (exit 7) | 一个活动的写入者持有锁;稍后重试 |
written by a different driver (exit 8) | 结果包含未知的模式;此构建版本不会猜测它 |
状态 abandoned,来源 supervisor_lost | 监督进程被终止;在重新派发之前检查工作树 |
状态 failed,来源 head_moved / index_moved | 在运行期间有内容被提交或暂存;工作树已保留,请审查 |
摘要显示 (no summary produced) | 委托从未发射结果块;读取 runs/<run_id>/stdout.log |
文件
运行产物存放在 $DELEGATE_RUNS_DIR/<run_id>/(prompt.txt、meta.json、journal.jsonl、
stdout.log、stderr.log、result.json)。如果未设置,DELEGATE_RUNS_DIR 默认使用驱动旁边的 runs/
—— 在插件安装时,这是共享的插件目录,因此请务必设置它。使用 DELEGATE_MAX_LOG_BYTES
(每个流默认 32 MB)限制日志大小,并使用 DELEGATE_LOG_TAIL_BYTES(默认 512 KB)设置超过限制后的保留窗口。
超过限制后,驱动会保留完整的已解析记录,而不是字节后缀,因此遥测信息仍然可以解析。窗口无法容纳的记录会统计在 records_dropped 中 —— 非零值意味着状态仍然是可信的,但委托自己的最终答案可能不完整。
⚠️ 产物包含你的任务文本、完整提示词和 CLI 的原始输出 —— 请将其视为敏感内容。 在最终处理时,stdout.log 和 stderr.log 中的 token 模式和类似凭据的 NAME=value 对会被屏蔽(result.json 中的不会被屏蔽)。在 POSIX 系统上,运行目录的权限为 chmod 0700;在 Windows 上,权限是继承的,本驱动无法限制。成功执行 collect 后,会删除超过 DELEGATE_PRUNE_DAYS 天(默认 30 天;设为 0 禁用)的已完成运行;可以通过 prune 更快地清理。
深入阅读
-
要了解一批已知的本地 CLI 检查,请阅读 原生检查运行器合约。它涵盖 JSON argv 计划、不可变尝试、有限收集和 PowerShell 调用。使用相同的参考资料来了解
collect --summary、仅心跳诊断和可选的启动截止时间。 -
contracts/status-precedence.md—— 如何决定状态,以及要测试的交叉乘积 -
contracts/git-fields.md—— 触发器测量了什么,以及它无法看到什么 -
contracts/result-schema-v2.md—— 结果形状、v1 向后读取、日志、单写入者 -
references/writing-the-brief.md—— 如何编写一个委托可以盲执行的任务 -
references/reviewing-the-result.md—— 如何审查返回的结果,包括清场 -
references/task-queues.md—— 如何运行多个依赖任务而不丢失线索
测试:在该技能目录下运行 node test/run.mjs。
执行回归测试:node --test test/execution.test.mjs(Node/Python CLI 固定装置)。