-
设计见 docs/done/system-design/28-agent-registry-unification.md。 改造前 Agent 本体散在三处(system/agents/ 11 个、system/templates/ 50 个、 data/companies/{id}/agents/ 17 个),同一个 AgentScaffold 被三个 baseDir 实例化。 后果是同一角色在 N 家公司下有 N 份互不相干的副本,模板更新不会同步; org-coder / org-planner 这类走 hireFromBaseMetaAgent 造出来的本体 legacy_path 指回自己公司目录,永远无法被第二家公司复用。 本次: - G1 单一注册表:system/templates/ 整体并入 system/agents/,base → _base。 7 个同名 id 逐个裁决,被丢弃的一份归档到 docs/_archive/ 而非删除。 source_kind(template/global_agent/company_hired) → origin(builtin/created)。 - G2 运行态下沉:CLI 的 cwd 从本体根改为 system/agents/{aid}/{cid}/{taskId}/。 memory / conversations 落公司态 {aid}/{cid}/,按公司隔离不串。 sessionPool 的 key 含 (companyId, taskId),两家公司调同一 agent 不复用会话。 - G2b 产物不下沉:交付物仍落 data/companies/{cid}/,system/agents/ 只留运行痕迹。 写入边界按链路分叉——graph 节点走节点私有目录(被 indexNodeOutputs 索引), chat 走公司 workspace,同一提示词里不再出现两条互斥指令。 - G3 公司只存引用:data/companies/{id}/agents/ 删除,新增 company_agents 关联表。 不复用 org_graph_nodes——后者是执行流图,work 层本体被雇时不写它,天生覆盖不全。 - G4 缺则创建:本体缺失时从 _base 复制骨架进 system/agents/{id}/, agent-creator 填 agent.md/soul.md 后才从 draft 转 active(哨兵判据, 骨架未填时哨兵仍在,不许被 hire)。产物即刻是全系统可复用的本体。 新增 server/services/agent-locator.js 收编原先散在 6 处的寻址逻辑。 边界守卫(graph-engine)的豁免从「本体根上五个文件名」放宽为整棵 system/agents/: 新本体的骨架本来就要进 git,gitignore 挡不住 ?? system/agents/{newId}/, 旧判据会把一个真正成功的招聘节点判成 boundary_escape fail。 存量迁移见 scripts/migrate-agent-registry.js(幂等,备份到 data/_migration-backup-*)。 真 Claude CLI E2E 已验证:新建公司 → orggraph-planning 自动规划 → 自动招聘 org-planner/org-coder/org-validator 三节点流水线; data/companies/{cid}/agents/ 不存在,company_agents 4 行, system_agents 56→56(零新建私有本体,纯复用),git status 零增量。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
## 1. 子 run 转终态不回写父节点 → 任务永久卡死(界面走查发现) 复现:org 节点的子 work run 第一次失败 → 父节点判 fail → 入口节点没有 fail 出边 → blocked → 用户在中栏把子 run 修好跑到 completed → **父节点仍钉在 blocked, root run 也钉住,org 层永远推不下去**。唯一人工出路是对 org 节点点「重试」, 而那会把整张子图重置到下一个 attempt,刚跑完的成果全丢。 根因:propagateToParent 的幂等守卫写的是 `parentNode.status !== 'running' → return`, 判据用错了维度——它想防「同一个结果被结算两次」,实际拦掉的是「子 run 换了个结果」。 subgraph 节点的状态本来就是从子 run 派生的,子 run 换了终态就该重新派生。 修法:新增 reviveParentFromChild,只认「转好」(fail/blocked → success), 且要求 parentNode.child_run_id 正是这个子 run。恢复走 **blocked → ready → running → success 三步合法转换**,而不是给全局放宽 NODE_TRANSITIONS——转换表是给所有调用方的 安全网,不该为一条恢复路径把 blocked → success 对外开放;三条 node.status 事件 也让「这个节点是被救回来的」在 timeline 上看得见。 补测试时才暴露的第二半:父 run 多半也钉在 blocked,而 RUN_TRANSITIONS 没有 blocked → completed,settleRun 在转换非法时是**静默跳过**的。所以复活时要先把 父 run 从 blocked 拉回 running(这条合法),否则节点复活了 run 还是卡着。 ## 2. 边界守卫把并行 agent 的写算到当前节点头上(真 CLI 冒烟的断点) 真跑实测:招聘模板库里没有的角色走 hired_from_base,自动流水线起 agent-creator, 而它的 CLI workDir 就是 system/agents/agent-creator/——一个被 git 跟踪的目录。 它在里面重写自己的 todo.md、删一条 scheduled_tasks,被整仓 git diff 扫到, 归属算到当时正在跑的 work 节点,一个**真正成功**的节点被判 fail。 只要这些运行态文件还被 git 跟踪,每次含 hired_from_base 的招聘都稳定复现。 修法:splitUnattributable 把 agent 运行态文件(todo.md / plan.md / memory.md / _lessons.md / .claude/scheduled_tasks.json)从越界证据里摘出去,单独记一条 unattributable,不参与判罚。摘除范围刻意窄——同目录下的 agent.md / config.json 等源码仍然算越界。 路径提取按「首个空白之前」切而不是固定偏移:diffRepoStatus 已去掉前导空格, porcelain 状态位长度不定(修改类 "M path" 1 字符、未跟踪类 "?? path" 2 字符), 固定 slice(3) 恰好对**修改类**失效——而 agent-creator 改自己 todo.md 正是修改类。 这条单独立了一个回归用例。 ponytail: 这是缓解不是根治。根治要把运行态文件从 git 移出去(27-P3B W13), 本次只保证守卫不再冤枉节点;同一次招聘里 agent 若写了运行态文件之外的源码, 仍会算到当前节点头上。 测试 760/760 全绿(72 文件,起点 753/71):新增 graph-engine-revive-parent.test.js 4 条 + graph-boundary.test.js 3 条。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
测试 736/736 全绿(68 文件,起点 719/66,净增 17 条,未改任何既有断言)。 W1 · seed 接进启动流程 + 悬空行回收 - server/index.js 与 agent-server.js 均在 listen() 前调 runStartupSeedSafely() (幂等、失败降级记日志、不挡服务启动) - seedTemplates() 此前**没有任何运行时调用方**,只有测试与手动脚本引用, 于是 system/templates/ 新增本体永远进不了 system_agents,而 work_graph_nodes.system_agent_id 是指向该表的外键 - 占位 id 回收:legacy_path 已失效的 company_hired 行改为可被 template 就地转正, 保留行 id 以保外键。此前 seedTemplates 遇占用即 skip,导致污染不可逆: lookupSharedAgentMd 恒 null → hire 静默退回全量拷贝 → 模板改动对已雇公司不生效 - 生产库实查:company_hired 0 行(原 10 条悬空全清)、template 45、 verifyCounts().ok=true、foreign_key_check 空 W2 · 项目根分配(四环闭合) - ensureProjectRoot 复用 path-resolver.allocateProjectRoot(sandbox 模式) - 分配 → 进 agent_call 与 planning 两处提示词 + callAgent metadata → 落 artifacts/project-root.json(wx 独占)→ readProjectRootRecord 读回 - 子 run 经 createRun 继承 root_run_id,跨层共享同一项目根 - 核实 data/companies/ 在 .gitignore 内,故项目根内的合法写入不会被 W3 守卫误报 W3 · 产出边界守卫(部分,文档如实标注) - 真 CLI 是子进程直接写盘,引擎拦不住,守卫只能拒收结果、不能回滚 - 7 条「拦不住」的情形逐条列明;文档自述「在 cwd 隔离做掉之前, 真跑仍应在可丢弃的工作副本里做」 W4 · 修验收 fail-open(性质严重) - benchmark-validator / testdata-validator 的 Step 表教 agent 写 verdict:"fail", 而引擎 FAILING_VERDICTS={attributed,inconclusive} 不含它 → 契约非法的产物 被判 success 放行。失效方向是放行不是拦截 - org-validator 的审计字段 verdict → audit_verdict:它与 work 层判词写同一路径 (nodes/<nodeRunId>/attempt-<n>.json),此前只靠「该节点恰好是 subgraph」 才没被 readNodeProduct 当判词读,属隐患藏在巧合里 - 新增防线 template-verdicts.test.js,从 graph-engine.js 源码取 FAILING_VERDICTS / ATTRIBUTION_REASONS 扫全部模板;经三次变异验证有效 (改回 "fail" / 改回 verdict / 造 "ok" 均变红并报精确行号) 真 CLI 冒烟未执行,两条理由(终验 agent 自行判断,符合任务给的门槛): ① W3 按其自身文档明确「不是沙箱」且要求真跑在可丢弃副本里做,而当前是主仓库、 持有未提交交付物、且有并行 agent 在写,W3 自述的误判项必然触发; ② 断点已零成本静态证明:getAgentConfig 解析裸名只查 system/agents/、 从不查 system/templates/,resolveAgentName 不做按需 hire,实测 prd-planning / sdd-planning / benchmark-validator / testdata-validator / validator-triager / org-validator 全部 throw「Agent not found」。花 $3-8 复现已知断点无意义。 故「做完这四件就能真跑」不成立,真跑挪到 W5/W6 之后。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>