名称: task-orchestrator
描述: 具备依赖分析、并行 tmux/Codex 执行和自愈心跳监控的自主多智能体任务编排。适用于包含多个需要协调并行执行的议题/任务的大型项目。
元数据: {"clawdbot":{"emoji":"🎭","requires":{"anyBins":["tmux","codex","gh"]}}}
使用 tmux + Codex 进行具备自愈监控能力的自主多智能体构建编排。
请同时加载 senior-engineering 技能以获取工程原则。
一个定义所有任务、其依赖关系、涉及文件及状态的 JSON 文件。
{
"project": "项目名称",
"repo": "所有者/仓库",
"workdir": "/工作目录/路径",
"created": "2026-01-17T00:00:00Z",
"model": "gpt-5.2-codex",
"modelTier": "high",
"phases": [
{
"name": "阶段 1:关键",
"tasks": [
{
"id": "t1",
"issue": 1,
"title": "修复 X",
"files": ["src/foo.js"],
"dependsOn": [],
"status": "pending",
"worktree": null,
"tmuxSession": null,
"startedAt": null,
"lastProgress": null,
"completedAt": null,
"prNumber": null
}
]
}
]
}
dependsOn 数组强制执行顺序# 1. 创建工作目录
WORKDIR="${TMPDIR:-/tmp}/orchestrator-$(date +%s)"
mkdir -p "$WORKDIR"
# 2. 克隆仓库以创建工作树
git clone https://github.com/OWNER/REPO.git "$WORKDIR/repo"
cd "$WORKDIR/repo"
# 3. 创建 tmux 套接字
SOCKET="$WORKDIR/orchestrator.sock"
# 4. 初始化清单
cat > "$WORKDIR/manifest.json" << 'EOF'
{
"project": "PROJECT_NAME",
"repo": "OWNER/REPO",
"workdir": "WORKDIR_PATH",
"socket": "SOCKET_PATH",
"created": "TIMESTAMP",
"model": "gpt-5.2-codex",
"modelTier": "high",
"phases": []
}
EOF
# 获取所有开放议题
gh issue list --repo OWNER/REPO --state open --json number,title,body,labels > issues.json
# 根据议题正文中提及的文件进行分组
# 涉及相同文件的任务应串行执行
# 为每个任务创建独立的工作树
cd "$WORKDIR/repo"
git worktree add -b fix/issue-N "$WORKDIR/task-tN" main
SOCKET="$WORKDIR/orchestrator.sock"
# 为任务创建会话
tmux -S "$SOCKET" new-session -d -s "task-tN"
# 启动 Codex(使用 ~/.codex/config.toml 中配置的 gpt-5.2-codex,reasoning_effort=high)
# 注意:模型配置在 ~/.codex/config.toml 中,而非 CLI 参数
tmux -S "$SOCKET" send-keys -t "task-tN" \
"cd $WORKDIR/task-tN && codex --yolo '修复议题 #N: 描述。运行测试,提交清晰的提交信息,推送到 origin。'" Enter
#!/bin/bash
# check_progress.sh - 通过心跳运行
WORKDIR="$1"
SOCKET="$WORKDIR/orchestrator.sock"
MANIFEST="$WORKDIR/manifest.json"
STALL_THRESHOLD_MINS=20
check_session() {
local session="$1"
local task_id="$2"
# 捕获最近的输出
local output=$(tmux -S "$SOCKET" capture-pane -p -t "$session" -S -50 2>/dev/null)
# 检查完成指示器
if echo "$output" | grep -qE "(All tests passed|Successfully pushed|❯ $)"; then
echo "DONE:$task_id"
return 0
fi
# 检查错误
if echo "$output" | grep -qiE "(error:|failed:|FATAL|panic)"; then
echo "ERROR:$task_id"
return 1
fi
# 检查停滞(提示等待输入)
if echo "$output" | grep -qE "(\? |Continue\?|y/n|Press any key)"; then
echo "STUCK:$task_id:waiting_for_input"
return 2
fi
echo "RUNNING:$task_id"
return 0
}
# 检查所有活动会话
for session in $(tmux -S "$SOCKET" list-sessions -F "#{session_name}" 2>/dev/null); do
check_session "$session" "$session"
done
当任务卡住时,编排器应执行以下操作:
等待输入 → 发送适当的响应
bash
tmux -S "$SOCKET" send-keys -t "$session" "y" Enter
错误/失败 → 捕获日志、分析、修复后重试
```bash
# 捕获错误上下文
tmux -S "$SOCKET" capture-pane -p -t "$session" -S -100 > "$WORKDIR/logs/$task_id-error.log"
# 终止并重启,附带错误上下文
tmux -S "$SOCKET" kill-session -t "$session"
tmux -S "$SOCKET" new-session -d -s "$session"
tmux -S "$SOCKET" send-keys -t "$session" \
"cd $WORKDIR/$task_id && codex --model gpt-5.2-codex-high --yolo '上次尝试失败,错误信息:$(cat error.log | tail -20)。修复问题并重试。'" Enter
```
# 如果在阈值内没有提交,则重启
```
# 添加到 cron(每 15 分钟一次)
cron action:add job:{
"label": "orchestrator-heartbeat",
"schedule": "*/15 * * * *",
"prompt": "检查 WORKDIR 的编排进度。读取清单,检查所有 tmux 会话,自愈任何卡住的任务,如果当前阶段完成则推进到下一阶段。不要打扰人工——自行修复问题。"
}
# 1. 获取议题
gh issue list --repo OWNER/REPO --state open --json number,title,body > /tmp/issues.json
# 2. 分析依赖关系(提及的文件、显式依赖)
# 分组到阶段:
# - 阶段 1:关键/阻塞议题(无依赖)
# - 阶段 2:高优先级(可能依赖阶段 1)
# - 阶段 3:中/低优先级(依赖早期阶段)
# 3. 在每个阶段内,识别:
# - 并行批次:不同文件,无依赖 → 同时运行
# - 串行批次:相同文件或显式依赖 → 按顺序运行
编写包含所有任务、依赖关系和文件映射的 manifest.json。
# 为阶段 1 的任务创建工作树
for task in phase1_tasks; do
git worktree add -b "fix/issue-$issue" "$WORKDIR/task-$id" main
done
# 启动 tmux 会话
for task in phase1_parallel_batch; do
tmux -S "$SOCKET" new-session -d -s "task-$id"
tmux -S "$SOCKET" send-keys -t "task-$id" \
"cd $WORKDIR/task-$id && codex --model gpt-5.2-codex-high --yolo '$PROMPT'" Enter
done
心跳每 15 分钟检查一次:
1. 轮询所有会话
2. 更新清单进度
3. 自愈卡住的任务
4. 当阶段 N 的所有任务完成时 → 启动阶段 N+1
# 当任务成功完成时
cd "$WORKDIR/task-$id"
git push -u origin "fix/issue-$issue"
gh pr create --repo OWNER/REPO \
--head "fix/issue-$issue" \
--title "fix: 议题 #$issue - $TITLE" \
--body "关闭 #$issue
## 变更
[由 Codex 编排器自动生成]
## 测试
- [ ] 单元测试通过
- [ ] 手动验证"
# 在所有 PR 合并或工作完成后
tmux -S "$SOCKET" kill-server
cd "$WORKDIR/repo"
for task in all_tasks; do
git worktree remove "$WORKDIR/task-$id" --force
done
rm -rf "$WORKDIR"
| 状态 | 含义 |
|---|---|
pending |
尚未开始 |
blocked |
等待依赖项 |
running |
Codex 会话活跃 |
stuck |
需要干预(自动修复) |
error |
失败,需要重试 |
complete |
完成,准备创建 PR |
pr_open |
PR 已创建 |
merged |
PR 已合并 |
{
"project": "nuri-security-framework",
"repo": "jdrhyne/nuri-security-framework",
"phases": [
{
"name": "阶段 1:关键",
"tasks": [
{"id": "t1", "issue": 1, "files": ["ceo_root_manager.js"], "dependsOn": []},
{"id": "t2", "issue": 2, "files": ["ceo_root_manager.js"], "dependsOn": ["t1"]},
{"id": "t3", "issue": 3, "files": ["workspace_validator.js"], "dependsOn": []}
]
},
{
"name": "阶段 2:高优先级",
"tasks": [
{"id": "t4", "issue": 4, "files": ["kill_switch.js", "container_executor.js"], "dependsOn": []},
{"id": "t5", "issue": 5, "files": ["kill_switch.js"], "dependsOn": ["t4"]},
{"id": "t6", "issue": 6, "files": ["ceo_root_manager.js"], "dependsOn": ["t2"]},
{"id": "t7", "issue": 7, "files": ["container_executor.js"], "dependsOn": []},
{"id": "t8", "issue": 8, "files": ["container_executor.js", "egress_proxy.js"], "dependsOn": ["t7"]}
]
}
]
}
阶段 1 中的并行执行:
- t1 和 t3 并行运行(不同文件)
- t2 等待 t1(相同文件)
阶段 2 中的并行执行:
- t4, t6, t7 可以同时开始
- t5 等待 t4, t8 等待 t7
--model gpt-5.2-codex-high使用 codex exec --full-auto 时,沙箱:
- 无网络访问 — git push 失败,提示“无法解析主机”
- 受限的文件系统 — 无法写入 ~/nuri_workspace 等路径
心跳应检查:
1. Shell 提示符空闲 — 如果 tmux 窗格显示 用户名@主机名 路径 %,则工作器已完成
2. 未推送的提交 — git log @{u}.. --oneline 显示未推送到远程的提交
3. 推送失败 — 在输出中查找“无法解析主机”
检测到后,编排器(而非工作器)应:
1. 从沙箱外部推送提交
2. 通过 gh pr create 创建 PR
3. 更新清单并通知
# 在心跳中,对每个任务:
cd /tmp/orchestrator-*/task-tN
if tmux capture-pane 显示 shell 提示符; then
# 工作器完成,检查未推送的工作
if git log @{u}.. --oneline | grep -q .; then
git push -u origin HEAD
gh pr create --title "$(git log --format=%s -1)" --body "关闭 #N" --base main
fi
fi