首页
/ OpenHuman 并行 Subagent 工程实践:长时任务回滚下的工作保全与路径作用域治理

OpenHuman 并行 Subagent 工程实践:长时任务回滚下的工作保全与路径作用域治理

2026-09-05 10:19:23作者:郜逊炳

本文基于 OpenHuman 仓库中一条真实沉淀下来的工程记忆笔记(位于 .claude/projects/-Users-enamakel-work-tinyhumansai-openhuman-2/memory/subagent-output-can-be-lost.md),复盘一次多 Subagent 并行开发中"约 20 分钟的子代理产出被大幅回滚"的事故,并给出三条可落地的规则:及时提交已验证的工作切片、为并行子代理划分互不重叠的路径作用域、以及对子代理做存活检查与结果复核。读完本文,你将掌握在 OpenHuman 这类 Rust 内核 + 前端双栈仓库中安全编排并行 Agent 的完整方法论,并能直接使用仓库自带的 agent-batch 工具链做路径所有权冲突检测。

一、事故复盘:一次 20 分钟的 Subagent 产出为何几乎全部丢失

原文档记录的是 OpenHuman 在构建 agent_workflows 特性期间的两个真实事故:

事故 1:长时 Subagent 因 API 错误被回滚。 一个名为 codecrusher 的 Subagent(其角色定义见 .claude/agents/codecrusher.md,是一个定位为"把架构方案变成生产级代码"的实现型 Agent)连续运行了约 20 分钟,随后命中 API socket 错误,其工作树(working tree)中的产出被大幅回滚——约 10 个文件中只有 2 个幸存。值得注意的是,这些产出当时全部处于"未提交"状态。

事故 2:Subagent 在长命令上死循环。 另一个负责跑测试 harness 的 Subagent 陷入循环,反复执行 5 分钟一跑一停的后台 cargo test 命令却始终不收敛,最终需要人工介入:用 TaskStop 停掉该子任务,再执行 pkill -f "cargo test" 清理游离的 cargo 进程。

这两起事故暴露了并行 Agent 开发的共性风险,而不是某个工具的实现缺陷:

  1. 未提交的工作树是回滚的唯一受害者。 回滚、崩溃、环境清理等操作只会影响尚未固化的变更。已经 commit 的工作对这类事故免疫。
  2. 长命令循环会让 Subagent "假活"。 一个反复触发 5 分钟 cargo test 的子代理看起来一直在动,实际上没有收敛,主线程若无存活探测机制就会一直空等。

OpenHuman 的仓库结构天然适合做并行子代理分工:Rust 核心代码集中在 src/openhuman/(下设 memory/cron/channels/ 等数十个模块目录),Tauri + React 前端集中在 app/ 目录(app/src/components/ 下 800 余个组件文件)。这种"目录即边界"的布局,正是后面路径作用域规则的物理基础。

二、核心规则 1:及时提交已独立验证的工作切片(Checkpoint 原则)

原文档给出的第一条规则是:当一个 Subagent 完成了一个"自包含、可独立验证"的切片时(例如只改 app/ 下的前端),应当立即将其作为 checkpoint 提交,而不是让它在未提交状态下悬挂,同时其他 Agent 继续运转——因为未提交的工作是唯一有回滚风险的。

把这条规则展开成可操作的判定标准:

  • 自包含(self-contained):该切片不依赖其他并行 Agent 尚未完成的改动。例如"仅重写 app/src/features/ 下某面板组件"是自包含的;"新增 Rust 模块并同步修改前端调用"则不是,因为跨栈依赖会形成提交顺序约束。
  • 可独立验证(independently-verified):切片自身有验证手段——前端切片可以跑 npm test(见 .claude/agents/dev-agent.md 中约定的 npm test / cd src-tauri && cargo test 双测试入口),Rust 切片可以跑定向 cargo test。验证不通过就谈不上 checkpoint。
  • 及时(promptly):提交动作发生在切片验证通过的当时,而不是整批 Agent 全部结束后的"统一提交"。事故 1 中 10 个文件的产出之所以几乎全丢,正是因为验证完成后它们仍悬挂在工作树里。

这里有一个与"规则 2"配合的细节:被派出去并行干活的子代理本身不应拥有提交权,由主线程在收口后统一 reconcile + commit(见下一节)。Checkpoint 提交针对的是"主线程确认子代理切片通过独立验证后,替它固化",二者不矛盾——提交权收拢在主线程,但提交时机要提前到验证通过的那一刻

三、核心规则 2:并行 Subagent 的路径作用域必须互不重叠

原文档第二条规则:给并行 Subagent 分配互不重叠的路径作用域(例如一个只动 src/,一个只动 app/),并明确告知它们不要提交,由主线程负责协调与提交。

这一条在 OpenHuman 仓库中不只是口头约定,而是有配套工具链强制执行的。

3.1 agent-batch 工具链:把"作用域不重叠"从约定变成可校验的 Spec

