OpenHuman 并行 Subagent 工程实践:长时任务回滚下的工作保全与路径作用域治理
本文基于 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 开发的共性风险,而不是某个工具的实现缺陷:
- 未提交的工作树是回滚的唯一受害者。 回滚、崩溃、环境清理等操作只会影响尚未固化的变更。已经
commit的工作对这类事故免疫。 - 长命令循环会让 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_id、base_repo、base_branch、tracking_issue、agents五个字段;单个批次硬上限 25 个 Agent;每个 Agent 必须含id(格式a01–a999)、issue、title、branch(格式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.mjs、launch.mjs、status.mjs 等脚本,分别承担 Spec 校验、批次拉起与状态查询,配套测试位于 scripts/agent-batch/tests/。
从源码结构看,这套"Spec 先行 + 静态冲突检测 + 统一分支命名"的设计,正是事故 1 与事故 2 的直接对策:作用域不重叠,回滚一个 Agent 的工作树就不会误伤其他 Agent 的文件;所有权可穷举,主线程收口时才能明确"哪些文件归谁"。
四、核心规则 3:Subagent 存活检查与卡死处置
原文档第三条规则:当 Subagent 失去响应时,通过文件 mtime + TaskOutput(block:false) 检查存活;如果它卡在长后台命令上循环,就 TaskStop 它,pkill 掉游离的 cargo 进程,然后由主线程直接把活干完。
拆解这套处置流程:
- 判定"安静"不等于"死",用证据而非感觉。 子代理可能只是在做长耗时操作(例如编译、跑测试)。检查其负责目录内文件的 mtime 是否仍在推进,配合非阻塞方式拉取任务输出(
TaskOutput的block:false语义即"看一眼就返回,不挂起等待"),两项结合才能区分"在干活"与"卡死了"。 - 循环型卡死的特征识别。 事故 2 中的模式是:每 5 分钟一轮的后台
cargo test反复出现、无收敛迹象。识别要点是同一长命令周期性重复而产出物(测试报告、文件变更)没有净增长。 - 止损动作顺序。 先
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不会替你发现这些遗漏。
由此得到的复核标准(原文原意):
- 验证"Rust 改动真的能编译且测试有效",运行
cargo test --manifest-path Cargo.toml --no-run——它会编译全部 test target;之后再实际跑测试。 - 绝不基于
cargo check的退出码、或一次显示 "0 passed; N filtered out" 的cargo test <filter>运行来报告"N 个测试通过"——后者只说明过滤器没匹配到任何东西,不是成功。结果行必须出现非零的 passed 计数。
这条规则与本文主题的关系在于:它是"checkpoint 原则"的质量闸门。只有经过独立复核的切片才配得上被及时提交;把"子代理口头报告的通过"直接提交,等于把一次虚假绿灯固化进历史。
六、落地清单:在 OpenHuman 中编排并行 Subagent 的完整流程
综合原文档四条规则与仓库配套工具,完整工作流如下:
- 写批次 Spec。 按 docs/agent-workflows/pilot-batch-example.json 的结构定义每个子代理的
owned_paths(目录前缀)与allowed_shared_paths;Rust 侧尽量按 src/openhuman/ 下的模块目录划界(memory/、cron/、inference/等),前端侧按 app/src/ 下的功能目录划界(components/settings/、features/等)。 - 静态冲突检测。
node scripts/agent-batch/overlap.mjs <spec.json>,退出码 0 才允许派发;冲突信息会打印成a01 ↔ a02: "src/a/" vs "src/" (prefix)的形式,逐条消除。 - 派发时写明两条禁令。 子代理(无论 codecrusher 还是 test-agent)不得提交、不得越出 owned_paths 修改文件。
- 主线程盯存活。 对每个子代理周期性地:看其目录文件 mtime 是否推进 + 非阻塞拉取任务输出。发现长命令循环(如反复 5 分钟
cargo test)→TaskStop+pkill -f "cargo test"→ 主线程直接收尾。 - 切片验证通过即提交。 用
cargo test --no-run(Rust 侧)或npm test(前端侧)独立复核子代理声称的结果,确认结果行有非零 passed 计数后,由主线程立即 commit 固化该切片。 - 批次收口。 全部切片提交后,再处理
allowed_shared_paths(如 docs/TEST-COVERAGE-MATRIX.md)的合并更新。
七、适用范围与限制说明
- 本文所有结论基于 OpenHuman 仓库当前状态:
agent-batch工具链要求 Node 20+(scripts/agent-batch/lib.mjs 注释中声明零第三方依赖);Spec 校验器中base_repo被硬编码为tinyhumansai/openhuman、base_branch必须为main、单批次最多 25 个 Agent(见validateSpec实现)。 - 原文档本身是一条面向 Claude Code 工作流的记忆笔记(其 YAML frontmatter 中
type: feedback),事故描述中的TaskStop、TaskOutput(block:false)是该环境下的工具原语;在其他 Agent 编排环境中应对应替换为等价的"停止子任务 / 非阻塞查询输出"能力。 - 路径作用域与 checkpoint 两条规则不依赖具体工具链,可直接迁移到任何"多 Agent 并行修改同一工作树"的场景;但
agent-batch的分支命名(cursor/<id>-<issue>-<slug>)与仓库硬编码校验属于 OpenHuman 特定约定。
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 StartedRust0623
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