AutoGPT Orchestrate Skill:用 tmux 与 git worktree 编排 Claude Code 并行代理舰队的元监督系统
本文基于 AutoGPT 仓库中的 orchestrate 技能定义 及其 scripts 目录 下的十个 Shell 脚本撰写。它讲解如何用「一个 tmux 会话 + N 个窗口 + N 个 git worktree」的模型并行驱动多个 Claude Code 代理:从空闲 worktree 的发现与分配、代理的生成与任务下发、机械层(零 token)的闲置重启与对话框批准、到基于 checkpoint 与 CI/Review 状态的严格完成校验。读完后,你将能够复现这套「LLM 监督 + 脚本机械执行」的双层代理编排架构,并理解其中状态机、原子状态写入、会话恢复与假完成检测等关键实现细节。
一、系统总览:一个会话,N 个窗口,一个机械保姆
orchestrate 是一个可被用户直接调用的 Claude Code 技能(user-invocable: true),其定位是元代理监督者:当前这个 Claude 会话本身就是监督者,负责阅读各代理窗口(pane)的输出、检查 CI、对停滞的代理下达针对性指导。其核心拓扑是:
Orchestrating Claude(本会话 = 监督者)
└── 读取 pane 输出、检查 CI、干预
run-loop.sh(独立 tmux 窗口,每 30s 一轮)
└── 仅做机械动作:闲置重启、对话框批准、状态流转
这套模型的关键设计原则是:监督者不另开窗口。技能文档明确警告——另开一个监督 Claude 窗口会丢失上下文、难以观察,且会叠加上下文压缩问题;因此监督者必须留在当前对话中,以每 2~3 分钟一次的轮询节奏主动监控。
与之配合的是机械层 run-loop.sh,它「零 token」运行,只处理无需判断力的事务:重启已崩溃的代理、在对话框上按 Enter、在完成且验证通过后流转状态。从源码看,run-loop.sh 在启动时会先把自身所在的 scripts 目录复制到 $HOME/.claude/orchestrator/scripts 这一稳定位置(run-loop.sh#L22-L27),原因是 recycle-agent.sh 回收 worktree 时可能把当前分支切换回 spare/N,从而清掉仅存在于当前分支上的技能脚本副本。
脚本工具箱
技能共提供 10 个脚本,职责划分清晰:
| 脚本 | 用途 |
|---|---|
find-spare.sh [REPO_ROOT] |
列出空闲 worktree,每行输出 PATH BRANCH |
spawn-agent.sh SESSION PATH SPARE NEW_BRANCH OBJECTIVE [PR] [STEPS...] |
建窗口 + 切分支 + 启动 claude + 下发任务,stdout 只输出 SESSION:WIN |
recycle-agent.sh WINDOW PATH SPARE_BRANCH |
杀掉窗口 + 还原 spare 分支 |
| run-loop.sh | 机械保姆:闲置重启 + 对话框批准 + 完成信号流转 + 健康检查 |
verify-complete.sh WINDOW |
验证 PR 真正完成:checkpoint ✓ + 0 未解决线程 + CI 绿 + 无新鲜 CHANGES_REQUESTED |
notify.sh MESSAGE |
经 Discord webhook(DISCORD_WEBHOOK_URL 或状态文件 .discord_webhook)、macOS 通知中心、stdout 三路发通知 |
capacity.sh [REPO_ROOT] |
打印可用与占用中的 worktree |
| status.sh | 打印舰队状态 + 各 pane 实时前台命令 |
| poll-cycle.sh | 一次监控循环——分类 pane、跟踪 checkpoint、返回 JSON 动作数组 |
classify-pane.sh WINDOW |
对单个 pane 做状态分类 |
二、worktree 的发现与回收:spare/N 分支模型
整套系统以 git worktree 为代理的物理隔离单元。每个 worktree 平时停留在一个 spare/N 分支上表示「空闲」,被占用时切出 feat/xxx 任务分支,完成后回收回 spare/N。
find-spare.sh 的实现非常精炼:直接解析 git worktree list --porcelain 输出,用 awk 提取 worktree 与 branch 字段,再只保留分支名匹配 refs/heads/spare/[0-9]+$ 的行(find-spare.sh#L18-L24):
git -C "$REPO_ROOT" worktree list --porcelain \
| awk '
/^worktree / { path = substr($0, 10) }
/^branch / { branch = substr($0, 8); print path " " branch }
' \
| { grep -E " refs/heads/spare/[0-9]+$" || true; } \
| sed 's|refs/heads/||'
因此「空闲 worktree」的判定标准就是当前分支是 spare/N——这也正是后面「受保护 worktree」规则依赖的锚点。
回收则由 recycle-agent.sh 完成,其步骤(recycle-agent.sh#L23-L30):
tmux kill-window -t "$WINDOW" 2>/dev/null || true # 杀窗口(可能已不存在)
git -C "$WORKTREE_PATH" rebase --abort 2>/dev/null || true
git -C "$WORKTREE_PATH" merge --abort 2>/dev/null || true
git -C "$WORKTREE_PATH" reset --hard HEAD 2>/dev/null
git -C "$WORKTREE_PATH" clean -fd 2>/dev/null
git -C "$WORKTREE_PATH" checkout "$SPARE_BRANCH" # 还原到 spare/N
先中止可能进行中的 rebase/merge、硬重置并清掉未跟踪文件,最后切回 spare 分支——保证 worktree 以干净状态重新进入「空闲」池。
受保护 worktree 规则:技能文档特别指出 AutoGPT1(分支 dx/orchestrate-skill)承载了 orchestrate 技能脚本本身,若被当作 spare 使用,recycle-agent.sh 把它切回 spare/1 会清掉 .claude/skills/,使后续所有 spawn-agent.sh 调用失效。规则是:用 find-spare.sh 选 worktree 时必须跳过当前分支为受保护分支的 worktree;若误在其中生成了代理,绝不允许对它跑 recycle-agent.sh,任务结束后要手动恢复分支。
三、生成代理:spawn-agent.sh 的完整动作链
spawn-agent.sh 是整个系统里最复杂的脚本,一次调用完成六件事:
- 生成稳定会话 ID:
SESSION_ID=$(uuidgen ...)(L35),该 UUID 传给claude --session-id,使会话崩溃后始终能用claude --resume $SESSION_ID精确恢复完整上下文; - 创建/切换到任务分支:
git checkout -b "$NEW_BRANCH",失败则退回checkout "$NEW_BRANCH"(L38-L39); - 开命名 tmux 窗口:
tmux new-window -t "$SESSION" -n "$WORKTREE_NAME" -P -F '#{window_index}',捕获窗口号组成WINDOW="SESSION:IDX"; - 写入初始 agent 记录:用 jq 把包含
window、worktree_path、spare_branch、session_id、state: "running"等字段的记录追加到状态文件(L47-L74),若提供了PR_NUMBER与STEPS,再打一层补丁写入pr_number/steps——因此调用方不得在脚本返回后再重复追加记录; - 启动 claude 并等待就绪:
tmux send-keys ... "cd <path> && claude --permission-mode bypassPermissions --session-id '<ID>'" Enter(L91);随后_wait_idle最长等 60 秒,判定条件是三行 pane 尾部出现❯且无 spinner 字符(✳✽✢✶·等),期间遇到Enter to confirm对话框会自动Down Enter确认(L95-L120); - 下发任务文本:把 objective 与完成协议拼在一起发送——「每完成一步输出
CHECKPOINT:<step-name>,全部完成后单独一行输出ORCHESTRATOR:DONE」。
第 6 步严格采用「文本与 Enter 分开发送 + sleep 0.3」的拆分模式(L124-L126),原因在注释中写得很清楚:合并发送时 Enter 可能在字符串尚未完全缓冲进 Claude 输入区时触发,消息会卡成未发送的 [Pasted text +N lines]。这个模式被上升为全局规则:长消息必须拆分,单字符短消息(y、Down、空 Enter)才可以合并。
四、checkpoint 协议与 pane 状态机
协议约定
代理每完成一个必需步骤,就在 pane 中输出一行 CHECKPOINT:<step-name>。必需步骤在生成时以参数传入(如 pr-address pr-test)。run-loop.sh 不会在所有必需 checkpoint 都被发现之前回收窗口;verify-complete.sh 失败时代理会被自动 re-brief。
poll-cycle.sh:状态机的核心
poll-cycle.sh 每轮对每个 agent 执行一次完整分类并原子更新状态文件,其要点(结合源码):
- 安全防御:先对
window值做正则校验^[a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+(\.[0-9]+)?$,防止 tmux target 注入(L88-L92);对状态文件先做jq -e '.'预校验,被 SIGKILL 截断的损坏文件会输出[]而不是在set -e下崩溃(L40-L44); - checkpoint 解析:捕获最近 500 行滚动缓冲区(
tmux capture-pane -S -500),grep 出所有CHECKPOINT:[a-zA-Z0-9_-]+并sort -u后与状态中已有 checkpoint 求并集(L113-L130); - 卡死检测:对处于
running的 agent,取 pane 尾部 20 行做 md5(跨平台兼容md5sum/md5),与上一轮的last_output_hash比较;哈希连续稳定超过idle_threshold_seconds(默认 300 秒)即判定stuck并触发 kick(L129-L136); - 升级机制:
revision_count累计 kick 次数,达到 3 次即置为escalated,不再自动重启,转人工处理——对应技能文档「Escalate after 3 kicks」规则(L162-L166、L187-L195); - 完成信号:pane 中出现
ORCHESTRATOR:DONE时,状态置为pending_evaluation、动作记为complete——但 run-loop 不做验证只记日志,真正的评估交给监督者的后台轮询(L144-L150); - 原子写:所有状态更新遵循「jq 写
.tmp再mv」的模式,且用trap在退出时清理残留.tmp(L30、L252-L255)。
classify-pane.sh:单窗口四态分类
classify-pane.sh 输出 {"state": "running|idle|waiting_approval|complete", ...} JSON,判定优先级是:
- pane 中存在独占一行的
ORCHESTRATOR:DONE→complete(L43); - 尾部 40 行命中审批模式("Do you want to proceed"、"[y/n]"、"Esc to cancel" 等 10 种模式)→
waiting_approval(L48-L70); - pane 前台进程是 shell(zsh/bash/fish…)→
idle,即 claude 已退出(L74-L79); - 其余 →
running。
run-loop.sh 的机械动作
拿到 poll-cycle 的动作数组后,run-loop 只做三件事(run-loop.sh#L194-L198):
kick:仅对idle状态(代理进程已退出)生效——先wait_for_claude_idle等 pane 稳定,再发送claude --resume '<session_id>' --permission-mode bypassPermissions(无 session_id 时用--continue)恢复原会话上下文(L114-L136);approve:按三种模式自动批准——Enter to confirm对话框发Down Enter;编号选项对话框(❯ 1.已在第一项)直接按 Enter;git/npm/pnpm/poetry/pytest/docker/make/cargo/pip/yarn等白名单前缀或 localhost curl 的 y/n 提示发y(L141-L170)。其余未知对话框一律跳过,交由监督者处理;- 轮询间隔自适应退避:基础间隔 30 秒,无任何活动时按
POLL_CURRENT + POLL_CURRENT/2 + 1递增直至封顶 300 秒;一旦发生 kick/complete/出现 waiting_approval 则立即重置回基础值(L204-L211)。
一个容易误读的细节:run-loop 日志中的 RUNNING 计数匹配正则 running|stuck|waiting_approval|idle,所以全队在等待审批时 RUNNING 也 >0,并不表示代理在干活。怀疑时直接查状态文件:
jq '.agents[] | {window, state, worktree}' ~/.claude/orchestrator-state.json
五、状态文件:单一事实源
状态文件位于 ~/.claude/orchestrator-state.json(可用 ORCHESTRATOR_STATE_FILE 环境变量覆盖),从不提交进 git,由监督者直接用 jq + 原子写维护。完整结构如下:
{
"active": true,
"tmux_session": "autogpt1",
"idle_threshold_seconds": 300,
"loop_window": "autogpt1:5",
"repo": "Significant-Gravitas/AutoGPT",
"discord_webhook": "https://discord.com/api/webhooks/...",
"last_poll_at": 0,
"agents": [
{
"window": "autogpt1:3",
"worktree": "AutoGPT6",
"worktree_path": "/path/to/AutoGPT6",
"spare_branch": "spare/6",
"branch": "feat/my-feature",
"objective": "Implement X and open a PR",
"pr_number": "12345",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"steps": ["pr-address", "pr-test"],
"checkpoints": ["pr-address"],
"state": "running",
"last_output_hash": "",
"last_seen_at": 0,
"spawned_at": 0,
"idle_since": 0,
"revision_count": 0,
"last_rebriefed_at": 0
}
]
}
顶层可选字段:repo(GitHub owner/repo,用于 CI/线程检查,缺省时脚本从 git remote 自动推导);discord_webhook(完成通知,亦读 DISCORD_WEBHOOK_URL 环境变量)。
关键字段语义(与 spawn-agent.sh#L57-L73 的实际写入一一对应):
session_id——传给claude --session-id的 UUID,用于claude --resume UUID在崩溃或窗口关闭后恢复精确会话;last_rebriefed_at——上次 re-brief 的 Unix 时间戳,强制 5 分钟冷却防止刷屏;spawned_at——代理生成时间,是 verify-complete 第 4 项检查(「CI 运行必须晚于代理生成」)的基准;revision_count/idle_since——卡死与升级计数的累加器。
代理状态机共七个状态:running | idle | stuck | waiting_approval | complete | done | escalated(poll-cycle 内部还有 pending_evaluation 作为完成评估的中间态)。done 的含义是「已验证完成」——窗口仍然开着、会话仍然活着、worktree 仍停在任务分支上,尚未回收,这与下文「窗口永不自动杀死」的原则一致。
状态漂移的恢复
脚本写入的状态文件可能因窗口被关、会话过期或监督者跨对话重启而与真实环境漂移。典型征兆:loop_window 指向已不存在的窗口;agent 状态是 running 但 tmux 窗口已关闭或只剩 shell 提示符;last_seen_at 已是数小时前。恢复步骤:
# 1. 核对真实 tmux 窗口
tmux list-windows -t SESSION -F '#{window_index}: #{window_name} (#{pane_current_command})'
# 2. 与状态文件交叉比对
jq -r '.agents[] | "\(.window) \(.state) \(.worktree)"' ~/.claude/orchestrator-state.json
# 3a. agent 窗口已关 —— 标 idle 让 run-loop 重启它
jq --arg w "SESSION:WIN" '(.agents[] | select(.window==$w)).state = "idle"' \
~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
# 3b. loop_window 已亡 —— 清引用并重启 run-loop.sh
jq '.loop_window = null' ~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
LOOP_WIN=$(tmux new-window -t "$SESSION" -n "orchestrator" -P -F '#{window_index}')
tmux send-keys -t "${SESSION}:${LOOP_WIN}" "bash $SKILLS_DIR/run-loop.sh" Enter
jq --arg w "${SESSION}:${LOOP_WIN}" '.loop_window = $w' ~/.claude/orchestrator-state.json \
> /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
# 4. 任何修复后重跑 status.sh 确认一致性再恢复监督
六、生成代理的完整操作序列
监督者(Claude 本身)按下述六步执行,其中第 3 步的意图映射来自技能文档的「Intent → action mapping」表:
| 用户说的话 | 动作 |
|---|---|
| "status" / "what's running" | 跑 status.sh + capacity.sh 并展示 |
| "how many free" / "capacity" | 跑 capacity.sh |
| "start N agents on X, Y, Z" | 执行下面的生成序列 |
| "add task: …" | 同序列但只处理单个任务 |
| "stop" / "shut down" | 执行停机序列(见第九节) |
| "poll" / "check now" | 跑 poll-cycle.sh 并处理动作 |
| "recycle window X" | 直接跑 recycle-agent.sh |
步骤 1 —— 解析 tmux 会话:
tmux list-sessions -F "#{session_name}: #{session_windows} windows" 2>/dev/null
绝不在 Claude 进程内创建 tmux 会话——它将成为 Claude 进程的子进程,随会话结束而死。没有现成会话时,应提示用户先在终端执行 tmux new-session -d -s autogpt1 再重新调用。
步骤 2 —— 展示可用容量:bash $SKILLS_DIR/capacity.sh $(git rev-parse --show-toplevel)。capacity.sh 输出两段:find-spare.sh 给出的可用 worktree 列表,以及状态文件中 state != "done" 的在用 worktree(capacity.sh#L15-L43)。
步骤 3 —— 收集任务要素:每个任务需要 objective(做什么)、branch(如 feat/my-feature,未给则从 objective 推导)、pr_number(在已有 PR 上工作时必填,供验证用)、steps(按顺序的 checkpoint 名)。idle_threshold_seconds 仅当用户提及时才询问(默认 300)。永远不要求用户指定 worktree——从 find-spare.sh 自动分配。
步骤 4 —— 每任务生成一个代理:
SPARE_LIST=$(bash $SKILLS_DIR/find-spare.sh $(git rev-parse --show-toplevel))
# 对每个任务取下一行 spare:
WORKTREE_PATH=$(echo "$SPARE_LINE" | awk '{print $1}')
SPARE_BRANCH=$(echo "$SPARE_LINE" | awk '{print $2}')
# 带 PR 号与必需步骤:
WINDOW=$(bash $SKILLS_DIR/spawn-agent.sh "$SESSION" "$WORKTREE_PATH" "$SPARE_BRANCH" "$NEW_BRANCH" "$OBJECTIVE" "$PR_NUMBER" "pr-address" "pr-test")
# 无 PR 的新工作:
WINDOW=$(bash $SKILLS_DIR/spawn-agent.sh "$SESSION" "$WORKTREE_PATH" "$SPARE_BRANCH" "$NEW_BRANCH" "$OBJECTIVE")
状态文件不存在时初始化(repo 从 git remote 推导,供 verify-complete.sh 与监督者使用):
REPO=$(git remote get-url origin 2>/dev/null | sed 's|.*github\.com[:/]||; s|\.git$||' || echo "")
jq -n \
--arg session "$SESSION" \
--arg repo "$REPO" \
--argjson threshold 300 \
'{active:true, tmux_session:$session, idle_threshold_seconds:$threshold,
repo:$repo, loop_window:null, supervisor_window:null, last_poll_at:0, agents:[]}' \
> ~/.claude/orchestrator-state.json
可选地挂上 Discord webhook:
jq --arg hook "$DISCORD_WEBHOOK_URL" '.discord_webhook = $hook' ~/.claude/orchestrator-state.json \
> /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
步骤 5 —— 启动机械保姆:
LOOP_WIN=$(tmux new-window -t "$SESSION" -n "orchestrator" -P -F '#{window_index}')
LOOP_WINDOW="${SESSION}:${LOOP_WIN}"
tmux send-keys -t "$LOOP_WINDOW" "bash $SKILLS_DIR/run-loop.sh" Enter
jq --arg w "$LOOP_WINDOW" '.loop_window = $w' ~/.claude/orchestrator-state.json \
> /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
步骤 6 —— 立即开始监督:见下一节的轮询循环;绝不另开监督窗口。
监督者的自持轮询循环
监督者是反应式的——只在工具完成或用户发消息时才被触发。为此要构造一个不依赖用户的自持轮询:
- 每次轮询以
run_in_background: true+ 前置 sleep 启动:sleep 120 && tmux capture-pane -t autogpt1:0 -p -S -200 | tail -40 # 对每个活跃窗口类似 - 后台任务通知后读取 pane 输出并采取动作;
- 立即排程下一次后台轮询——这是循环存活的关键;
- 全部代理
done/escalated后停止排程。
技能文档特别强调:永远不要对用户说"我会每 2-3 分钟轮询一次"——没有触发器,这句话什么都不做;必须真正启动后台任务。
每轮轮询的检查清单:
# 1. 读状态
cat ~/.claude/orchestrator-state.json | jq '.agents[] | {window, worktree, branch, state, pr_number, checkpoints}'
# 2. 对每个 running/stuck/idle 代理捕获 pane
tmux capture-pane -t SESSION:WIN -p -S -200 | tail -60
按观察结果决策:
| 看到什么 | 动作 |
|---|---|
| Spinner / 工具运行中 | 什么都不做——代理在干活 |
空闲 ❯ 提示符且无 ORCHESTRATOR:DONE |
停滞——发送带状态文件中 objective 的针对性 nudge |
| 错误循环卡死 | 发送带精确错误与解法的定向修复 |
| 等待输入 / 提问 | 经 tmux send-keys 回答并解除阻塞 |
| CI 红 | gh pr checks PR_NUMBER --repo REPO,告诉代理具体哪一项失败 |
| GitHub abuse 限流错误 | nudge:"等 60 秒再继续,每次回复间隔 sleep 3" |
| 上下文被压缩 / 代理迷失 | 发送恢复指令:读状态文件中该 agent 记录 + gh pr view PR_NUMBER --json title,body |
输出中出现 ORCHESTRATOR:DONE |
用 GraphQL 查真实未解决线程数,>0 则 re-brief;=0 则跑 verify-complete.sh |
从状态文件轮询所有窗口,而不是靠记忆:
jq -r '.agents[] | select(.state | test("running|idle|stuck|waiting_approval")) | .window' ~/.claude/orchestrator-state.json
凡是在 spawn-agent.sh 之外手工加的窗口,必须立刻补进状态文件。
七、worktree 生命周期与「永不自动杀窗」
spare/N 分支 → spawn-agent.sh(--session-id UUID)→ 窗口 + feat/分支 + claude 运行中
↓
CHECKPOINT:<step>(随步骤完成逐个出现)
↓
ORCHESTRATOR:DONE
↓
verify-complete.sh:checkpoint ✓ + 0 线程 + CI 绿 + 无新鲜 CHANGES_REQUESTED
↓
状态 → "done",发通知,窗口保持打开
↓
用户/监督者显式要求回收
↓
recycle-agent.sh → 回到 spare/N(重新空闲)
窗口永不自动杀死:worktree 停在任务分支、会话保持存活——代理已完成工作,但窗口、git 状态与 Claude 会话全部保留,直到你选择回收。恢复 done 或崩溃会话:
# 按存储的 session ID 恢复(首选——精确会话、完整上下文)
claude --resume SESSION_ID --permission-mode bypassPermissions
# 或恢复该 worktree 目录中最近一次会话
cd /path/to/worktree && claude --continue --permission-mode bypassPermissions
经 tmux 恢复时新建窗口并把命令送进去,然后更新状态文件中的窗口地址:
NEW_WIN=$(tmux new-window -t SESSION -n WORKTREE_NAME -P -F '#{window_index}')
tmux send-keys -t "SESSION:${NEW_WIN}" "cd /path/to/worktree && claude --resume SESSION_ID --permission-mode bypassPermissions" Enter
jq --arg old "SESSION:OLD_WIN" --arg new "SESSION:NEW_WIN" \
'(.agents[] | select(.window == $old)).window = $new' \
~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
--continue 会恢复包括所有工具调用、文件编辑与上下文在内的完整对话历史,代理从断点精确继续。手工回收的完整命令:
bash ~/.claude/orchestrator/scripts/recycle-agent.sh SESSION:WIN WORKTREE_PATH spare/N
jq --arg w "SESSION:WIN" '.agents |= map(if .window == $w then .state = "recycled" else . end)' \
~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
八、完成校验:verify-complete.sh 的六项检查
verify-complete.sh WINDOW 是「ORCHESTRATOR:DONE」的严格闸门,检查顺序刻意敏感(verify-complete.sh#L2-L10):
1. Checkpoints —— 必需步骤是否都打了 checkpoint
2. CI complete —— 无 pending(机器人在检查运行后才发评论,必须先等)
3. CI passing —— 无 fail
4. spawned_at —— 分支上存在晚于代理生成时刻的 CI 运行(证明真实 push 过)
5. 0 unresolved threads —— 放在 CI 之后,确保机器人评论被算入
6. 无新鲜 CHANGES_REQUESTED —— 同上
源码层面的几处关键设计:
- fail-closed 原则:
gh pr view ... --json commits,latestReviews拉取失败时直接判 NOT COMPLETE,而不是静默放行(L127-L131); - CHANGES_REQUESTED 陈旧性规则:一条 CHANGES_REQUESTED 评审只有在提交时间晚于最新 commit 时才阻断验证;使用
committedDate而非updatedAt(后者会被机器人评论等任何 PR 活动刷新,制造假阴性);使用latestReviews而非reviews,评审者后来 approve 会自动使旧的 CHANGES_REQUESTED 失效(L117-L177); - 无 repo 时的降级:状态文件与 git remote 都推不出 repo 时,只验证 checkpoint 并打印警告后放行(L54-L58)——这是唯一允许跳过 GitHub 检查的分支。
通过 → run-loop 会自动流转、无需人工干预;失败 → 用失败原因 re-brief 代理。绝不手工把状态改成 done 来绕过。
re-brief 停滞代理的正确姿势
发任何 nudge 前,先确认 pane 处于空闲 ❯ 提示符——往仍在处理的 pane 里发文本会产生代理永远看不见的 [Pasted text +N lines]:
tmux capture-pane -t SESSION:WIN -p 2>/dev/null | tail -5
# 末行是 spinner(✳✽✢✶·)、"Running…" 或没有 ❯ —— 等 10–15 秒再查
确认空闲后:
OBJ=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .objective' ~/.claude/orchestrator-state.json)
PR=$(jq -r --arg w SESSION:WIN '.agents[] | select(.window==$w) | .pr_number' ~/.claude/orchestrator-state.json)
tmux send-keys -t SESSION:WIN "You appear stalled. Your objective: $OBJ. Check: gh pr view $PR --json title,body,headRefName to reorient."
sleep 0.3
tmux send-keys -t SESSION:WIN Enter
若 agent 记录里设了 image_path,附加一句 "Re-read context at IMAGE_PATH with the Read tool."
代理的自恢复协议
spawn-agent.sh 会自动把以下指令注入每个 objective(见 spawn-agent.sh#L124 的拼装文本):
如果你的上下文被压缩、忘记了该做什么,运行:
cat ~/.claude/orchestrator-state.json | jq '.agents[] | select(.window=="SESSION:WIN")'以及gh pr view PR_NUMBER --json title,body,headRefName来重新定向。 每完成一个步骤就单独一行输出CHECKPOINT:<step-name>。
给代理传图片
tmux send-keys 只能传文本,无法往 pane 粘贴原始图片。给代理视觉上下文的标准做法:
- 把图片存到稳定路径:
cp "$USER_PROVIDED_PATH" /tmp/orchestrator-context-$(date +%s).png; - 在 objective 字符串里引用路径,例如:
OBJECTIVE="Implement the layout shown in /tmp/orchestrator-context-1234567890.png. Read that image first with the Read tool to understand the design." - 代理用
Read工具直接读图——Claude Code 代理是多模态的。
命名约定固定为 /tmp/orchestrator-context-<timestamp>.png,这样监督者 re-brief 时知道找哪张图。
九、/pr-test 串行化规则与最终评估
为什么必须串行
/pr-test 与 /pr-test --fix 运行本地 Docker + 集成测试,依赖共享端口、共享数据库和共享构建缓存。两个 /pr-test 同时跑会导致端口冲突与数据库损坏。规则:任意时刻只允许一个 /pr-test,由监督者串行化。
监督者拥有测试队列:
- 代理们并行做
pr-review与pr-address是安全的(只 push 代码、回复 GitHub); - 某个 PR 需要本地测试时,把它加入你的心智队列——不要给代理
pr-test步骤; - 由你自己顺序执行
/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER --fix; - 通过
tmux send-keys把结果回喂给相关代理:tmux send-keys -t SESSION:WIN "Local tests for PR #N: <paste failure output or 'all passed'>. Fix any failures and push, then output ORCHESTRATOR:DONE." sleep 0.3 tmux send-keys -t SESSION:WIN Enter - 等 CI 确认变绿后才可标记代理 done。
多个 PR 同时等待测试时,先测进展最靠前的(pending CI 检查最少);上一个跑完才开下一个。
最终评估:脚本是闸门,人是裁判
verify-complete.sh 只能阻止过早标记,无法判断工作是否真的合格——那是监督者的职责。当 run-loop 把代理标为 pending_evaluation 并通知你时,必须完成以下三步:
第 1 步:亲自跑 /pr-test(必需、串行、用 TodoWrite 排队)
- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/NNNN — <feature description>
- [ ] /pr-test https://github.com/Significant-Gravitas/AutoGPT/pull/MMMM — <feature description>
/pr-test 可能「偷懒」给出含糊输出,此时带完整上下文重跑:
/pr-test https://github.com/OWNER/REPO/pull/PR_NUMBER
Context: This PR implements <objective from state file>. Key files: <list>.
Please verify: <specific behaviors to check>.
结果评估铁律:任何头条功能场景 PARTIAL 都是即时阻断项——不批准、不标 done、不接受 ORCHESTRATOR:DONE:
| /pr-test 结果 | 动作 |
|---|---|
| 全部头条场景 PASS | 进入评估第 2 步 |
| 任一头条场景 PARTIAL | 立即 re-brief 代理 |
| 任一头条场景 FAIL | 立即 re-brief 代理 |
PARTIAL 的含义是「功能只部分工作」——例如 Apply 按钮从未出现、或 AI 没有返回任何 action block:代理完成了目标的一部分而非全部。PARTIAL/FAIL 时的处理:① 不标记 done;② 用具体失败场景 re-brief,例如:
tmux send-keys -t SESSION:WIN "PARTIAL result on /pr-test — S5 (Apply button) never appeared. The AI must output JSON action blocks for the Apply button to render. Fix this before re-running /pr-test."
sleep 0.3
tmux send-keys -t SESSION:WIN Enter
③ 把状态改回 running;④ 等新 ORCHESTRATOR:DONE 后再从头重跑 /pr-test。只有全 PASS 才有资格批准——PASS 与 PARTIAL 的混合即为失败。 技能文档记述了一次真实教训:一个 PR 曾在 S5 PARTIAL 的情况下被误批——AI 从未输出 JSON action block 导致 Apply 按钮根本不出现,修复其实已在代理触手可及,只因 PARTIAL 未被当作阻断项而漏过。
第 2 步:自主评估四查:读 PR diff 对照 objective(代码是否真的实现了要求);读已解决线程(是真修复还是无改动直接 resolve);查 CI 运行名(有无不该通过的可疑重试);查 PR 描述(标题、摘要、测试计划是否齐全)。
第 3 步:裁决:
- 全 PASS + 评估良好 → 标
done,告知用户 PR 已就绪,询问是否关窗; - 任一 PARTIAL/FAIL → re-brief,状态回
running; - 即便全 PASS 但评估发现缺口 → re-brief 具体缺口,状态回
running。
# 正面评估通过后才标记 done
jq --arg w "SESSION:WIN" '(.agents[] | select(.window == $w)).state = "done"' \
~/.claude/orchestrator-state.json > /tmp/orch.tmp && mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
绝不基于脚本输出就标记 done——监督者握有完整 objective 上下文,脚本没有。
十、线程解决完整性:防「假完成」的 GraphQL 稽核
这是全系统最常见的失败模式:代理调用 resolveReviewThread 让未解决计数归零,却根本没修代码——产生能通过 verify-complete.sh 的假「完成」信号。
唯一合法的解决顺序:① 读线程理解诉求 → ② 做真实代码修改 → ③ git commit + git push → ④ 在线程中回复 commit SHA(如 "Fixed in abc1234")→ ⑤ 然后才调用 resolveReviewThread。「Accepted」「Acknowledged」之类的回复不算解决,只有真实 commit 才算。
监督者必须用 GraphQL 亲验线程计数,永远不信代理的"0 unresolved"自报。第一步取总数,第二步分页遍历所有页(大 PR 上 first:100 只会漏掉第一页之后的线程):
# 取总数
TOTAL=$(gh api graphql -f query='{ repository(owner: "OWNER", name: "REPO") { pullRequest(number: PR) { reviewThreads { totalCount } } } }' \
| jq '.data.repository.pullRequest.reviewThreads.totalCount')
# 分页统计未解决
CURSOR=""; UNRESOLVED=0
while true; do
AFTER=${CURSOR:+", after: \"$CURSOR\""}
PAGE=$(gh api graphql -f query="{ repository(owner: \"OWNER\", name: \"REPO\") { pullRequest(number: PR) { reviewThreads(first: 100${AFTER}) { pageInfo { hasNextPage endCursor } nodes { isResolved } } } } }")
UNRESOLVED=$(( UNRESOLVED + $(echo "$PAGE" | jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length') ))
HAS_NEXT=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
CURSOR=$(echo "$PAGE" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
[ "$HAS_NEXT" = "false" ] && break
done
echo "Unresolved: $UNRESOLVED"
未解决 >0 即代理未完成任务——带真实计数与规则 re-brief。此外还应稽核假解决:分页收集所有已解决线程,凡最后一条评论不含 "Fixed in"、"Removed in"、"Addressed in"(附 commit 链接)的已解决线程都应追查——代理可能虚假解决,或需要解释的假阳性。每条代理 objective 都应包含该禁令原文,并作为 pr-address 工作的第 1 号规则。
GitHub 限流的两种形态
| 错误 | HTTP 状态 | 成因 | 恢复 |
|---|---|---|---|
body 含 {"code":"abuse"} |
403 | 次级限流——短时间写操作(评论、mutation)过多 | 等 2–3 分钟,60 秒通常不够 |
API rate limit exceeded |
429 | 主级限流——每小时读调用过多 | 等到 X-RateLimit-Reset 时间戳 |
预防:代理回复线程的 API 调用之间必须 sleep 3;未解决线程超过 20 条时提高到 sleep 5。看到 403 abuse:① nudge 代理停止所有 API 写操作、等 2 分钟后以 sleep 3 间隔恢复;② 等待期间不要再 nudge——第二次 nudge 会重置限流时钟。
十一、停机条件与全局规则
何时停止舰队(active = false)
以下检查全部满足才可停:
| 检查项 | 验证方式 |
|---|---|
所有代理为 done 或 escalated |
`jq '[.agents[] |
| 所有 PR 有 0 未解决评审线程 | 每 PR 的 GraphQL isResolved 检查 |
| 所有 PR CI 绿,且运行触发于代理最后一次 push 之后 | gh run list --branch BRANCH --limit 1 时间戳 > 状态中的 spawned_at |
| 无新鲜 CHANGES_REQUESTED(晚于最新 commit) | verify-complete.sh 检查此点——陈旧的前置评审被忽略 |
无未经人工审阅的 escalated 代理 |
有则先上报用户 |
不要仅因代理输出了 ORCHESTRATOR:DONE 就停——那是验证的信号,不是停止的信号。但用户明确说 "stop" / "shut down" / "kill everything" 时即使代理仍在跑也应停:
jq '.active = false' ~/.claude/orchestrator-state.json > /tmp/orch.tmp \
&& mv /tmp/orch.tmp ~/.claude/orchestrator-state.json
LOOP_WINDOW=$(jq -r '.loop_window // ""' ~/.claude/orchestrator-state.json)
[ -n "$LOOP_WINDOW" ] && tmux kill-window -t "$LOOP_WINDOW" 2>/dev/null || true
优雅停机不回收运行中的 worktree——代理可能仍在任务中途;跑 capacity.sh 查看仍在进行的工作。注意 run-loop.sh 每轮开头都会检查 .active,置 false 后它会自行退出(run-loop.sh#L180-L183)。
全局关键规则
技能文档将 16 条铁律收束为:脚本承担重活(不重写其逻辑);从不让用户挑 worktree;从不重启运行中的代理(只对 idle kick);自动确认 settings 对话框(Down+Enter);所有 spawn 一律 --permission-mode bypassPermissions;3 次 kick 后升级;状态写入永远原子化(.tmp → mv);不批准 worktree 范围外的破坏性命令;验证不过永不回收;不建 TASK.md 文件(有提交风险,上下文持久化靠状态文件 + gh pr view);re-brief 停滞代理要读状态文件取 objective 并经 tmux 发送;ORCHESTRATOR:DONE 是验证信号而非验收信号;受保护 worktree 绝不作 spare;图片经文件路径传递(/tmp/orchestrator-context-<ts>.png);send-keys 长消息必须拆分;所有窗口从状态文件动态派生(绝不硬编码窗口数);代理重新派任务时立刻更新状态文件记录;无 commit 禁止 GraphQL resolveReviewThread;代理声称 "0 未解决线程" 后必须亲自 GraphQL 复核。
十二、小结:这套编排架构的可借鉴之处
从 SKILL.md 与配套脚本看,orchestrate 技能给出的是一套可迁移的多代理编排方法论,其核心取舍值得借鉴:
- 判断与机械分离:LLM 监督者只做需要上下文与判断的事(解读失败、定向 re-brief、最终评估),脚本层只做确定性事务(重启、批准、checkpoint 解析),且脚本通过稳定路径自拷贝抵御分支切换;
- 单一事实源 + 原子写:一切以 JSON 状态文件为准,
jq写.tmp再mv,配合损坏文件预校验与 trap 清理,保证轮询链在异常下不雪崩; - 完成信号与验收信号解耦:
ORCHESTRATOR:DONE只触发pending_evaluation,真正放行必须经过六项顺序敏感的校验与人工级评估,PARTIAL 视同 FAIL; - 对「假完成」的制度化防御:CHANGES_REQUESTED 陈旧性判定、线程解决需附 commit SHA、GraphQL 分页全量复核——全部针对代理可能「刷指标」的失败模式;
- 资源隔离与回收闭环:git worktree + spare/N 分支构成可自动回收的执行池,受保护 worktree 规则防止回收动作自我伤害。
适用前提:本机有 tmux、jq、gh CLI 与可写家目录;仓库以 worktree 池方式组织并预置 spare/N 分支;Claude Code 支持 --session-id/--resume/--continue 与 --permission-mode bypassPermissions。技能脚本位于 .claude/skills/orchestrate/scripts/,状态文件默认 ~/.claude/orchestrator-state.json,均可通过 ORCHESTRATOR_STATE_FILE 环境变量重定向。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00