仓库在 scripts/agent-batch/ 下提供了一套零依赖(仅 Node 20+ 标准库)的批量 Agent 编排工具,其中 scripts/agent-batch/overlap.mjs 专门用于"证明批次 Spec 中各 Agent 的路径所有权互不重叠":

node scripts/agent-batch/overlap.mjs <spec.json>
# 所有 agent 拥有互不相交的路径前缀时退出码 0;存在任何冲突时退出码 1

它的判定逻辑在 scripts/agent-batch/lib.mjs 中:

  • validateSpec 对 Spec 做静态校验:顶层必须含 batch_idbase_repobase_branchtracking_issueagents 五个字段;单个批次硬上限 25 个 Agent;每个 Agent 必须含 id(格式 a01a999)、issuetitlebranch(格式 cursor/<id>-<issue>-<slug>)、owned_paths
  • owned_paths 只允许目录前缀,不允许 glob(含 *? 直接报错),且必须是仓库相对路径——这从源头保证了作用域是可穷举、可比较的;
  • findOverlaps 对任意两个 Agent 的路径两两比较,发现 exact(完全相同)或 prefix(前缀包含)即记为冲突;
  • 特例:allowed_shared_paths 中的路径不计为冲突——这是为"不可避免地共享的文件"(如测试覆盖矩阵)预留的逃生口。

3.2 真实的批次 Spec 长什么样

docs/agent-workflows/pilot-batch-example.json 给出了一个三个 Agent 的示例批次,正好演示了作用域划分与共享路径声明:

{
  "batch_id": "example-pilot-2026-05-15",
  "base_repo": "tinyhumansai/openhuman",
  "base_branch": "main",
  "tracking_issue": 1480,
  "agents": [
    {
      "id": "a01",
      "issue": 9001,
      "title": "tighten memory namespace migration logging",
      "branch": "cursor/a01-9001-memory-namespace-logging",
      "owned_paths": ["src/openhuman/memory/"],
      "allowed_shared_paths": ["docs/TEST-COVERAGE-MATRIX.md"],
      "labels": ["cursor-agent", "pilot", "batch:example-pilot-2026-05-15"]
    },
    {
      "id": "a02",
      "issue": 9002,
      "title": "deduplicate cron RPC validation helpers",
      "branch": "cursor/a02-9002-cron-rpc-dedupe",
      "owned_paths": ["src/openhuman/cron/"],
      "allowed_shared_paths": ["docs/TEST-COVERAGE-MATRIX.md"]
    },
    {
      "id": "a03",
      "issue": 9003,
      "title": "settings panel a11y labels",
      "branch": "cursor/a03-9003-settings-a11y-labels",
      "owned_paths": ["app/src/components/settings/"],
      "allowed_shared_paths": ["docs/TEST-COVERAGE-MATRIX.md"]
    }
  ]
}

三个 Agent 分别独占 src/openhuman/memory/src/openhuman/cron/app/src/components/settings/——注意第三个落在前端目录,与原文档"一个在 src/、一个在 app/"的分工示例同构;三者都把 docs/TEST-COVERAGE-MATRIX.md 声明为共享路径,这样覆盖矩阵的常规更新不会触发冲突告警。此外,同一目录下还有 validate.mjslaunch.mjsstatus.mjs 等脚本,分别承担 Spec 校验、批次拉起与状态查询,配套测试位于 scripts/agent-batch/tests/

从源码结构看,这套"Spec 先行 + 静态冲突检测 + 统一分支命名"的设计,正是事故 1 与事故 2 的直接对策:作用域不重叠,回滚一个 Agent 的工作树就不会误伤其他 Agent 的文件;所有权可穷举,主线程收口时才能明确"哪些文件归谁"。

四、核心规则 3:Subagent 存活检查与卡死处置

原文档第三条规则:当 Subagent 失去响应时,通过文件 mtime + TaskOutput(block:false) 检查存活;如果它卡在长后台命令上循环,就 TaskStop 它,pkill 掉游离的 cargo 进程,然后由主线程直接把活干完。

拆解这套处置流程:

  1. 判定"安静"不等于"死",用证据而非感觉。 子代理可能只是在做长耗时操作(例如编译、跑测试)。检查其负责目录内文件的 mtime 是否仍在推进,配合非阻塞方式拉取任务输出(TaskOutputblock:false 语义即"看一眼就返回,不挂起等待"),两项结合才能区分"在干活"与"卡死了"。
  2. 循环型卡死的特征识别。 事故 2 中的模式是:每 5 分钟一轮的后台 cargo test 反复出现、无收敛迹象。识别要点是同一长命令周期性重复而产出物(测试报告、文件变更)没有净增长
  3. 止损动作顺序。TaskStop 停止子任务本身,再 pkill -f "cargo test" 清理被该任务派生出来、但已脱离任务生命周期的后台进程——只杀任务不杀进程,cargo 的编译/测试子进程会继续占用 CPU 与 target 目录锁;清理完成后不要重开一个新子代理去猜哪里卡住,而是主线程直接接手收尾,减少再次分叉的开销。

