fix: spawn_workers 返回 Agent 指令(非 Skill)+ run_in_background
根因:Skill 工具只加载指令到当前上下文,不会创建子 Agent。
Agent 工具默认阻塞调用,Eng 派发后卡住无法进入 monitor。
修复:
- spawn_workers() 返回 Agent 调用参数:{description, subagent_type, prompt, run_in_background}
- run_in_background: true 确保 Eng 不被子 Worker 阻塞
- commands/eng.md 明确标注 Agent 工具 + run_in_background
派发模式:Eng → Agent(run_in_background:true) × N → monitor 轮询 → merge
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -56,20 +56,47 @@ python "$HOME/plugins/airplan/scripts/airplan.py" --mode eng --project . --sub d
|
||||
dispatch 返回 `{waveId, taskIds, dispatchPath}`。**然后按以下步骤操作,不可跳过**:
|
||||
|
||||
1. 从 dispatch 返回值中取 `taskIds` 列表
|
||||
2. 对 `taskIds` 中的**每个** `tid`,顺序执行:
|
||||
a. 读 `AirPlan/state/airarc/reviews/task-graph.json`,从 `nodes[tid].task` 取任务描述文本
|
||||
b. 调用 `Skill` 工具启动子代理:
|
||||
- `skill`: `"airplan-v2:do"`
|
||||
- `args`: `"--sub enter --task-id {tid} --task-text '{task描述}' --project ."`
|
||||
c. 每个 `Skill` 调用自动以 `fork_context=false` 运行,创建隔离的 Do Worker
|
||||
3. 所有 Worker spawn 完成后,进入 monitor 状态
|
||||
4. Worker 完成后,对其 `result.json` 调用 `--sub merge --result <path>`
|
||||
5. merge 后 `task-graph.json` 节点状态自动同步为 DONE,不会被重复派发
|
||||
2. 对 `taskIds` 中的**每个** `tid`,在同一条消息中并发调用 `Agent` 工具(**必须** `run_in_background: true`,否则 Eng 会被阻塞等待子 Agent 完成):
|
||||
```
|
||||
Agent(
|
||||
description: "Do Worker: {tid}",
|
||||
subagent_type: "general-purpose",
|
||||
run_in_background: true,
|
||||
prompt: """你是 AirDo Worker,任务 ID: {tid}。
|
||||
项目路径: {project_root}
|
||||
|
||||
## 任务
|
||||
{task_text}
|
||||
|
||||
## 文件范围
|
||||
{files}
|
||||
|
||||
## 完成标准
|
||||
{done_when}
|
||||
|
||||
## 工作流程
|
||||
1. 先运行 `python scripts/airplan.py --mode do --sub enter --task-id {tid} --task-text '{task_text}' --project .` 初始化
|
||||
2. 读取项目文件,理解现有代码结构
|
||||
3. 实现任务需求,修改/创建源代码文件
|
||||
4. 完成后运行 `python scripts/airplan.py --mode do --sub finish --task-id {tid} --result <result_path>`
|
||||
|
||||
## 约束
|
||||
- 只修改属于此任务的文件
|
||||
- 完成后必须运行 finish 命令
|
||||
- 遇到无法解决的问题时返回 blocked 状态
|
||||
"""
|
||||
)
|
||||
```
|
||||
3. 每个 `Agent` 创建**独立子 Agent**,上下文不继承 Eng 对话(满足 INV-2 `fork_context=false`)
|
||||
4. 所有 Worker spawn 完成后,进入 monitor 状态
|
||||
5. Worker 完成后,对其 `result.json` 调用 `--sub merge --result <path>`
|
||||
6. merge 后 `task-graph.json` 节点状态自动同步为 DONE,不会被重复派发
|
||||
|
||||
**注意**:
|
||||
- 一个 task 对应一次 `Skill("airplan", ...)` 调用,多个 task 可以在同一条消息中并发发起
|
||||
- task-text 从 task-graph.json 获取,不是猜的
|
||||
- Do Worker 子代理的 allowed-tools 是 `[Read, Glob, Grep, Bash, Write, Edit]`,Agent 框架自动应用
|
||||
- 使用 `Agent` 工具,不是 `Skill` 工具——`Skill` 只加载指令到当前上下文,不创建子 Agent
|
||||
- `Agent` 创建的每个子 Agent 拥有独立上下文,天然满足上下文隔离
|
||||
- 多个 `Agent` 调用可以在同一条消息中并发发起
|
||||
- prompt 中的 task-text/files/done_when 从 `task-graph.json` 获取
|
||||
|
||||
### monitor
|
||||
|
||||
|
||||
@@ -615,8 +615,12 @@ def _select_ready_tasks(project_root: Path, max_count: int) -> list[str]:
|
||||
|
||||
|
||||
def spawn_workers(project_root: Path, task_ids: list[str]) -> list[dict]:
|
||||
"""T-1.21: 为每个 ready 任务准备 spawn 指令,返回 Agent 可直接消费的 Skill 调用参数列表。
|
||||
不实际启动子进程——启动由 Agent 框架的 Skill 工具完成。
|
||||
"""T-1.21: 为每个 ready 任务准备 Agent 派发指令。
|
||||
|
||||
使用 Agent 工具(非 Skill 工具)spawn 隔离子 Agent。
|
||||
每个子 Agent 有自己的上下文,不继承 Eng 的完整对话。这正是 INV-2(fork_context=false)。
|
||||
|
||||
返回 Agent 调用参数列表,Eng Agent 遍历列表逐个调用。
|
||||
"""
|
||||
tg_json = airplan_root(project_root) / "state" / "airarc" / "reviews" / "task-graph.json"
|
||||
graph = TaskGraph.load(tg_json) if tg_json.exists() else TaskGraph()
|
||||
@@ -624,9 +628,40 @@ def spawn_workers(project_root: Path, task_ids: list[str]) -> list[dict]:
|
||||
for tid in task_ids:
|
||||
node = graph.nodes.get(tid)
|
||||
task_text = node.task if node else ""
|
||||
files = node.files_dirs if node else ""
|
||||
done_when = node.done_when if node else ""
|
||||
|
||||
prompt_parts = [
|
||||
f"你是 AirDo Worker,任务 ID: {tid}。",
|
||||
f"项目路径: {project_root}",
|
||||
"",
|
||||
f"## 任务",
|
||||
f"{task_text}",
|
||||
"",
|
||||
f"## 文件范围",
|
||||
f"{files}" if files else "(无限制)",
|
||||
"",
|
||||
f"## 完成标准",
|
||||
f"{done_when}" if done_when else "编译通过,无回归",
|
||||
"",
|
||||
"## 工作流程",
|
||||
"1. 先运行 `python scripts/airplan.py --mode do --sub enter --task-id {tid} --task-text '{task_text}' --project {project_root}` 初始化 Worker 状态",
|
||||
"2. 读取项目文件,理解现有代码结构",
|
||||
"3. 实现任务需求,修改/创建源代码文件",
|
||||
"4. 完成后运行 `python scripts/airplan.py --mode do --sub finish --task-id {tid} --result AirPlan/state/airdo/tasks/{tid}/result.json`",
|
||||
"",
|
||||
"## 约束",
|
||||
"- 只修改属于此任务的文件",
|
||||
"- 完成后必须运行 finish 命令",
|
||||
"- 遇到无法解决的问题时返回 blocked 状态",
|
||||
]
|
||||
prompt = "\n".join(prompt_parts).format(tid=tid, task_text=task_text, project_root=project_root)
|
||||
|
||||
instructions.append({
|
||||
"skill": "airplan-v2:do",
|
||||
"args": f"--sub enter --task-id {tid} --task-text '{task_text}' --project .",
|
||||
"description": f"Do Worker: {tid}",
|
||||
"subagent_type": "general-purpose",
|
||||
"prompt": prompt,
|
||||
"run_in_background": True, # 关键:后台运行,Eng 不阻塞
|
||||
"taskId": tid,
|
||||
"taskText": task_text,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user