五、核心规则 4:始终独立复核 Subagent 声称的验证结果

原文档最后一条规则:永远独立复核子代理声称的结果,并交叉引用了同目录下的另一条记忆笔记 [[cargo-check-vs-test-verification]](.claude/projects/-Users-enamakel-work-tinyhumansai-openhuman-2/memory/cargo-check-vs-test-verification.md)——曾有一个子代理报告"测试通过",而实际上测试 cfg 从未编译成功。

这条笔记给出了本仓库中"验证不彻底"的具体机理,值得完整继承:

  • cargo check 只编译 lib,不编译 #[cfg(test)] 模块和同级的 *_tests.rs 文件。一个域可以 cargo check 绿灯,而它自己的测试模块里藏着未解析的 import(use super::* 测试文件缺导入)、pub(crate) use 重导出私有函数的 E0364、测试专用构造点缺字段等错误——全部要到 cargo test 编译 test cfg 时才暴露。笔记中记载的真实案例:cargo check 绿灯放行过一个新域,但其 select_tests.rs 有未解析的 WorkflowPhase/PHASE_* 导入、ops.rs 有一处 pub(crate) use slugify(E0364),全在 cargo test 编译时现形。
  • 给被广泛构造的 struct(例如 PromptContext)新增字段时,必须更新所有构造点,包括 tests/*.rs 里的集成测试(如 tests/personality_e2e.rs),而 cargo check 不会替你发现这些遗漏。

由此得到的复核标准(原文原意):

  1. 验证"Rust 改动真的能编译且测试有效",运行 cargo test --manifest-path Cargo.toml --no-run——它会编译全部 test target;之后再实际跑测试。
  2. 绝不基于 cargo check 的退出码、或一次显示 "0 passed; N filtered out" 的 cargo test <filter> 运行来报告"N 个测试通过"——后者只说明过滤器没匹配到任何东西,不是成功。结果行必须出现非零的 passed 计数。

这条规则与本文主题的关系在于:它是"checkpoint 原则"的质量闸门。只有经过独立复核的切片才配得上被及时提交;把"子代理口头报告的通过"直接提交,等于把一次虚假绿灯固化进历史。

六、落地清单:在 OpenHuman 中编排并行 Subagent 的完整流程

综合原文档四条规则与仓库配套工具,完整工作流如下:

  1. 写批次 Spec。docs/agent-workflows/pilot-batch-example.json 的结构定义每个子代理的 owned_paths(目录前缀)与 allowed_shared_paths;Rust 侧尽量按 src/openhuman/ 下的模块目录划界(memory/cron/inference/ 等),前端侧按 app/src/ 下的功能目录划界(components/settings/features/ 等)。
  2. 静态冲突检测。 node scripts/agent-batch/overlap.mjs <spec.json>,退出码 0 才允许派发;冲突信息会打印成 a01 ↔ a02: "src/a/" vs "src/" (prefix) 的形式,逐条消除。
  3. 派发时写明两条禁令。 子代理(无论 codecrusher 还是 test-agent不得提交不得越出 owned_paths 修改文件
  4. 主线程盯存活。 对每个子代理周期性地:看其目录文件 mtime 是否推进 + 非阻塞拉取任务输出。发现长命令循环(如反复 5 分钟 cargo test)→ TaskStop + pkill -f "cargo test" → 主线程直接收尾。
  5. 切片验证通过即提交。cargo test --no-run(Rust 侧)或 npm test(前端侧)独立复核子代理声称的结果,确认结果行有非零 passed 计数后,由主线程立即 commit 固化该切片。
  6. 批次收口。 全部切片提交后,再处理 allowed_shared_paths(如 docs/TEST-COVERAGE-MATRIX.md)的合并更新。

七、适用范围与限制说明

  • 本文所有结论基于 OpenHuman 仓库当前状态:agent-batch 工具链要求 Node 20+(scripts/agent-batch/lib.mjs 注释中声明零第三方依赖);Spec 校验器中 base_repo 被硬编码为 tinyhumansai/openhumanbase_branch 必须为 main、单批次最多 25 个 Agent(见 validateSpec 实现)。
  • 原文档本身是一条面向 Claude Code 工作流的记忆笔记(其 YAML frontmatter 中 type: feedback),事故描述中的 TaskStopTaskOutput(block:false) 是该环境下的工具原语;在其他 Agent 编排环境中应对应替换为等价的"停止子任务 / 非阻塞查询输出"能力。
  • 路径作用域与 checkpoint 两条规则不依赖具体工具链,可直接迁移到任何"多 Agent 并行修改同一工作树"的场景;但 agent-batch 的分支命名(cursor/<id>-<issue>-<slug>)与仓库硬编码校验属于 OpenHuman 特定约定。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384