Commit b43106fc8d264219e415383b358913db894437bc

Authored by yibo
1 parent d1d06391

docs(graph-agent): 端到端流程设计 + Org Graph 重定位为 company 级 workflow + 统一命名规范

新增四份文档:

- 27-e2e-task-execution-flow.md — 跨阶段总览:建公司 → 下达任务 → org graph
  逐节点执行 → work graph 规划与循环 → 验收收敛。核心发现:这套流程 70-80%
  已在 docs/prd/newfunction/_spec/ 里被完整设计(DevLoop 的 L1/L2/L3 三层循环、
  BugTriager 四分类归因、分级预算熔断、contract_hash 闸门),DevLoop 实为本
  架构唯一跑通的参考实现,通用引擎应从它抽象而来。
- 27-example-dev-company-walkthrough.md — 参考场景走查,把架构在一家具体公司
  上跑一遍数(各阶段 JSON 形态、fan-out/fan-in 图、成本估算 ~44 次调用、
  按阻塞程度排序的 8 个断点清单),作为实现靶子与端到端验收场景。
- 27-naming-conventions.md — 统一命名规范 {作用域}-{职能}。消歧三种 benchmark
  (skill- / agent- / task-);OKR 体系正名(goal → okr,第 1 层 Goal → Objective);
  规划类统一为 orggraph-planning / workgraph-planning / brd-prd-sdd-planning。
- 27-p3-p4-work-graph-console.md — P3/P4 合并设计:Work Graph 执行引擎(支持
  启动/下一步/暂停/停止/重试,手动与自动共用同一 advance 原语)+ 执行控制台页面。

重写 27-p2-org-graph.md 至 v0.4:Org Graph 从「静态汇报结构」重定位为
「company 级别生成的 workflow」,与 Work Graph 同构(condition/edge_kind 执行流边),
两层共用同一套 schema、执行引擎与画布组件。org_graph_edges 从未写入过数据,
可直接 DROP 重建,零迁移负担;hire() 不再需要 reportsTo(图整体规划整体写入),
P2 对 company-service.js 的侵入缩至零。

同步修正总纲至 v0.3:Org Graph/Work Graph 术语定义、§3.1 架构图、§3.3 整节;
标注 P1 已实现(226489d);P6「DevLoop 收编」从「可选」改为「应前置」。

修正三处过期陈述:P1 已实现(非设计稿)、runProject 已完整实现(非 501)、
UnitsDAG.vue 已实现 Kahn 分波 DAG 渲染(画布应扩展而非重写)。
并记录一处待处理冲突:graph-service.js:44 已落地的全量唯一索引与 P2 需要的
部分唯一索引冲突,实现 T1 时必须先 DROP。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/todo/system-design/27-e2e-task-execution-flow.md 0 → 100644
  1 +# 27-E2E · 端到端任务执行流程 — 从建公司到验收收敛
  2 +
  3 +> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的跨阶段总览文档,串起 P2(Org Graph)、P3/P4(Work Graph 执行)、P5(自我进化)。
  4 +> **版本**: v0.1
  5 +> **日期**: 2026-08-15
  6 +> **前置**: [27-P2 Org Graph 落地](./27-p2-org-graph.md)、[27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md)、**以及本文档最重要的参考源** `docs/prd/newfunction/_spec/`(DevLoop 的完整 MVCP 规格,尤其 `loops-overview.md` / `loops-l1.md` / `loops-l2.md` / `loops-l3.md` / `sdd-process.md` / `plan-cost-concurrency.md`)。
  7 +> **配套**: [27-EXAMPLE 参考场景走查](./27-example-dev-company-walkthrough.md) —— 本文档讲机制,那份文档把机制在一家具体公司上跑一遍数。两份对照着读。
  8 +
  9 +---
  10 +
  11 +## 一、这份文档要回答什么
  12 +
  13 +本文档把口述的七阶段端到端流程结构化,然后回答三个问题:
  14 +
  15 +1. 这个流程和已经写好的 P2 / P3-P4 设计有什么差异?
  16 +2. 哪些地方没有考虑到?
  17 +3. 这个流程是否是这样实现的?
  18 +
  19 +---
  20 +
  21 +## 二、三条核心结论(先给答案)
  22 +
  23 +### 结论 1:这套流程 70-80% 已经在本仓库被详细设计过,但它藏在 DevLoop 的规格里,而不在 27 号文档体系里
  24 +
  25 +`docs/prd/newfunction/_spec/` 是一套 50 份文件、按 **MVCP(Model / View / Control / Process)四象限**组织的完整 SDD。它已经定义了:
  26 +
  27 +- **三层嵌套循环 L1 ⊂ L2 ⊂ L3**(`loops-overview.md`),带完整状态机、退出条件、回滚目标、分级预算,甚至给每层配了颜色 token(L1 Emerald / L2 Amber / L3 Violet)。
  28 +- **失败归因的四分类调度**(`loops-l2.md` + `sdd-process.md` P-2):`contract_mismatch` / `impl_bug` / `spec_ambiguous` / `infra_flake`,每类决定「改不改设计文档、重置哪些节点、烧不烧预算」。
  29 +- **分级预算与熔断**(`sdd-process.md` P-7):`retry_budget=5`(每 Unit)、`l2_budget=3`(每 PRD rev)、`token_budget=5M`(每项目)、单 agent 墙钟 10min。
  30 +- **成本与并发预算的数字版**(`plan-cost-concurrency.md`):每个 agent 每次调用的 token 估算、典型项目总成本(中项目 ≈ 410K token ≈ $4.8)、三层并发上限、墙钟估算、6 个监控指标及告警阈值。
  31 +- **前端组件全集**(`sdd-view.md` V-1..V-11),且**代码已经存在**:`web/src/components/devloop/` 下 8 个组件,含 `UnitsDAG.vue`(SVG + Kahn 分波布局的 DAG 渲染器)、`LiveAgentStream.vue`(带 Last-Event-ID 断线重连的 SSE 消费)、`UnitDrawer.vue`(节点详情抽屉)。
  32 +
  33 +**这直接影响你的问题「这个流程是否是这样实现的」——答案是:在"一家写代码的公司"这个具体场景下,是的,而且已经实现了大半。** `loop-controller.js` 的 `runProject()` 已完整实现(不是我在 [27-P3/P4](./27-p3-p4-work-graph-console.md) 文档里写的 501,那条陈述已过期,见 §九),包含 Kahn 拓扑分波、L2 闸门、Integrator、Triager 四分支调度、预算熔断、SSE 埋点。
  34 +
  35 +### 结论 2:DevLoop 就是你这套架构的一个「硬编码实例」
  36 +
  37 +把两者并排看,对应关系是逐项的:
  38 +
  39 +| 你描述的通用概念 | DevLoop 里的硬编码版本 |
  40 +|---|---|
  41 +| Org Graph(planner → coder → validator) | 固定的 6 个 agent:PM → Architect → Coder/Tester → Integrator → Triager |
  42 +| org node 调 planning subagent 规划自己的 work graph | Architect 生成 `units.json`(一张带 `depends_on` 的 DAG)——**图是被规划出来的,但规划者是写死的 Architect** |
  43 +| work graph 节点 = system agent 引用 | Unit = 一次 Coder + Tester 调用 |
  44 +| work graph 内 loop 回退 | **L1**:`runUnit` 的 retry-with-reflection,`retry_budget=5` |
  45 +| fan-out 并行 code subagent → fan-in 整合 → integration test | `_groupUnitsByWave()` Kahn 分波 → L2 闸门 → Integrator 跑真实进程 e2e |
  46 +| 出 bug 时判断「重新 planning 还是跳回某节点」 | **L2**:BugTriager 输出 `category` 四分类,LoopController 据此分支 |
  47 +| 全局 validator + 人工介入 | **L3**:human feedback loop + `sign-off` |
  48 +
  49 +**所以 27 号文档 §五把「DevLoop 收编」列为 P6(可选),这个定位是低估了。** DevLoop 不是一个将来可以顺便合并进来的边缘子系统,它是这套通用架构唯一一个已经跑通的参考实现。正确的做法是**反过来:把 DevLoop 的 L1/L2/L3 机制抽象成通用 Work Graph 引擎的默认行为**,而不是先造一个通用引擎、将来再想办法把 DevLoop 塞进去。
  50 +
  51 +### 结论 3:你的流程里有一个环节是真正全新的,而且它恰好是风险最大的一环
  52 +
  53 +第七步「不成功则判断是不是需要迭代某一个 system agent 的 skill / memory / data / LLM,先迭代 system agent,再 loop 某个 work graph」——**这一层在 DevLoop 的 L1/L2/L3 里不存在**。DevLoop 的 L3 是「找人类补充需求」,不是「让 agent 自己变强」。这一层对应的是 27 号文档的 P5(自我进化),但 P5 原本被设计成一个离线的、benchmark ≥95 才准生效的慢速通道,**而你的流程把它拉进了任务执行的在线重试循环里**。这个改动带来两个必须处理的风险,见 §八。
  54 +
  55 +---
  56 +
  57 +## 三、流程还原(七阶段)
  58 +
  59 +每阶段标注:✅ 已实现 / 📐 已设计未实现 / ⚠️ 部分存在有冲突 / ❌ 空白
  60 +
  61 +### 阶段 1 · 用户新建 company,写入目标和描述
  62 +
  63 +**状态**:✅ 已实现(`POST /api/company/create`,`company-service.js:37`)
  64 +
  65 +`company.json` 已有 `description` 字段。当前创建出来的是「没有员工的空壳公司」,响应文案提示"请通知 MasterBot 雇佣 Agent"。
  66 +
  67 +### 阶段 2 · orggraph-planning 根据描述生成基础 org graph
  68 +
  69 +**状态**:📐 已设计未实现([27-P2](./27-p2-org-graph.md) §4.8/§4.9/§4.10)
  70 +
  71 +P2 已经设计了:`system/org-graph-templates/{dev,content,ops}.json` 模板库 + `orggraph-planning` agent + 创建时自动招聘并 fire-and-forget 触发首轮对话。
  72 +
  73 +「每个 org node 可以引用已有 system agent,也可以新建一个」——这一点 P2 通过 `hire()` 的 `needs_training` 分支已覆盖(`company.js:917-940` 已有自动触发 agent-creator + innovator 的流水线)。
  74 +
  75 +⚠️ **但有一处粒度不一致**:你举例说开发型公司需要 planner / code / validator **三个**节点;P2 的 `dev.json` 模板写的是 ceo / cto / hr / devloop-pm / devloop-architect / devloop-coder / devloop-tester **七个**。这不是笔误,而是两种不同的建模视角,见 §五。
  76 +
  77 +### 阶段 3 · 用户输入一个具体的开发 task
  78 +
  79 +**状态**:⚠️ 部分存在,且**没有「给公司下达任务」的入口**
  80 +
  81 +现有任务系统在 `server/routes/agent-chat.js`,不在 `task-service.js`(后者是只读聚合器,靠时间窗口启发式猜测父子关系):
  82 +
  83 +- `POST /api/agent/task/dispatch`(行 1470):入参 `{from, to, task, priority, goalId, nodeId, autoExecute, parentTaskId}`,taskId 格式 `T-{base36}`,持久化为**全局平铺 JSON** `system/tasklog/{taskId}.json`,30 分钟去重窗口,`autoExecute` 时 HTTP 自调用触发目标 agent。
  84 +- `POST /api/agent/task/report`(行 1656):**已有的完成回调机制**——更新 todo/plan、更新 goal 节点、自动推进 Pipeline、回调唤醒上级 agent。
  85 +
  86 +❌ **缺口**:`from`/`to` 都必须是已存在的 agent 目录,**只支持 agent → agent 派发,不支持「用户 → 公司」**。你的流程第 3 步需要一个新入口:用户把任务给公司,由公司决定哪个 org node 先接手。
  87 +
  88 +⚠️ **另一处**:27 号文档 `work_graphs.goal_task_id` 这个单字段不够用。定位一个 OKR 节点需要 `(companyId, okrNodeId)`(OKR 树 per-company 存储,节点 ID 是点分层级)。建议拆成 `okr_node_id` + `task_id` 两个字段。⚠️ 另见 [27-NAMING](./27-naming-conventions.md) §六:OKR 体系当前在代码里叫 `goal`(`goals.js` / `G-001.1.1` / `data/companies/{cid}/goals/`),应正名为 `okr` / `O-001.1.1`。
  89 +
  90 +### 阶段 4 · 第一个 org node(planner)调 planning-work-graph subagent 规划自己的 work graph
  91 +
  92 +**状态**:📐 概念已在 27 号文档 §3.4 定义,执行引擎见 [27-P3/P4](./27-p3-p4-work-graph-console.md),**但 planning agent 本身未设计**
  93 +
  94 +你举的例子 `brd-planning → prd-planning → sdd-planning → benchmark → testdata → code-planner-validator → loop-validator`,与 DevLoop 的实际链路高度重合:
  95 +
  96 +| 你的节点 | DevLoop 对应物 | 状态 |
  97 +|---|---|---|
  98 +| brd / prd | PM Agent 产出多页 PRD 站点(`sdd-model.md` M-3) | ✅ 已实现 |
  99 +| sdd | Architect 产出 SDD(MVCP 四页)+ `units.json` | ✅ 已实现 |
  100 +| benchmark / testdata | Architect 同步产出 `integration/spec.json`(M-6) | ✅ 已实现 |
  101 +| code-planner-validator | Architect 的 self-check + L2 gate 判定 | ✅ 已实现 |
  102 +| loop-validator | Loop-Validator(27 号 §3.5)/ BugTriager | 📐 27 号未实现;DevLoop 版 ✅ 已实现 |
  103 +
  104 +💡 **值得注意**:DevLoop 的 `loops-l2.md` 已经就「谁来写验收标准」做过一次显式的方案选型(A/B/C 三选一),并选了 **A:由 Architect(设计者)在生成 SDD 时同步产出 `integration/spec.json`**,明确否决了 **C:由 Integrator(执行者)自己写测试**,理由是「容易写一个永远会通过的 trivial test」。你的流程让 planner 阶段产出 benchmark/testdata、让后面的 validator 阶段消费它,正好符合方案 A 的思路——**设计者定标准、执行者不定标准**。这条已经被论证过的原则应该在通用层继承下来。
  105 +
  106 +### 阶段 5 · 完成当前 org node,进入下一个 org node
  107 +
  108 +**状态**:❌ **这是与 P2 设计的核心语义冲突所在**,见 §五
  109 +
  110 +### 阶段 6 · code agent 规划 code work graph:fan-out 并行 → fan-in 整合 → integration test → 出 bug 判断重新 planning 还是跳回节点
  111 +
  112 +**状态**:⚠️ **结构全部就位,但并行没做,且有一条不变量直接挡路**
  113 +
  114 +- **分波结构**:✅ `_groupUnitsByWave()`(`loop-controller.js:523`)是标准 Kahn 拓扑分波,`units.json` 的 `depends_on` 声明 DAG,`doc-store.js` 写入前用三色 DFS 校验无环。前端 `UnitsDAG.vue` 已经能渲染这个分波布局。
  115 +- ❌ **波内实际是串行的**:`loop-controller.js:609` 明确注释 `// 串行跑(mock 同步快,不需并行)`,实现是 `for (const uid of wave) { await runUnit(...) }`。
  116 +- ⚠️ **改成并行会立刻撞上两处障碍**:
  117 + 1. `_spec/CONVENTIONS.md` §12 第一条不变量:**「任意 (pid, agent) 同时只允许 1 个 status=running」**——如果并行跑 3 个 coder subagent,它们是同一个 system agent 的三次调用,直接违反这条。
  118 + 2. `SessionPool` 的 key 是 `acp:{agentName}:{client}:{deploy}:{workDir}[:{scope}]`(`session-pool.js:29`),同 agent 同 workDir 会**复用同一个 session**——并发调用会撞在一起。必须引入 `scope` 之类的实例隔离。
  119 +- ✅ **fan-out/fan-in 的现成实现存在**,只是 DevLoop 没用它:`orchestrator.js:265` 的 `executeWithDependencies()` 用 `Scheduler.getExecutionGroups()` 分组 + `Promise.allSettled` 组内并行、组间串行(天然 fan-in 屏障),单点失败降级成 `{status:'ERROR', retryable}` 而不终结整组。另有 `agent-chat.js:2440` 的 sub-agent 批量调度(`MAX_CONCURRENCY=2`,带 `checkpoint.json` 断点续跑)。
  120 +- ✅ **「重新 planning 还是跳回原节点」这个判断已经有成熟实现**:这正是 BugTriager 的 `category` 四分类(`sdd-process.md` P-2):
  121 +
  122 +| category | 改不改设计文档 | 重置哪些节点 | 烧不烧 L2 预算 |
  123 +|---|---|---|---|
  124 +| `contract_mismatch` | ✅ 改 SDD(≈ 重新 planning) | ✅ reset 相关 unit | ✅ 烧 |
  125 +| `impl_bug` | ❌ | ✅ 只 reset 那一个(≈ 跳回节点) | ❌ 不烧 |
  126 +| `spec_ambiguous` | ❌(升 L3 找人) | ❌ | ❌ |
  127 +| `infra_flake` | ❌ | ❌(直接重跑) | ❌ |
  128 +
  129 +**这套「不同失败类型消耗不同预算」的设计比单一计数器精细得多**,而且自带防死循环:`confidence < 0.5` 自动升 L3 交给人,`agents-triager.md:397` 还规定「第 2 次 triage 自动升 spec_ambiguous」防止在错误归因上打转。
  130 +
  131 +### 阶段 7 · 全局 validator:整体判断 / 测试 / 审计 / benchmark / test data,失败则迭代 system agent 再 loop
  132 +
  133 +> ⚠️ **v0.2 重要更正**:本节 v0.1 把 benchmark / test data 理解成了**系统级基础设施**,因此去核实 `benchmark-runner.js` / `benchmark.sqlite` 并判定"是假的、不存在"。**这个理解是错的。** 实际设计是:benchmark / test data / test script 是 **work graph 里的三个 agent 节点**(`task-benchmark` / `task-test-data` / `task-test-script`),由 planning agent 动态决定加哪几个。完整设计见 §十一。下面关于既有基础设施的核实结论仍然属实,但**它们与本流程无关**——本流程不使用 `benchmark-runner.js`。
  134 +
  135 +**状态**:⚠️ 产出侧有清晰设计(§十一),**执行侧仍是空白**(产出验收标准 ≠ 执行验收标准)
  136 +
  137 +- ⚠️ **`benchmark-runner.js` 是假的**。文件头自述"MVP 版本: 使用 heuristic-based 评分"。实际算法:`successRate` 基础 70 分、描述超 20 字加 5 分、`robustness` 靠 grep 描述里有没有 "error"/"retry"/"timeout" 字符串、`speed`/`cost` 按 layer 查表推算。**测试用例数是伪造的**:`testCasesTotal = 5 + seededRandom(0,10)`。三张表(`benchmark_runs`/`comparisons`/`security_scans`)全部以 `skill_name` 为主体,**没有 agent_id / task_id / company_id 任何一列**——它是给 Skill 打分的,不能给 agent 或 task 用。
  138 +- ❌ **"test data" 作为系统概念完全不存在**。全仓库搜到的都是 agent 自己在 workspace 里写的自然语言文本。
  139 +- ✅ **唯一真实执行的评测是 DevLoop 的 integration test**(起真实进程跑 e2e,产 `integration/runs/{runId}/result.json`,含 `ok` / `failures[]` / http-trace),但它是 DevLoop 私有的。
  140 +- 💡 **`qa-judge` 的协议设计非常契合你的三级恢复,但它没有任何程序化调用入口**。它的输出协议(`system/templates/qa-judge/agent.md:223-333`)是严格 JSON,`status: "PASS" | "REWORK" | "RETRAIN_REQUIRED"`,而它的**缺陷溯源三分类几乎逐条对应你说的三级**:
  141 +
  142 +| qa-judge 分类 | 判断标准 | 输出 | 对应你的第几级 |
  143 +|---|---|---|---|
  144 +| 粗心错误 | 5 分钟内可修正,无需新知识 | `REWORK` + 具体修正位置 | L1 跳回某节点只改一部分 |
  145 +| 设计缺陷 | 需要调整设计思路 | `REWORK` + 设计建议 | L2 重新 planning 一张 work graph |
  146 +| **能力缺陷** | 反映知识框架缺失 | `RETRAIN_REQUIRED` + `capability_gaps[]` | **L4 迭代 system agent** |
  147 +
  148 + 但全仓库 grep,`qa-judge` 只出现在测试文件和模板目录里,**生产代码零引用**——它靠 agent 在对话里按 prompt 自行调用,没有调用入口、没有对 JSON 输出做 schema 校验、判决不持久化。
  149 +
  150 +---
  151 +
  152 +## 四、与 P2 / P3-P4 设计的差异汇总
  153 +
  154 +| # | 你的流程 | P2 / P3-P4 现有设计 | 差异性质 |
  155 +|---|---|---|---|
  156 +| 1 | org graph 逐节点**执行**,planner 是「执行的第一个节点」 | org graph 是**静态汇报结构**(`reports_to`/`collaborates_with`),不执行 | **语义冲突**,见 §五 |
  157 +| 2 | 开发公司 = planner / code / validator 三节点 | `dev.json` 模板 = 7 个层级化角色 | 建模粒度不一致,见 §五 |
  158 +| 3 | code 阶段 fan-out 并行多个 subagent | P3/P4 的 `advanceOne()` 明确一次只推进**一个**节点 | **能力缺失**,需重新设计 |
  159 +| 4 | 失败时判断「重新 planning vs 跳回节点」 | P4 的 Loop-Validator 只有「跳回节点」一种 | 缺一个分支;DevLoop 的四分类可直接借用 |
  160 +| 5 | 第三级:迭代 system agent 本体 | 27 号 P5 有,但被设计成离线慢通道,不在执行循环里 | **新增层级**,见 §八 |
  161 +| 6 | 验收标准 = benchmark / test data | 两份文档都没提验收标准从哪来、由谁执行 | **完全空白** |
  162 +| 7 | 用户下达具体 task | 两份文档都没有任务入口设计 | **完全空白** |
  163 +
  164 +---
  165 +
  166 +## 五、Org Graph 的定位(v0.2 已定案)
  167 +
  168 +> ✅ **本节 v0.1 提出的 A/B/C 三方案已作废,定案如下**:
  169 +>
  170 +> **Org Graph 是 company 级别生成的 workflow;Work Graph 是 org graph node 级别生成的 workflow。**
  171 +>
  172 +> 两者是**同一种东西在不同层级上的递归**,不是两种不同的结构:
  173 +>
  174 +> | | 生成者 | 生成时机 | 节点指向 | 边的语义 |
  175 +> |---|---|---|---|---|
  176 +> | **Org Graph** | orggraph-planning | 建公司时,一次 | org node agent | 执行流(success/fail 条件) |
  177 +> | **Work Graph** | workgraph-planning agent | 每个 org node 接到任务时 | system agent 引用 | 执行流(success/fail 条件) |
  178 +>
  179 +> **这意味着 [27-P2](./27-p2-org-graph.md) 现在的 org graph 模型需要改**:它把边定义成 `reports_to` / `collaborates_with`(静态汇报结构),而实际需要的是**带 success/fail 条件的执行流边**,与 work graph 的边同构。汇报关系如果还需要,应降级为节点上的元数据属性,而不是 org graph 的主干语义。
  180 +>
  181 +> **好处**:一套 schema、一套执行引擎、一套控制台 UI,在两个层级上原样复用;三层下钻(org graph → work graph → 并行子图)天然递归;"启动/下一步/暂停/停止/重试"五个操作在每一层语义完全一致。
  182 +>
  183 +> 下面保留 v0.1 的问题分析作为背景(说明为什么"静态汇报结构"这个建模行不通),A/B/C 方案表已不再适用。
  184 +
  185 +### 问题(v0.1 分析,保留作为背景)
  186 +
  187 +P2 把 Org Graph 定义为**公司的静态协作结构**——节点是角色引用,边是 `reports_to` / `collaborates_with`,它描述"HR 向 CTO 汇报",**它不执行任何东西**。
  188 +
  189 +你描述的是**执行序列**——"org planner agent 作为执行 org graph 的第一个节点"、"完成当前 org graph 节点工作,进入下一个 code graph node agent"。planner → coder → validator 是一条流水线。
  190 +
  191 +**这两件事是正交的**:planner / coder / validator 三者之间大概率没有汇报关系(他们可能都向 CTO 汇报),但他们之间有明确的执行先后。用 `reports_to` 边表达执行顺序会把两种语义混在一张图里,之后无法区分"谁是谁的上级"和"谁的产出喂给谁"。
  192 +
  193 +### ~~三个方案~~(已作废,见本节开头定案;保留供追溯)
  194 +
  195 +| 方案 | 做法 | 评价 |
  196 +|---|---|---|
  197 +| **A** | 给 `org_graph_edges` 加第三种 `relation_type='hands_off_to'`,org graph 同时承载汇报结构和执行流水线 | 简单,但把**一条固定流水线焊死在公司上**。一个 bugfix 任务可能不需要 planner,一个调研任务可能不需要 coder——任务级的差异无法表达 |
  198 +| **B(推荐)** | 每个任务规划一张**顶层 Work Graph,它的节点指向 org node**(而不是直接指向 system agent)。org graph 保持 P2 的静态定义不变 | 任务级的参与者和顺序都可变;**整套 P3/P4 引擎、控制台 UI、暂停/单步/重试全部原样复用**,因为它就是一张 work graph,只是节点类型不同 |
  199 +| **C** | B + orggraph-planning 额外产出一份 `defaultPipeline` 作为规划起点 | B 的补充,让「开发型公司默认就是 planner→coder→validator」这个直觉落地,同时保留任务级偏离的自由 |
  200 +
  201 +### 定案的落地方式
  202 +
  203 +1. **`org_graph_edges` 改为执行流边**,与 `work_graph_edges` 同构:
  204 +
  205 +```sql
  206 +-- P2 现有:relation_type IN ('reports_to','collaborates_with') ← 静态汇报结构,作废
  207 +-- 改为:
  208 +condition TEXT NOT NULL CHECK (condition IN ('success','fail')),
  209 +edge_kind TEXT NOT NULL DEFAULT 'forward' CHECK (edge_kind IN ('forward','backward')),
  210 +```
  211 +
  212 + 汇报关系若仍需要,降级为 `org_graph_nodes` 上的一个可空属性(`reports_to_node_id`),不再是图的主干语义。
  213 +
  214 +2. **两层图的递归关系**(复用 [27-P3/P4](./27-p3-p4-work-graph-console.md) 已有的 `parent_node_id`):
  215 +
  216 +```
  217 +Org Graph(company 级 workflow,orggraph-planning 建公司时生成)
  218 + 节点 = org node agent
  219 + └─ Work Graph(org node 级 workflow,该 node 接到任务时由 planning agent 生成)
  220 + 节点 = system agent 引用
  221 + └─ 嵌套子图(fan-out 出来的并行分支,P3/P4 §4.D.3 已设计)
  222 +```
  223 +
  224 +3. **执行引擎、schema 形态、控制台 UI 在两层之间完全复用**——这是这个定位最大的收益:不需要为 org 级单独造一套控制逻辑,`启动/下一步/暂停/停止/重试` 在每层语义一致,控制台天然是逐层下钻。
  225 +
  226 +---
  227 +
  228 +## 六、可以直接复用的既有机制(两份设计文档都没引用到)
  229 +
  230 +以下全部来自 `_spec/` 或已落地代码,**建议在 P3/P4 实现前先读一遍,避免重复发明**:
  231 +
  232 +1. **`contract_hash` 三方一致性校验**(`sdd-model.md` M-2/M-5 + `doc-store.js:382`):`hashContract()` 对 `contract.exports` 做递归 key 排序的规范化序列化后 sha256 取前 16 位。Coder 的 `contract_bridge` 工具校验三件事:contract.json 存在、`version` 与重算 hash 自洽、重算 hash 与 `units.json` 声明的 `contract_hash` 相等。**这解决的正是"上游产物改了,下游还停在旧契约上"这个跨节点一致性问题。**
  233 +2. **L2 闸门的 hash 校验**(`loops-l2.md`):闸门条件不是"所有节点都跑过一次",而是"所有节点 status=passed **且** `contract_hash` 与当前 SDD 一致",避免设计文档改过之后某个旧节点还停在 passed。
  234 +3. **增量重跑算法**(`loops-l3.md`):added / changed / removed / **unchanged** 四分类,`unchanged`(id 在且 hash 不变)**保持 passed 无须重跑**——"这是增量化的关键"。这直接回答了我在 P3/P4 §八.3 列为开放问题的「重试要不要级联下游」。
  235 +4. **跨节点读取隔离**(`CONVENTIONS.md` §12):**跨 unit 只允许读 `../{otherUnit}/contract.json`,禁止读 `code/`**。下游只能看到上游的契约,看不到实现。
  236 +5. **BugTriager 四分类 + 差异化预算消耗**(§三 阶段 6 的表)。
  237 +6. **分级预算与熔断**(`sdd-process.md` P-7)+ **成本/并发的数字版**(`plan-cost-concurrency.md`):三层并发上限(L1 ≤5 并行、L2 串行 1 个、系统 ≤20 ACP session、同公司 ≤3 项目、bash 子进程 ≤50)。
  238 +7. **前端组件**:`UnitsDAG.vue` 已实现 SVG + Kahn 分波布局 + 节点状态着色 + 点击下钻;`LiveAgentStream.vue` 已实现带 Last-Event-ID 重连的 SSE 消费。**我在 P3/P4 §四.B.2 写"手写分层布局算法"时不知道这个组件已经存在**,实际工作量应该是扩展它而不是从零写。
  239 +8. **任务回调链路**:`POST /api/agent/task/report` 已实现「更新 goal 节点 → 自动推进 Pipeline → 回调唤醒上级 agent」。
  240 +9. **`orchestrator.executeWithDependencies()`**:现成的 fan-out/fan-in(`Promise.allSettled` + 依赖分组 + 单点失败降级)。
  241 +10. **`memory-service.js`**(1059 行,9 个导出函数):`cerebellum/`(固化的成功执行方案,按关键词召回)、`hippocampus-index.json`(索引)、`autoReflect()`、`consolidateMemory()`。这是 L4 里"迭代 memory"唯一一块已经成熟的基础设施。
  242 +
  243 +---
  244 +
  245 +## 七、没有考虑到的地方
  246 +
  247 +### 7.1 三级恢复叠加会让熔断失效(最严重)
  248 +
  249 +你的流程有三级恢复:跳回节点 → 重新 planning → 迭代 system agent。**如果每一级都重置下一级的计数器,整个循环就没有终止条件**,而"直至最后满足这个任务的 benchmark"这句话字面上就是一个无限循环。
  250 +
  251 +具体地说:L1 的 `retry_budget=5` 在 L2 重新 planning 之后会重新计数;L2 的 `l2_budget=3` 在 L4 迭代完 agent 之后如果也重置,那么"迭代 agent → 重跑 L2 三轮 → 再迭代 agent"可以永远跑下去。
  252 +
  253 +**DevLoop 已经部分解决了这个问题**:它的 `l2_budget` 是「每个 PRD rev 3 轮」而不是「每次 L2 3 轮」,且 `impl_bug` / `infra_flake` 不烧预算。但它没有第四层,所以没遇到这个问题。
  254 +
  255 +**建议**:引入一个**任务级总预算**,跨所有层级共享、只增不减:
  256 +
  257 +```
  258 +task_budget = {
  259 + total_agent_calls: 100, // 所有层级所有 agent 调用的总次数
  260 + total_tokens: 5_000_000,
  261 + wallclock_ms: 7_200_000, // 2h
  262 + l4_iterations: 2, // 迭代 system agent 最多 2 次(最贵、副作用最大)
  263 +}
  264 +```
  265 +
  266 +任何一层的重试都从这个总池子里扣,扣完即挂起等人工。分层预算(`retry_budget` / `l2_budget`)继续保留作为**局部快速失败**信号,但总预算是最终兜底。
  267 +
  268 +### 7.2 ~~L4 迭代 public system agent 会污染其他公司~~ ✅ v0.2 已解决
  269 +
  270 +> **已由 §八 的 L4-company / L4-general 拆分解决**:在线自动迭代只碰 company 记忆分区,general 本体的更新移出任务循环、改由定时 dream 流程 + master agent/人工驱动。下面保留原始风险分析作为背景,其中提的三个缓解方案(fork 副本 / 只改私有 / 人工确认)已不再需要单独采纳。
  271 +
  272 +#### 原始风险分析(v0.1,保留作为背景)
  273 +
  274 +27 号文档 §3.2 定义 `scope=public` 的 system agent 跨公司共享记忆。如果 `coder` 是 public、被 10 家公司引用,那么"为了让 A 公司这个任务通过,迭代 coder 的 skill/memory"意味着**为了修一个任务,改了 10 家公司共用的 agent**。
  275 +
  276 +27 号文档自己已经意识到这个风险(§3.2 要求公共 `memory.md` 的写入走自省校验 + git 版本化),但那是针对"缓慢的自我进化"设计的,不是针对"任务失败了赶紧改一下再试"这种在线高频路径。
  277 +
  278 +**建议**(三选一,倾向 A):
  279 +- **A**:L4 触发时,先把该 system agent **fork 成一个 company-scoped 副本**(`scope='company'`, `owner_company_id`),只改副本,本公司后续调用指向副本。副本表现好、跑过 benchmark ≥95,再走既有的 `depositTemplate()` 链路回灌 public。**这样在线迭代的爆炸半径永远限制在一家公司内。**
  280 +- **B**:L4 只允许改 company-scoped 的私有记忆(`companies/{cid}/memory.md`),不许碰公共 `memory.md`/`skills/`。能力有限但零风险。
  281 +- **C**:L4 需要人工确认才生效。安全但破坏了自动化。
  282 +
  283 +### 7.3 谁来验收「验收标准」本身(v0.2 已部分缓解)
  284 +
  285 +planner 阶段产出 benchmark/testdata/testscript,validator 阶段用它验收。**如果产出了一个过松的标准,任何垃圾都能通过;产出了一个不可能达成的,就永远收敛不了。**
  286 +
  287 +DevLoop 就这个问题做过显式决策(`loops-l2.md` 方案 A/B/C),选择"设计者写、执行者不写",明确否决"执行者自己写测试",理由是执行者会写出"永远会通过的 trivial test"。
  288 +
  289 +✅ **§十一 的三节点设计天然符合这条原则**:`task-benchmark` / `-test-data` / `-test-script` 三个 agent 与真正写代码的 coder 是**完全不同的 system agent**,产出者和被验收者分离,不存在"自己给自己出考题"的激励问题。
  290 +
  291 +⚠️ **但仍未解决"标准写错了"这一层**。缓解手段有两个,建议都要:
  292 +1. work graph 里的 `code-planner-validator` 节点承担一项明确职责——审"这套验收标准是否可度量、是否覆盖原始需求、是否存在平凡通过的漏洞",而不只是审 PRD/SDD 本身。
  293 +2. 保留人工 sign-off(DevLoop L3 的做法)作为最终关卡,至少在 v1 阶段不要全自动闭环。
  294 +
  295 +### 7.4 并行与现有不变量冲突
  296 +
  297 +`CONVENTIONS.md` §12 第一条「任意 (pid, agent) 同时只允许 1 个 status=running」+ SessionPool 的 key 复用,两者都挡着 fan-out。必须先设计**同一 system agent 的多实例并发隔离**(给 SessionPool key 加 scope、给不变量加 instance 维度),否则"并行多个 code subagent"跑不起来。
  298 +
  299 +### 7.5 三层图的暂停/停止如何传播
  300 +
  301 +上一轮 P3/P4 §八.4 已经把"子图不继承父图暂停状态"列为刻意简化。现在变成**三层**(任务级 → org node 级 → 并行子图级),手动模式下用户点"暂停"到底暂停哪一层、正在跑的下层要不要停,会成为真实的困惑点。建议:暂停/停止**向下级联**(符合直觉),"下一步"只作用于当前查看的层级。
  302 +
  303 +### 7.6 成本量级
  304 +
  305 +按 `plan-cost-concurrency.md` 的实测估算,DevLoop 一个中等项目(8 unit、2 个 PRD rev、2 轮 L2)≈ 39 次 agent 调用、410K token、约 $4.8、墙钟 20 分钟。
  306 +
  307 +你的流程在此之上还要叠加:顶层任务图规划、每个 org node 各自的 work graph 规划、L4 的 agent 迭代 + 重新 benchmark。**粗估一个中等任务 60-100 次 agent 调用、$10-20、1-2 小时**。这个量级下:
  308 +- 「自动模式一键跑到底」需要在启动前把预估成本告诉用户
  309 +- §7.1 的任务级预算不是可选项
  310 +
  311 +### 7.7 其他
  312 +
  313 +- **失败的部分成功**:fan-out 5 个并行节点,3 成功 2 失败,fan-in 节点要不要跑?(建议:不跑,等全绿;但要有 UI 明确显示"卡在等 2 个失败节点")
  314 +- **task status 无状态机**:`system/tasklog/{taskId}.json` 的 status 是自由赋值,没有转换校验。
  315 +- **`knowledge/` 目录没有读取方**:L4 里"迭代 data"这一维,目录存在、创建时能写一次,但全仓库没有任何代码读它。要么先补读取方,要么 L4 暂时不含这一维。
  316 +- **per-agent LLM 选择不存在**:`runtime-resolver.js` 只推断 protocol/transport/launcher,**没有 model 字段**,`config.json` 里也 grep 不到 `model` 键。虽然 `agents-overview.md` 的规格表里给每个 agent 指定了模型(Architect/BugTriager 用 Opus、其余 Sonnet),但**这只存在于文档,代码里没有实现**。L4 想"换 LLM"需要从 client 定义、runtime-resolver、协议层一路新增。
  317 +
  318 +---
  319 +
  320 +## 八、L4 · System Agent 自我迭代(v0.2 已定案:拆成两条独立通道)
  321 +
  322 +把第七步正式命名为 **L4**。**关键定案:L4 按"改的是 company 分区还是 general 本体"拆成两条完全不同的通道**,它们的触发方式、时机、驱动者、风险等级都不一样:
  323 +
  324 +| | **L4-company** | **L4-general** |
  325 +|---|---|---|
  326 +| 改什么 | agent 的**公司/角色记忆分区**(`companies/{cid}/`) | agent 的**general 本体**(公共 `agent.md` / `memory.md` / skill) |
  327 +| 触发 | 任务执行过程中,validator 判定能力缺陷时 | 定时(如每日)或人工手动触发 |
  328 +| 驱动者 | 执行引擎,**自动** | **master agent 或人工** |
  329 +| 输入 | 本次任务的失败上下文 | **当天所有公司**的执行成功/失败/经验汇总 |
  330 +| 在任务循环内 | ✅ 在环内,会触发重跑 | ❌ 离线批处理,不阻塞任何任务 |
  331 +| 爆炸半径 | 一家公司 | 全局(所以才需要人/master 把关) |
  332 +
  333 +这个拆分**直接解决了 v0.1 §7.2 提出的跨公司污染风险**:在线自动迭代永远只碰 company 分区,general 本体的更新被移出任务循环、改由离线批处理 + 人工/master 驱动。v0.1 提的三个缓解方案(fork 副本 / 只改私有 / 人工确认)不再需要——这个分层本身就是更彻底的答案。
  334 +
  335 +### 8.1 L4-general 的"agent dream"流程
  336 +
  337 +离线自我更新的形态:**根据当天所有 company 的执行记录(成功、失败、经验),更新 general 本体**。类比睡眠期的记忆固化。
  338 +
  339 +🔴 **现状**:这个流程不存在,但**零件基本齐了**——`memory-service.js`(1059 行)已有 `autoReflect()`(自动反思)、`saveCerebellumPlan()` / `retrieveRelevantPlans()`(小脑:固化的成功执行方案 + 按关键词召回)、`loadHippocampusIndex()` / `syncHippocampusMd()`(海马体索引 + 人类可读版同步)、`updateMemoryMdSections()`。缺的是**跨公司的批量汇总层**:现有全部接口都是 `agentId` 单点的,没有"把这个 agent 在 10 家公司的当日记录聚合起来蒸馏一次"这一步。
  340 +
  341 +建议实现形态:一个定时任务(复用现有 `cron.json` 机制)或手动端点,对每个 public system agent:收集当日跨公司执行记录 → 调 agent 自身或专职 evolver 做蒸馏 → 产出对 general 本体的 diff → **走 27 号文档 §3.2 已定的 git 版本化 + 自省校验**(坏的迭代可 revert)→ 可选的人工确认后生效。
  342 +
  343 +### 8.2 四个可迭代维度的现状
  344 +
  345 +| 维度 | 现状 | L4-company(在线) | L4-general(离线 dream) |
  346 +|---|---|---|---|
  347 +| **memory** | ✅ `memory-service.js` 最成熟,全套读写 API | ✅ 主要手段 | ✅ 主要手段 |
  348 +| **skill** | ⚠️ 有运行时注入先例(`company.js:941-971` 招聘后 `skillDb.searchSkills` + `copyDir`),但是路由内联的,没封装成服务 | ✅ 纳入,需先封装 | ✅ 纳入 |
  349 +| **data** (`knowledge/`) | ❌ 目录存在、创建时写一次,但**全仓库没有任何代码读它** | ❌ 先补读取方 | ❌ 先补读取方 |
  350 +| **LLM** | ❌ 完全不存在 per-agent 模型选择(`runtime-resolver.js` 只推断 protocol/transport/launcher,无 model 字段) | ❌ 改动链路太长 | ❌ 同左 |
  351 +
  352 +---
  353 +
  354 +## 八bis、元 Agent 继承(v0.2 新增)
  355 +
  356 +**所有 org graph / work graph 的 node agent,都由一个元 agent 架构继承生成,统一是「全局通用本体 + 公司/角色记忆分区」的结构。**
  357 +
  358 +这条定案取代了 v0.1 §四 里"三个验收 agent 要不要按父节点特化"这个问题——答案是**不特化**:不存在 `task-benchmark` / `test-planner-benchmark` 各自独立的 agent,只有通用的 benchmark 设计能力,靠记忆分区区分它在不同 org node / 不同公司下的经验积累。agent 池不随 org node 数量膨胀,也与 [27-P1](./27-p1-virtual-reference.md) 虚拟引用寻址的初衷一致(同一份本体,多处引用,各自私有语境)。
  359 +
  360 +✅ **元 agent 在仓库里已经存在**:`system/templates/base/`("标准基座员工 Base Agent")。它已经强制了一套四段式生命周期:
  361 +
  362 +```
  363 +RESEARCH → PLAN → EXECUTE → VERIFY
  364 +```
  365 +
  366 +每阶段之间通过 `node bin/lemon.js task transition --agentId <id> --phase <next> --artifact <path>` 切换,**切换时清空上下文并换装对应的元技能**(`meta-research` / `meta-plan` / `meta-execute` / `meta-verify`);VERIFY 阶段失败则把原因写入 `_lessons.md` 并打回上一阶段。
  367 +
  368 +💡 **这本身就是一个最小 work graph**:四个节点、线性前进、末端带失败回退边。也就是说"每个 node agent 内部也是一张图"这件事已经有雏形。设计 Work Graph 引擎时应当与它对齐(要么把 base agent 的四段式改写成引擎上的一张标准图模板,要么至少保证两者的阶段语义不冲突),否则会出现"引擎层的 work graph"和"agent 内部的四段式"两套并行的状态机。
  369 +
  370 +---
  371 +
  372 +## 九、对现有文档的修订要求
  373 +
  374 +调研中发现仓库已推进,**我先前两份文档里有三条陈述已经过期或与已落地代码冲突**,需要修正:
  375 +
  376 +| # | 文档 | 错误陈述 | 事实 |
  377 +|---|---|---|---|
  378 +| 1 | [27-P2](./27-p2-org-graph.md) §九、[27-P3/P4](./27-p3-p4-work-graph-console.md) §九 | "P1 目前仍是设计稿,未实现,`graph-service.js` 尚不存在" | **P1 已实现并提交**(commit `226489d`),`server/services/graph-service.js` 已存在(104 行),配套测试 `graph-service.test.js` / `agent-manager-p1.test.js` 均在 |
  379 +| 2 | [27-P3/P4](./27-p3-p4-work-graph-console.md) §三.1 | "`runProject` 目前直接 501" | **已完整实现**(`loop-controller.js:576`),含 Kahn 分波、L2 闸门、Triager 四分支、预算熔断 |
  380 +| 3 | [27-P2](./27-p2-org-graph.md) §4.1 | 提议建部分唯一索引 `idx_org_graph_nodes_company_role_active`(带 `WHERE status='active'`) | **`graph-service.js:44` 已经建了全量唯一索引 `idx_org_graph_nodes_company_role`**(无 WHERE)。P2 若要加 `status` 列并改成部分索引,必须先 DROP 已落地的那个索引 |
  381 +| 4 | [27-P3/P4](./27-p3-p4-work-graph-console.md) §四.B.2、§六 T3 | "手写分层布局算法,不引入图形库" 作为新工作量 | `web/src/components/devloop/UnitsDAG.vue` **已实现** SVG + Kahn 分波布局,应改为"扩展既有组件" |
  382 +
  383 +此外建议:
  384 +
  385 +- **27 号总文档 §六**:把 P6「DevLoop 收编(可选)」的定位从"可选的后续整合"改为"DevLoop 是本架构的参考实现,通用引擎应从它抽象而来"(结论 2)。
  386 +- **27-P2**:**核心建模需推翻重写**——`org_graph_edges` 从 `reports_to`/`collaborates_with` 改为与 `work_graph_edges` 同构的 `condition IN ('success','fail')` + `edge_kind`;汇报关系降级为节点可空属性;§4.3 的 `reportsTo` 语义改为"接在谁后面";§4.7 前端改为复用 Work Graph 画布组件(详见该文档 v0.4 修正块)。
  387 +- **27-P3/P4**:补 `node_kind='org_node'`(§五)、fan-out 并行 + 实例隔离(§7.4)、任务级总预算(§7.1)、三层暂停级联(§7.5);把 §八.3 的开放问题用 `loops-l3.md` 的 unchanged 算法关掉。
  388 +
  389 +---
  390 +
  391 +## 十、可行性判断与落地建议
  392 +
  393 +### 这个流程能这样实现吗?
  394 +
  395 +**能,但不是从零实现——大部分是"把 DevLoop 已经跑通的东西泛化",而不是新造。** 分三类:
  396 +
  397 +- **约 50% 是"改造既有实现"**:runProject 的分波执行、Triager 四分支、预算熔断、SSE、DAG 前端组件、contract hash 校验、memory-service——都已经在跑,问题是它们绑死在"写代码"这一个场景上。
  398 +- **约 30% 是"补通用层"**:任务入口、org 级任务图(§五)、planning agent、并行实例隔离。
  399 +- **约 20% 是真空**:面向 task/agent 的真实 benchmark(现有的是 skill 专用假打分)、test data 全套、qa-judge 的程序化入口、L4。
  400 +
  401 +### 最大的结构性风险
  402 +
  403 +**整条流程的终止条件(验收)是实现程度最低的一环。** L1/L2/L3 的循环机制都相当成熟,但"跑完了到底算不算通过"这件事,现在没有任何真实执行的评测基础设施支撑(benchmark 是假的、test data 不存在、qa-judge 没有调用入口)。**先建验收,再建循环**——否则会得到一个跑得很顺畅但永远不知道该不该停的系统。
  404 +
  405 +### 建议的落地顺序
  406 +
  407 +1. **验收先行**:给 qa-judge 补程序化调用入口 + 输出 schema 校验 + 判决持久化;定义 task 级 benchmark/testdata 的存储与执行方式(可以直接抄 DevLoop 的 `integration/spec.json` + 真实进程执行)。
  408 +2. **任务入口**:补"用户 → 公司"的任务下达端点,复用既有 `task/dispatch` + `task/report` 回调链路。
  409 +3. **org graph 改为执行流图**(§五定案):边模型改造 + 复用 work graph 引擎跑通两层。此时可以端到端跑通一条**串行、无并行、无 L4** 的最小闭环(参照 [27-EXAMPLE](./27-example-dev-company-walkthrough.md) 的场景)。
  410 +4. **并行**:先解决实例隔离(SessionPool scope + 不变量),再把 `_groupUnitsByWave` 的波内串行改并行。
  411 +5. **L4**:最后做,且第一版只做 memory + skill 两个维度、fork 成 company-scoped 副本、需人工批准。
  412 +
  413 +前三步做完就能验证你描述的主干流程;4、5 是增强。
  414 +
  415 +> **v0.2 调整**:第 1 步"验收先行"的含义因 §十一 的更正而改变——**不是**"建 benchmark 基础设施"(那是误解),而是"定义验收契约格式 + 建执行器"。三个产出 agent 本身不需要新基础设施,它们就是普通 system agent。
  416 +
  417 +---
  418 +
  419 +## 十一、验收产出节点设计(v0.2 新增)
  420 +
  421 +### 11.1 三个 agent 节点,动态选择
  422 +
  423 +benchmark / test data / test script **不是系统基础设施,是 work graph 里的三个可选 agent 节点**:
  424 +
  425 +| 节点 | 产出 | 适用场景 |
  426 +|---|---|---|
  427 +| `{org-node}-benchmark` | 打分式验收标准:维度 + 权重 + 及格线 | 质量是**分数而非布尔**的场景——生成内容质量、性能指标、可读性 |
  428 +| `{org-node}-test-data` | 输入样本集:正常用例 + 边界 + 异常 | 需要**喂数据**才能验证的场景——解析器、转换器、有复杂输入空间的逻辑 |
  429 +| `{org-node}-test-script` | 可执行断言脚本 | 行为可以**用代码判定真假**的场景——API 契约、函数返回值、状态变更 |
  430 +
  431 +**由 planning agent 在规划 work graph 时动态决定加哪几个**——可以三个都加、只加一个、或某种组合。这使得"要不要加验收节点、加哪个"成为 work graph 规划的一等决策,而不是固定模板。
  432 +
  433 +例:`code-planner` 这个 org node 的 work graph 可能长成
  434 +
  435 +```
  436 +brd-planning → prd-planning → sdd-planning ─┬→ task-benchmark ──┐
  437 + ├→ task-test-data ──┼→ code-planner-validator → loop-validator
  438 + └→ task-test-script ┘
  439 +```
  440 +
  441 +三个验收节点之间**无依赖,可并行**(fan-out),汇合到 `code-planner-validator`(fan-in)。这正好是 §7.4 fan-out 能力的第一个真实用例。
  442 +
  443 +### 11.2 三者必须产出统一的「验收契约」
  444 +
  445 +三个节点的产出形态不同(分数表 / 数据集 / 脚本),但下游 validator 必须能统一消费。**否则每加一种验收节点就要改一次 validator。**
  446 +
  447 +建议:三者都写入同一个 `acceptance.json`(各占一个 section),格式定位参照 DevLoop 已有的 `integration/spec.json`(`sdd-model.md` M-6,含 `cases[]` / `steps[]` / `expect{status,json}` / `involves[]`,并支持 `{{stepN.field}}` 占位符引用前序步骤结果):
  448 +
  449 +```jsonc
  450 +{
  451 + "version": "1",
  452 + "okr_node_id": "O-001.1.1",
  453 + "task_id": "T-xxx",
  454 + "benchmark": { "dimensions": [{"name":"...","weight":0.4,"pass_threshold":80}] } | null,
  455 + "test_data": { "cases": [{"id":"...","input":{},"expect":{}}] } | null,
  456 + "test_script": { "runner": "vitest", "path": "tests/acceptance.spec.js" } | null
  457 +}
  458 +```
  459 +
  460 +三个字段各自可为 `null`(对应"这次没加这个节点"),validator 只处理非 null 的部分。
  461 +
  462 +### 11.3 「谁生成、谁执行」逐层递归(v0.2 定案)
  463 +
  464 +**产出者与执行者分离,且这个模式在每一层图上都重复出现**:
  465 +
  466 +| 层级 | 谁生成验收标准 | 谁执行验收 |
  467 +|---|---|---|
  468 +| **Org Graph**(company 级 workflow) | `planner` org node | `validator` org node |
  469 +| **Work Graph**(org node 级 workflow) | 图内的 `benchmark` / `test-data` / `test-script` 节点 | 图内的 `validator` 节点 |
  470 +
  471 +也就是说,**每个 node agent 都有自己的 benchmark,由它自身所在 work graph 的 validator / test-script / benchmark 节点来运行**——验收不是只发生在最外层的一次总检,而是每一层图收尾时都跑自己那一层的验收。这与 §八bis 元 agent 四段式末尾固定有 VERIFY 阶段,是同一个模式在不同粒度上的体现。
  472 +
  473 +**执行由 agent 节点承担,不由引擎硬编码**,保持"一切都是图上的节点"这个架构一致性;代价是每次验收要烧一次 agent 调用(计入 §7.1 的任务级总预算)。DevLoop 的现成先例是 **Integrator agent 起真实进程跑 e2e**(`agents-integrator.md`),产出 `result.json` 含 `ok` / `failures[]` / http-trace,validator 节点可直接复用这套做法跑 test script / test data。
  474 +
  475 +🔴 **剩余缺口**:benchmark 这类主观打分的判定,`qa-judge` 的输出协议(`PASS` / `REWORK` / `RETRAIN_REQUIRED` + `capability_gaps[]`)本来就是为此设计的——其中 `RETRAIN_REQUIRED` 正好是 §八 **L4-company** 的触发信号——但它目前**没有程序化调用入口**(全仓库 grep,生产代码零引用),需要补入口 + 输出 schema 校验 + 判决持久化。
  476 +
  477 +### 11.4 命名冲突提醒
  478 +
  479 +仓库里 `benchmark` 一词**已经被占用**:`benchmark-runner.js` / `data/benchmark.sqlite` / `_registry.json` 的 `benchmarkScore` / Arena ELO 排行榜,指的都是**给 Skill 打分**的那套体系(表结构以 `skill_name` 为主键,没有 agent_id/task_id 任何一列)。本文档 §十一 说的是**任务级验收标准**,两者同名但完全无关。建议在实现时给新概念换个不冲突的名字(如 `acceptance-benchmark` 或 `goal-benchmark`),否则日后 grep 和排查会很痛苦。
... ...
docs/todo/system-design/27-example-dev-company-walkthrough.md 0 → 100644
  1 +# 27-EXAMPLE · 参考场景走查 — 一家开发型公司从建立到交付一个任务
  2 +
  3 +> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的**参考场景文档**。
  4 +> **版本**: v0.1
  5 +> **日期**: 2026-08-15
  6 +> **定位**: 这份文档不定义任何新机制,它把 [27-E2E 端到端任务执行流程](./27-e2e-task-execution-flow.md) 描述的架构**跑一遍具体的数**。它的用途有三个:
  7 +> 1. **实现时的靶子**——每个阶段的输入输出都写成了具体的 JSON / 图形,可以直接拿来构造测试数据。
  8 +> 2. **验收场景**——P2/P3/P4 全部落地后,跑通这一个场景就等于端到端验证。
  9 +> 3. **沟通基准**——讨论"这里应该怎么设计"时,有一个共同的具体例子可指。
  10 +>
  11 +> **注意**:文中所有数据是**设计意图的示例**,不是从运行中的系统里导出的真实记录。标 🔴 的地方是当前尚未实现、走查到此会断的环节。
  12 +
  13 +---
  14 +
  15 +## 场景设定
  16 +
  17 +用户想做一个「给公众号文章自动生成配图」的小工具,于是建一家公司来做这件事。
  18 +
  19 +---
  20 +
  21 +## 阶段 1 · 建公司
  22 +
  23 +**用户动作**:填两个字段提交。
  24 +
  25 +```jsonc
  26 +// POST /api/company/create
  27 +{
  28 + "id": "auto-illust",
  29 + "name": "配图工厂",
  30 + "description": "做一个工具:输入公众号文章正文,自动理解段落语义并生成配图,输出图文混排的 HTML。需要能跑在本地,先做 CLI 版本。"
  31 +}
  32 +```
  33 +
  34 +**系统动作**([27-P2](./27-p2-org-graph.md) §4.10):
  35 +
  36 +1. `companyService.create()` 建目录 + `company.json`(`agents: []`)
  37 +2. 🔴 自动 `hire(id, 'orggraph-planning', {})`
  38 +3. 🔴 fire-and-forget 触发它的首轮对话,把 `description` 带进去
  39 +
  40 +此刻 `company.json.agents = ["orggraph-planning"]`,其余为空。API 立刻返回,组织结构在后台异步生成。
  41 +
  42 +---
  43 +
  44 +## 阶段 2 · orggraph-planning 生成 org graph
  45 +
  46 +**它读三样东西**:
  47 +
  48 +1. 公司 description(上面那段)
  49 +2. `system/org-graph-templates/*.json`(候选骨架,[27-P2](./27-p2-org-graph.md) §4.8)
  50 +3. `node bin/lemon.js company templates`(现实里能招到的角色,`listTemplates()` 返回 id/name/type/description)
  51 +
  52 +**它的判断**:description 里有"做一个工具""跑在本地""CLI 版本"→ 匹配 `dev.json`。
  53 +
  54 +**它产出的 org graph**——注意这是一张**company 级 workflow**,不是组织架构图([27-E2E](./27-e2e-task-execution-flow.md) §五定案:Org Graph 是 company 级生成的 workflow,Work Graph 是 org node 级生成的 workflow,两者同构):
  55 +
  56 +```jsonc
  57 +// org_graph_nodes + org_graph_edges 的等价视图
  58 +{
  59 + "nodes": ["code-planner", "code-coder", "code-validator"],
  60 + "edges": [
  61 + { "from": "code-planner", "to": "code-coder", "condition": "success", "edge_kind": "forward" },
  62 + { "from": "code-coder", "to": "code-validator", "condition": "success", "edge_kind": "forward" },
  63 + { "from": "code-validator", "to": "code-coder", "condition": "fail", "edge_kind": "backward" }
  64 + ]
  65 +}
  66 +```
  67 +
  68 +边是 `success` / `fail` 执行流边,与 work graph 的边完全同构——**同一套 schema、同一个执行引擎、同一个控制台 UI 在两层复用**。
  69 +
  70 +`ceo` / `hr` 这类不参与任务执行的角色仍然可以被招聘进公司(存在于 `company.json.agents[]`),但**不出现在 org graph 这张 workflow 里**——org graph 只画"任务怎么流过这家公司"。
  71 +
  72 +**它提交图 + 执行招聘**(两步,[27-P2](./27-p2-org-graph.md) §4.1 第 6-7 步):
  73 +
  74 +```bash
  75 +# 1. 整体提交图定义,过四项校验(无环 / 单一入口 / 可达性 / 回退边标注)后事务性落库
  76 +curl -X POST http://localhost:3010/api/company/auto-illust/org-graph -d @/tmp/orggraph.json
  77 +
  78 +# 2. 按拓扑序逐个招聘(注意:P2 v0.4 起 hire 不再需要 --reportsTo,
  79 +# 图的结构已在第 1 步整体写入,招聘只负责建 agent 目录)
  80 +node bin/lemon.js company hire --id auto-illust --role code-planner
  81 +node bin/lemon.js company hire --id auto-illust --role code-coder
  82 +node bin/lemon.js company hire --id auto-illust --role code-validator
  83 +```
  84 +
  85 +三个角色在 `system/templates/` 里都没有同名模板 → `hire()` 返回 `needs_training` → 触发既有的 agent-creator + innovator 培养流水线(`company.js:917-940`,已实现)。
  86 +
  87 +招完后 orggraph-planning 关掉自己的心跳转入休眠(一次性任务)。后续增量招聘归 HR 管。
  88 +
  89 +---
  90 +
  91 +## 阶段 3 · 用户下达一个具体任务
  92 +
  93 +```jsonc
  94 +// 🔴 POST /api/company/auto-illust/tasks ← 这个端点目前不存在
  95 +{
  96 + "task": "先做第一个版本:读一个 markdown 文件,按 H2 分段,每段调用图像生成 API 产出一张配图,最后输出内嵌 base64 图片的 HTML。图像 API 先用 mock,接口留出来。"
  97 +}
  98 +```
  99 +
  100 +🔴 **缺口**:现有的 `POST /api/agent/task/dispatch`(`agent-chat.js:1470`)只支持 **agent → agent**(`from`/`to` 都必须是已存在的 agent 目录),没有"用户 → 公司"的入口。见 [27-E2E](./27-e2e-task-execution-flow.md) §三 阶段 3。
  101 +
  102 +---
  103 +
  104 +## 阶段 4 · 任务进入 org graph 开始执行
  105 +
  106 +任务不需要另建一张顶层图——**阶段 2 生成的 org graph 本身就是这家公司的 workflow**,任务直接在它上面跑:
  107 +
  108 +```
  109 +[code-planner] --success--> [code-coder] --success--> [code-validator]
  110 + ↑ │
  111 + └────fail(backward)──┘
  112 +```
  113 +
  114 +从入度为 0 的节点(`code-planner`)开始执行。`execution_mode` 由用户选:`manual`(每步点"下一步")或 `auto`(一路跑到底)。假设选 `auto`。
  115 +
  116 +---
  117 +
  118 +## 阶段 5 · code-planner 的 Work Graph(Level 1)
  119 +
  120 +`code-planner` 这个 org node 被激活,它先调 **workgraph-planning agent** 规划自己这一层的图。
  121 +
  122 +**规划结果**——注意三个验收节点是**动态选进来的**([27-E2E](./27-e2e-task-execution-flow.md) §十一):
  123 +
  124 +```
  125 +brd-planning → prd-planning → sdd-planning ─┬→ task-test-data ───┐
  126 + └→ task-test-script ─┴→ code-planner-validator → loop-validator
  127 +```
  128 +
  129 +**planning agent 的选择理由**(这是它要在规划时说清楚的):
  130 +
  131 +| 验收节点 | 选/不选 | 理由 |
  132 +|---|---|---|
  133 +| `task-test-script` | ✅ 选 | "按 H2 分段""输出 HTML"这些行为可以用代码断言真假 |
  134 +| `task-test-data` | ✅ 选 | markdown 输入空间复杂,需要正常/边界/异常样本(空文件、没有 H2、H2 嵌套) |
  135 +| `task-benchmark` | ❌ 不选 | 这一版图像 API 是 mock,没有"配图质量好不好"这种打分维度要评。**如果将来接真实图像 API,就要把这个节点加回来** |
  136 +
  137 +这正是"动态选择加哪几个"的实际含义——不是三个固定都跑。
  138 +
  139 +**产出的验收契约**(`acceptance.json`,[27-E2E](./27-e2e-task-execution-flow.md) §11.2):
  140 +
  141 +```jsonc
  142 +{
  143 + "version": "1",
  144 + "okr_node_id": "O-001.1.1",
  145 + "task_id": "T-xxx",
  146 + "benchmark": null, // 本次没选这个节点
  147 + "test_data": {
  148 + "cases": [
  149 + { "id": "normal", "input": {"md": "# T\n## A\n正文\n## B\n正文"}, "expect": {"sections": 2} },
  150 + { "id": "no-h2", "input": {"md": "# T\n只有正文"}, "expect": {"sections": 0} },
  151 + { "id": "empty", "input": {"md": ""}, "expect": {"error": "empty_input"} }
  152 + ]
  153 + },
  154 + "test_script": { "runner": "vitest", "path": "tests/acceptance.spec.js" }
  155 +}
  156 +```
  157 +
  158 +**两个并行验收节点是 fan-out 的第一个真实用例**——它们之间无依赖,可以同时跑,汇合到 `code-planner-validator`。🔴 这会撞上 `CONVENTIONS.md` §12「任意 (pid, agent) 同时只允许 1 个 running」和 SessionPool 的 key 复用(见 [27-E2E](./27-e2e-task-execution-flow.md) §7.4)。
  159 +
  160 +`code-planner-validator` 的职责不只是审 SDD,还要审**验收标准本身**(§7.3):这套 test_data 覆盖了原始需求吗?有没有平凡通过的漏洞?
  161 +
  162 +---
  163 +
  164 +## 阶段 6 · code-coder 的 Work Graph(Level 1)+ 并行子图(Level 2)
  165 +
  166 +`code-coder` org node 激活,同样先调 planning agent。它读到上一阶段的 SDD,把工作拆成三个可并行的 unit:
  167 +
  168 +```
  169 + ┌→ [md-parser] → [unittest] ─┐
  170 +[start] ┼→ [img-client] → [unittest] ─┼→ [integrate] → [integration-test] → [loop-validator]
  171 + └→ [html-render]→ [unittest] ─┘
  172 +```
  173 +
  174 +- **fan-out**:三个 unit 无依赖,并行(DevLoop 的 `_groupUnitsByWave()` Kahn 分波已实现,🔴 但波内目前是串行的,`loop-controller.js:609` 注释 `// 串行跑`)
  175 +- **fan-in**:`integrate` 节点等三条支线全绿才能进入 `ready`(多入边 = 汇合点,全部满足才推进,[27-P3/P4](./27-p3-p4-work-graph-console.md) §D.1)
  176 +- 每个 `unittest` 失败 → 打回对应的 unit 重写 = **L1 循环**,`retry_budget=5`
  177 +
  178 +**假设 `integration-test` 失败了**:`html-render` 拿到的分段结构和 `md-parser` 产出的不一致。
  179 +
  180 +这时 `loop-validator` 要判断"重新 planning 还是跳回某个节点"——这正是 DevLoop **BugTriager 四分类**已经解决的问题(`sdd-process.md` P-2):
  181 +
  182 +| 归因 | 本例是否命中 | 动作 | 烧不烧预算 |
  183 +|---|---|---|---|
  184 +| `contract_mismatch` | ✅ 命中 | 两个 unit 对"分段结构"的契约理解不一致 → 改 SDD 统一契约 → reset 两个 unit | ✅ 烧 `l2_budget` |
  185 +| `impl_bug` | ❌ | — | 不烧 |
  186 +| `spec_ambiguous` | ❌ | — | 升 L3 找人 |
  187 +| `infra_flake` | ❌ | — | 不烧 |
  188 +
  189 +→ 改 SDD → reset `md-parser` + `html-render` → 回到 L1 重跑这两条支线 → 再次 `integrate` → 再次 `integration-test`。
  190 +
  191 +`l2_budget` 默认 3 轮,超出发 `project.l2_exhausted` 挂起等人。
  192 +
  193 +---
  194 +
  195 +## 阶段 7 · code-validator org node + 三级恢复
  196 +
  197 +`code-validator` 激活,消费阶段 5 产出的 `acceptance.json`:
  198 +
  199 +| 验收项 | 执行方式 | 状态 |
  200 +|---|---|---|
  201 +| `test_script` | 引擎起进程跑 vitest,读结果 | 🔴 执行器待建(可搬 DevLoop Integrator) |
  202 +| `test_data` | 喂样本比对实际输出 | 🔴 同上 |
  203 +| `benchmark` | 本次为 null,跳过 | — |
  204 +
  205 +**假设 `no-h2` 这个 case 一直过不了**,而且已经在 L1 打回 code-coder 重写 3 次、L2 重新 planning 1 次,仍然失败。
  206 +
  207 +此时进入你描述的第三级 —— **L4 · 迭代 system agent**([27-E2E](./27-e2e-task-execution-flow.md) §八):
  208 +
  209 +1. 判定这是**能力缺陷**而非粗心错误(`qa-judge` 的三分类:粗心错误 / 设计缺陷 / **能力缺陷** → `RETRAIN_REQUIRED` + `capability_gaps[]`。🔴 协议已定义在 `qa-judge/agent.md:223-333`,但没有程序化调用入口)
  210 +2. 触发 **L4-company**([27-E2E](./27-e2e-task-execution-flow.md) §八):**只迭代 `code-coder` 在 auto-illust 这家公司的记忆分区**(`companies/auto-illust/`),自动进行、无需人工确认——因为爆炸半径就限制在这一家公司内,改坏了也波及不到别人。
  211 +3. 回到 L1 重跑 → 再到 code-validator 验证
  212 +
  213 +**注意这里不会碰 `coder` 的 general 本体**。如果这次失败暴露的是 `code-coder` 通用能力上的短板(不只是本公司场景特有的),那属于 **L4-general**:它不在任务循环里发生,而是等当日的 **agent dream 流程**——由 master agent 或人工触发,汇总当天所有公司的成功/失败经验,统一蒸馏一次 general 本体,走 git 版本化以便回滚。这个拆分保证了"为修一个任务而改坏十家公司共用 agent"这件事在架构上不可能发生。
  214 +
  215 +**⚠️ 这里就是 §7.1 说的熔断失效风险**:L4 迭代完之后如果 L1/L2 的计数器重置,"迭代 agent → 重跑 → 再迭代"可以永远循环。所以必须有一个**跨层级共享、只增不减的任务级总预算**:
  216 +
  217 +```jsonc
  218 +{ "total_agent_calls": 100, "total_tokens": 5000000, "wallclock_ms": 7200000, "l4_iterations": 2 }
  219 +```
  220 +
  221 +扣完即挂起等人工,不管哪一层还想重试。
  222 +
  223 +---
  224 +
  225 +## 这个场景的成本量级
  226 +
  227 +按 `plan-cost-concurrency.md` 的实测口径(DevLoop 中项目 8 unit / 2 PRD rev / 2 轮 L2 ≈ 39 次调用 / 410K token / $4.8 / 墙钟 20 分钟)外推本场景:
  228 +
  229 +| 阶段 | agent 调用次数(估) |
  230 +|---|---|
  231 +| orggraph-planning + 招 5 个角色(其中 2 个走培养流水线) | ~8 |
  232 +| Level 0 任务图规划 | 1 |
  233 +| code-planner:workgraph-planning 1 + brd/prd/sdd-planning 3 + 验收 2 + code-planner-validator 1 + loop-validator 1 | 8 |
  234 +| code-coder:workgraph-planning 1 + 3 unit ×(code+test) 6 + 1.5 次重试 ~4 + integrate 1 + int-test 1 + judge 1 | ~14 |
  235 +| L2 一轮修复(triage + 改 SDD + reset 后重跑 2 unit) | ~7 |
  236 +| code-validator + L4 一次迭代 | ~6 |
  237 +| **合计** | **~44 次** |
  238 +
  239 +粗估 **$8-15、墙钟 40-90 分钟**(并行后)。这个量级下,"自动模式一键跑到底"必须在启动前把预估成本告诉用户。
  240 +
  241 +---
  242 +
  243 +## 走查暴露的 🔴 断点清单
  244 +
  245 +按阻塞程度排序,这也是实现顺序的依据:
  246 +
  247 +| # | 断点 | 出现阶段 | 对应设计 |
  248 +|---|---|---|---|
  249 +| 1 | `qa-judge` 没有程序化调用入口(判决无法驱动任何自动动作) | 5, 6, 7 | [27-E2E](./27-e2e-task-execution-flow.md) §11.3 |
  250 +| 2 | code-validator 节点的验收执行能力(跑 test_script / 喂 test_data)未实现 | 5, 7 | §11.3 |
  251 +| 3 | 「用户 → 公司」任务入口不存在 | 3 | §三 阶段 3 |
  252 +| 4 | **P2 的 org graph 边模型需从 `reports_to` 改为 `success`/`fail` 执行流边** | 2, 4 | §五定案 + [27-P2](./27-p2-org-graph.md) v0.4 修正 |
  253 +| 5 | fan-out 并行撞 CONVENTIONS §12 不变量 + SessionPool key 复用 | 5, 6 | §7.4 |
  254 +| 6 | 任务级总预算不存在 | 7 | §7.1 |
  255 +| 7 | L4-general 的 agent dream 流程不存在(跨公司批量蒸馏层缺失,单点接口已齐) | 7 之后(离线) | §8.1 |
  256 +| 8 | orggraph-planning / workgraph-planning agent 均未实现 | 2, 5, 6 | [27-P2](./27-p2-org-graph.md) §4.9 |
  257 +
  258 +第 4 项是**架构前提**,必须先改;改完之后做 1-3 项,这个场景就能在"串行、无 L4"的模式下端到端跑通一遍。
... ...
docs/todo/system-design/27-graph-agent-architecture.md
1 1 # 27. Graph Agent 架构 — Org Graph / Work Graph 与 System Agent 统一寻址
2 2  
3   -> **版本**: v0.2(P0 已实现)
  3 +> **版本**: v0.3(P0/P1 已实现;Org Graph 建模在 v0.3 修正为 company 级 workflow)
4 4 > **日期**: 2026-08-15
5   -> **状态**: P0 ✅ 已实现(`server/services/graph-db.js` + `graph-seed-service.js`,见第七节);P1 起 📝 设计中/未实现
  5 +> **状态**: P0 ✅ 已实现(`graph-db.js` + `graph-seed-service.js`,见第七节);**P1 ✅ 已实现**(commit `226489d`,`graph-service.js`);P2 起 📝 设计中/未实现
  6 +> **命名**: 新增 agent id / 表名 / 字段名前先读 [27-NAMING 命名规范](./27-naming-conventions.md)
6 7 > **前置**: [02 四层 Agent 架构](../../done/system-design/02-agent-architecture.md)、[24 公司层架构](../../done/system-design/24-company-layer-architecture.md)、[22 Agent 记忆系统](../../done/system-design/22-agent-memory-system.md)、`server/src/core/devloop/loop-controller.js`(现有 retry-with-reflection 实现)
7 8  
8 9 ---
... ... @@ -23,7 +24,7 @@
23 24 把「company 拥有并复制 agent」改造为「company 引用全局 system agent,并在其上定义**静态协作结构(Org Graph)**与**动态任务执行路径(Work Graph)**」:
24 25  
25 26 1. Agent 是跨公司复用、持续自我进化的一等公民,不是一次性文件拷贝。
26   -2. 公司的组织结构(Org Graph)与具体任务怎么跑(Work Graph)解耦:前者是company 层面相对稳定的静态定义,后者由 planning agent 针对每个目标动态规划、执行完即归档。
  27 +2. 公司级 workflow(Org Graph,建公司时由 `orggraph-planning` 生成一次)与 org node 级 workflow(Work Graph,每次接到任务时由 `workgraph-planning` 生成)分层:两者同构、共用同一套引擎,只是生成时机与节点指向不同。
27 28 3. 失败恢复不再局限于"重跑同一个节点":任意节点失败可以携带上下文回退到图中任意历史节点,同时有引擎级别的硬性防死循环机制。
28 29  
29 30 ---
... ... @@ -34,8 +35,9 @@
34 35 |------|------|
35 36 | **System Agent** | 全局唯一的可执行 Agent 实体。自带 prompt 定义、公共记忆(`memory.md`)、按公司分区的私有历史。是 24 号文档里"System Agent"与"Agent Template"两个概念的合并——不再区分"模板"和"实例",只有"这个 agent,被谁调用过"。 |
36 37 | **scope** | System Agent 的可见性:`public`(跨公司共享,记忆公共累积)或 `company`(私有,只属于 `owner_company_id` 指定的一家公司)。 |
37   -| **Org Graph** | 公司级别的**静态**协作结构。节点 = 对某个 system agent 的引用;边 = 汇报/协作关系。取代现有 `company.json.agents[]` 扁平列表。 |
38   -| **Work Graph** | 某个 org 节点收到一个 goal/task 后,现场调用 **work-graph-planning agent** 针对这一具体任务规划出的**动态**执行图。节点 = 对某个 system agent 的一次调用;边带 `success`/`fail` 条件与目标节点。执行完归档,不复用到下一个任务。 |
  38 +| **Org Graph** | **company 级别生成的 workflow**。由 `orggraph-planning` agent 在建公司时根据公司目标/描述生成一次。节点 = 对某个 org node agent 的引用;边 = **执行流**(`condition ∈ {success, fail}` + `edge_kind ∈ {forward, backward}`)。⚠️ v0.3 修正:本文档 v0.2 曾把它定义为"静态汇报结构、边是汇报/协作关系",该建模已作废,详见 [27-P2 v0.4](./27-p2-org-graph.md) §一。 |
  39 +| **Work Graph** | **org graph node 级别生成的 workflow**。某个 org node 收到任务后,现场调用 `workgraph-planning` agent 针对这一具体任务规划出。节点 = 对某个 system agent 的一次调用;边的语义与 Org Graph **完全同构**。执行完归档,不复用到下一个任务。 |
  40 +| **两者关系** | Org Graph 与 Work Graph 是**同一种东西在不同层级上的递归**——同一套 schema 形态、同一个执行引擎、同一套控制台 UI,区别只在生成者、生成时机、节点指向。 |
39 41 | **Loop-Validator** | 一个专职的 system agent,内置带硬编码熔断逻辑的 skill。所有"回退到历史节点"的边必须先经过它:备案 → 审计上次修复是否生效 → 判断修复方案是否被正确传递 → 循环数 > 3 强制终止。 |
40 42 | **Node 内容 / Edge 内容** | 图结构本身(节点、边、状态、指针)存 SQLite;节点的输入输出内容、日志、上下文,依旧是 Markdown 文件,物理落在对应 system agent 的目录下。 |
41 43  
... ... @@ -57,10 +59,12 @@
57 59 ┌────────────┴───────┴─────────┴──────────────┐
58 60 │ Company: {companyId} │
59 61 │ │
60   - │ Org Graph(静态,company 生命周期内相对稳定) │
61   - │ [CEO 节点] ──reports──> [CTO 节点] ──> ... │
  62 + │ Org Graph(company 级 workflow,建公司时生成)│
  63 + │ [code-planner] --success--> [code-coder] │
  64 + │ ↑ │ │
  65 + │ └──── fail(backward) ───┘ │
62 66 │ │
63   - │ Work Graph(动态,per-goal,执行完归档) │
  67 + │ Work Graph(org node 级 workflow,per-task) │
64 68 │ [node1:coder] --success--> [node2:tester] │
65 69 │ --fail-----> [loop-validator]│
66 70 │ │ │
... ... @@ -95,16 +99,20 @@ system/agents/{agentId}/
95 99  
96 100 **新建与升级治理**:Work-graph 执行中如果需要的 system agent 不存在,直接触发创建,默认 `scope=company`(私有)。是否升级为 `public`,复用现有 `hireWithTraining()` → `agent-creator`/`innovator` 训练 → benchmark ≥95 → 升级的门槛链路,只是终点从"写文件到 `system/templates/`"改为"更新 DB 里的 `scope` 字段"。**不新增治理机制,只改落点。**
97 101  
98   -### 3.3 Org Graph:公司静态结构
  102 +### 3.3 Org Graph:company 级 workflow
99 103  
100   -- 节点:对某个 system agent 的引用(`org_graph_nodes`),代替 `company.json.agents[]`。
101   -- 边:汇报/协作关系(`org_graph_edges`),公司创建/招聘时由 CEO 或 HR agent 维护。
102   -- "招聘"语义变化:不再是"拷贝一份文件",而是"在 org graph 里加一个指向某 system agent 的引用节点"。
  104 +> ⚠️ **v0.3 修正**:本节 v0.2 的内容("公司静态结构、边是汇报/协作关系、招聘时由 CEO 或 HR 维护")已作废。完整设计见 [27-P2 v0.4](./27-p2-org-graph.md)。
  105 +
  106 +- **节点**:对某个 org node agent 的引用(`org_graph_nodes`)。
  107 +- **边**:**执行流**(`org_graph_edges`:`condition ∈ {success, fail}`、`edge_kind ∈ {forward, backward}`),与 `work_graph_edges` 同构。
  108 +- **谁生成**:`orggraph-planning` agent,在建公司时**整体规划、整体写入**一次——不是靠招聘动作逐次累积出来的。
  109 +- **"招聘"语义**:只负责把这个角色的 agent 目录建出来(走 P1 的虚拟引用,不再拷贝 `agent.md`),**与图的结构无关**。
  110 +- **任务如何进入**:从图中入度为 0 的节点开始,按边的 `condition` 逐节点推进;每个 org node 被激活后再调 `workgraph-planning` 规划自己那一层的 work graph。
103 111  
104 112 ### 3.4 Work Graph:任务动态执行图
105 113  
106 114 1. 某 org 节点收到一个 goal/task。
107   -2. 该节点调用 **work-graph-planning agent**,针对这个具体任务规划出一张图:节点指向要执行的 system agent,边带 `success`/`fail` 条件。
  115 +2. 该节点调用 **`workgraph-planning` agent**,针对这个具体任务规划出一张图:节点指向要执行的 system agent,边带 `success`/`fail` 条件。
108 116 3. **目标节点范围不受 org graph 约束,只受 system agent 池约束**——planning agent 可以调用任意 public agent、company 自己私有的 agent,或判断需要新建一个(走 3.2 的创建+治理链路)。
109 117 4. **回退边必须显式经过 loop-validator 节点**——即"节点A --fail--> loop-validator --> 节点B",而不是"节点A --fail(隐藏回退属性)--> 节点B"。这是画图时的**结构要求**,不是引擎运行时才附加的隐藏逻辑。
110 118 5. **执行前结构校验**:图规划完成后、进入执行前,引擎做一次静态校验——任何边如果目标节点在图中已存在其他指向它的边(即会构成"回到历史节点"),但没有经过 loop-validator 节点,直接拒绝这张图,不进入执行。类比现有 `npm run spec:check` 的"结构先校验,再允许跑"模式。这是对"planning agent 应该自觉画上 validator"这条规则的**引擎级兜底**,防止规划疏漏导致死循环绕过审计。
... ... @@ -164,7 +172,8 @@ CREATE TABLE work_graphs (
164 172 id TEXT PRIMARY KEY,
165 173 company_id TEXT NOT NULL,
166 174 triggered_by_org_node_id TEXT REFERENCES org_graph_nodes(id),
167   - goal_task_id TEXT,
  175 + okr_node_id TEXT, -- 形如 O-001.1.1,配合 company_id 定位
  176 + task_id TEXT, -- 形如 T-xxx
168 177 status TEXT NOT NULL DEFAULT 'planning', -- planning | running | passed | failed | blocked
169 178 created_at INTEGER NOT NULL,
170 179 updated_at INTEGER NOT NULL
... ... @@ -213,10 +222,10 @@ CREATE TABLE work_graph_edges (
213 222 | **P0 地基** ✅ | 建表 + 现有数据迁移,不改变任何现有执行路径 | `system_agents`/`org_graph_*` 表;把现有 `system/agents/` + `system/templates/` 目录扫描导入为种子数据(`system_agents` 行,`scope` 按当前所属推断) | 迁移脚本跑完,`system_agents` 表行数与现有 agent+模板目录数一致(**实际行数与本文档最初估计不同,见第七节**);现有 `company-service.js` 行为不变(并行只读验证,已确认零改动) |
214 223 | **P1 虚拟引用** | 停止文件拷贝,改为引用 | 改造 `AgentManager.getAgentConfig()` + `PromptBuilder.build()` 支持 `(agentId, companyId)` 寻址;`company-service.hire()` 改为创建 org_graph 引用节点而非 `_copyTemplate()`。细化需求与设计见 [27-P1 虚拟引用寻址](./27-p1-virtual-reference.md) | 新雇佣的 agent 不再产生文件拷贝,调用时能正确拼装公共+私有上下文;旧公司(仍是拷贝模式)继续可跑,不强制迁移存量数据 |
215 224 | **P2 Org Graph** | `company.json.agents[]` 迁移为图结构 | `org_graph_nodes`/`edges` 落地;HR/CEO 招聘走新表。细化需求与设计见 [27-P2 Org Graph 落地](./27-p2-org-graph.md) | 现有 Company Dashboard 前端能正确读出 org graph 并渲染(哪怕暂时按列表展示) |
216   -| **P3 Work Graph 最小闭环** | 单向执行,先不做回退 | work-graph-planning agent(system agent,新建);`work_graphs`/`nodes`/`edges` 表;顺序执行器(success → next,fail → 停止/标记 blocked,暂不回退) | 一个 goal 能被规划成 2-3 节点的图并顺序跑完,状态正确落库 |
217   -| **P4 回退 + Loop-Validator** | 引入死循环防护 | loop-validator system agent + 内置熔断 skill;`edge_kind=backward` 支持;3.4 第 5 点的执行前结构校验 | 人为构造一个"总是失败"的节点,验证第 4 次回退被 loop-validator 强制终止,且校验能拒绝"回退边未经过 validator"的非法图 |
  225 +| **P3 Work Graph 最小闭环** | 单向执行,先不做回退 | `workgraph-planning` agent(system agent,新建);`work_graphs`/`nodes`/`edges` 表;顺序执行器(success → next,fail → 停止/标记 blocked,暂不回退)。P3+P4 已合并细化为一份需求与设计(含新增的执行控制台页面),见 [27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md) | 一个 goal 能被规划成 2-3 节点的图并顺序跑完,状态正确落库 |
  226 +| **P4 回退 + Loop-Validator** | 引入死循环防护 | loop-validator system agent + 内置熔断 skill;`edge_kind=backward` 支持;3.4 第 5 点的执行前结构校验。细化需求与设计同上,见 [27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md) | 人为构造一个"总是失败"的节点,验证第 4 次回退被 loop-validator 强制终止,且校验能拒绝"回退边未经过 validator"的非法图 |
218 227 | **P5 自我进化** | 公共记忆蒸馏机制 | 公共 `memory.md` 写入走自省校验 + git 版本化;company-scoped → public 的升级门槛复用现有 benchmark ≥95 链路 | 同一 public agent 被两家公司调用后,`memory.md` 有可观察的增量更新,且能 git revert 到升级前版本 |
219   -| **P6(可选)DevLoop 收编** | 统一循环逻辑 | 把 `loop-controller.js`/`unit-registry.js` 重构为 Work Graph 引擎上的一个具体图模板 | DevLoop 现有测试套件(`server/tests/devloop/**`)在新引擎上全部通过,无需单独维护两套循环代码 |
  228 +| **P6(~~可选~~ **应前置**)DevLoop 收编** | 统一循环逻辑 | 把 `loop-controller.js`/`unit-registry.js` 重构为 Work Graph 引擎上的一个具体图模板。⚠️ **定位修正**:端到端流程梳理后发现 DevLoop 不是"将来可选整合的边缘子系统",而是**本架构唯一一个已经跑通的参考实现**——它的 L1/L2/L3 三层循环、四分类失败归因、分级预算熔断、`contract_hash` 闸门、DAG 分波、SSE 埋点、前端 DAG 组件都已实现或已有完整规格(`docs/prd/newfunction/_spec/`)。通用 Work Graph 引擎应当**从它抽象而来**,而不是平行发明后再回头合并。详见 [27-E2E](./27-e2e-task-execution-flow.md) §二结论 2 | DevLoop 现有测试套件(`server/tests/devloop/**`)在新引擎上全部通过,无需单独维护两套循环代码 |
220 229  
221 230 **依赖顺序是强顺序的**:P1 依赖 P0 的数据先迁移进 DB;P3 的执行器依赖 P1 的引用寻址跑通;P4 的回退依赖 P3 的顺序执行先稳定。P5、P6 相对独立,可以在 P4 稳定后并行推进或往后延。
222 231  
... ... @@ -244,5 +253,9 @@ P0 阶段的处理方式是**迁移脚本层面的临时保护**:`system_agent
244 253  
245 254 ### 关联文档
246 255  
247   -P1(虚拟引用寻址)的详细需求与设计已有独立草稿:[27-P1 虚拟引用寻址](./27-p1-virtual-reference.md)。
248   -P2(Org Graph 落地)的详细需求与设计见:[27-P2 Org Graph 落地](./27-p2-org-graph.md)。
  256 +- **[27-E2E 端到端任务执行流程](./27-e2e-task-execution-flow.md)** — 跨阶段总览:从建公司 → 下达任务 → org graph 逐节点执行 → work graph 规划与循环 → 验收收敛。**建议先读这一份**,它指出了本文档体系与已有 DevLoop 实现之间的对应关系、三处语义冲突和若干空白。
  257 +- **[27-NAMING 命名规范](./27-naming-conventions.md)** — 跨文档命名约定(`{作用域}-{职能}`)。新增 agent id / 表名 / 字段名前必读,避免与既有的 `benchmark` / `goal` 等命名撞车。
  258 +- **[27-EXAMPLE 参考场景走查](./27-example-dev-company-walkthrough.md)** — 把上面那套架构在一家具体公司上跑一遍数(含各阶段真实形态的 JSON / 图 / 成本估算 / 断点清单)。实现时的靶子与端到端验收场景。
  259 +- [27-P1 虚拟引用寻址](./27-p1-virtual-reference.md) — 已实现(commit `226489d`)
  260 +- [27-P2 Org Graph 落地](./27-p2-org-graph.md) — 设计稿
  261 +- [27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md) — 设计稿
... ...
docs/todo/system-design/27-naming-conventions.md 0 → 100644
  1 +# 27-NAMING · Graph Agent 体系命名规范
  2 +
  3 +> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的跨文档命名约定。
  4 +> **版本**: v0.1
  5 +> **日期**: 2026-08-15
  6 +> **适用范围**: 27 系列全部文档 + 后续实现中新建的 system agent id、表名、字段名、API 路径。
  7 +> **为什么需要这份文档**: 本体系引入的概念(planner / validator / planning / benchmark)在仓库里**全部已经有同名但不同义的既有占用**。不先统一,日后 grep 和排查会持续付出代价。
  8 +
  9 +---
  10 +
  11 +## 一、总规则
  12 +
  13 +```
  14 +{作用域}-{职能}
  15 +```
  16 +
  17 +**任何 agent id、验收产物类型、评测体系名,都必须带作用域前缀**,不允许裸用 `planner` / `validator` / `planning` / `benchmark`。
  18 +
  19 +作用域从大到小:
  20 +
  21 +| 作用域 | 含义 |
  22 +|---|---|
  23 +| `orggraph` | 公司级 workflow |
  24 +| `workgraph` | org node 级 workflow |
  25 +| `{domain}` | 业务领域(`code` / `content` / `ops` …),即 org node 所属领域 |
  26 +| `task` | 单个任务 |
  27 +| `agent` | 单个 system agent |
  28 +| `skill` | 单个 skill |
  29 +
  30 +---
  31 +
  32 +## 二、规划类 agent —— `{产出物}-planning`
  33 +
  34 +| id | 职责 | 所在层级 |
  35 +|---|---|---|
  36 +| `orggraph-planning` | 根据公司目标/描述,生成**公司级 workflow**(org graph) | company,建公司时跑一次 |
  37 +| `workgraph-planning` | 根据任务,为某个 org node 生成**该节点的 workflow**(work graph) | org node,每次接到任务时 |
  38 +| `brd-planning` | 产出 BRD | work graph 内的节点 |
  39 +| `prd-planning` | 产出 PRD | work graph 内的节点 |
  40 +| `sdd-planning` | 产出 SDD | work graph 内的节点 |
  41 +
  42 +⚠️ **重命名**:[27-P2](./27-p2-org-graph.md) v0.2/v0.3 里叫 `org-graph-creator` 的 agent,**统一改名为 `orggraph-planning`**。v0.4 起全文使用新名。
  43 +
  44 +---
  45 +
  46 +## 三、Org node agent —— `{domain}-{职能}`
  47 +
  48 +org graph 上的节点 agent,一律带领域前缀:
  49 +
  50 +| id | 职责 |
  51 +|---|---|
  52 +| `code-planner` | 开发领域:规划阶段的 org node |
  53 +| `code-coder` | 开发领域:实现阶段的 org node |
  54 +| `code-validator` | 开发领域:验收阶段的 org node |
  55 +| `content-planner` / `content-writer` / `content-validator` | 内容领域的同构三段 |
  56 +
  57 +**不允许**裸用 `planner` / `coder` / `validator` —— 它们必须能被一眼看出属于哪个领域,否则跨领域公司(既做开发又做内容)会立刻撞名。
  58 +
  59 +---
  60 +
  61 +## 四、验收产物 —— `task-{类型}`
  62 +
  63 +work graph 内动态选入的验收产出节点(见 [27-E2E](./27-e2e-task-execution-flow.md) §十一):
  64 +
  65 +| id | 产出 |
  66 +|---|---|
  67 +| `task-benchmark` | 打分式验收标准(维度 + 权重 + 及格线) |
  68 +| `task-test-data` | 输入样本集(正常 / 边界 / 异常) |
  69 +| `task-test-script` | 可执行断言脚本 |
  70 +
  71 +### 为什么是 `task-` 而不是 `goal-`
  72 +
  73 +原本的理由是"`goal` 被 OKR 体系占用",但 §六 决定把 OKR 体系从 `goal` 改名为 `okr`,**这个撞名理由已经不成立**。改用 `task-` 的理由现在只剩一条,但它本身足够:
  74 +
  75 +**验收标准的生命周期跟着一次 task 执行走** —— 由该 task 的 `{domain}-planner` 阶段产出、该 task 的 `{domain}-validator` 阶段消费、task 结束即归档。而 OKR 里的 Key Result 是**跨多个 task、长周期的度量目标**,两者时间尺度不同。用 `task-` 与它的真实作用域一致。
  76 +
  77 +> `goal-` 现在是空闲的,如果更偏好那个词,全局换一下即可,不影响任何其他设计。
  78 +
  79 +---
  80 +
  81 +## 五、三种 benchmark 必须分开(当前全部叫 `benchmark`)
  82 +
  83 +仓库里 `benchmark` 一词同时指三件完全不同的事。**统一加前缀区分**:
  84 +
  85 +| 新名 | 指什么 | 现状 |
  86 +|---|---|---|
  87 +| **`skill-benchmark`** | 给 **Skill** 打分 + Arena ELO 排行榜 | ✅ 已实现:`server/services/benchmark-runner.js`、`benchmark-db.js`、`benchmark_runs` 表(主键 `skill_name`)、`comparisons` ELO 表、路由 `/benchmarks` `/benchmark-all` `/benchmark/status` |
  88 +| **`agent-benchmark`** | 给**单个 system agent** 打能力分,用于 hire / `depositTemplate()` 的 **≥95 门槛** | ⚠️ 部分存在:`system_agents.benchmark_score` 字段(P0 已落地)、`_registry.json` 的 `benchmarkScore`(实测仅 3 条非空:ceo=97 / qa-judge=95 / base=90) |
  89 +| **`task-benchmark`** | 给**单个任务**定验收标准,由 `task-benchmark` agent 产出、validator 节点执行 | 🔴 本次设计新增,尚未实现 |
  90 +
  91 +### 代码层重命名的影响面(未执行,需单独排期)
  92 +
  93 +文档层的消歧已在 27 系列全部完成。**代码层的重命名是一次真实的数据迁移,不是 find-replace**,因此本轮只给出影响面清单,不动代码:
  94 +
  95 +| 对象 | 位置 | 风险 |
  96 +|---|---|---|
  97 +| `benchmark_runs` / `comparisons` / `security_scans` 表 | `data/benchmark.sqlite` | 改表名需迁移脚本 + 停机;这三张表服务于线上 Arena 排行榜 |
  98 +| `benchmarkScore` 字段 | `system/templates/_registry.json`(磁盘数据) | 改字段名需同步改所有读写方 + 迁移已有数据 |
  99 +| `/benchmarks` `/benchmark-all` `/benchmark/status` | `server/routes/` | 公开 API,改路径破坏外部调用方 |
  100 +| `benchmark-runner.js` / `benchmark-db.js` | `server/services/` | 文件重命名成本低,但所有 require 点要同步 |
  101 +
  102 +**建议**:代码层保持现状(既有的就默认是 `skill-benchmark` 语义),**只对新增代码强制用新前缀**。即 `task-benchmark` 相关的表、字段、路由从第一天就带前缀,不与既有的混在一起。等哪天真要清理历史命名时,再按上表单独排期。
  103 +
  104 +---
  105 +
  106 +## 六、OKR 体系:从 `goal` 改名为 `okr`
  107 +
  108 +这棵树的正式名称是 **OKR(Objective - Key Result)**,不应该叫 "goal"。当前代码里 `goal` 既是树的名字、又是树里第 1 层节点的名字,是一处自我遮蔽的命名。
  109 +
  110 +### 现状层级
  111 +
  112 +```
  113 +G-001 → Goal (第1层)
  114 +G-001.1 → Objective (第2层)
  115 +G-001.1.1 → Key Result (第3层)
  116 +G-001.1.1.1 → Sub-Task (第4层+)
  117 +```
  118 +
  119 +### 建议改法
  120 +
  121 +体系整体叫 **OKR**,第 1 层的 "Goal" 也一并正名 —— OKR 的标准词汇里没有 "Goal" 这一层,现在的第 1 层实际承担的是"一个大目标下挂多组 OKR"的容器角色:
  122 +
  123 +| 层 | 现名 | 建议 | ID 前缀 |
  124 +|---|---|---|---|
  125 +| 1 | Goal | **Objective**(顶层目标) | `O-001` |
  126 +| 2 | Objective | **Key Result** | `O-001.1` |
  127 +| 3 | Key Result | **Sub-Task** | `O-001.1.1` |
  128 +| 4+ | Sub-Task | (合并进 Sub-Task 层级) | `O-001.1.1.1` |
  129 +
  130 +> 这样是标准的 Objective → Key Result → Sub-Task 三层。如果确实需要保留四层,则第 1 层建议叫 **Mission**(`M-001`),下挂 Objective / Key Result / Sub-Task —— 但**不要**再叫 Goal。
  131 +
  132 +### 影响面(代码层,需单独排期,本轮未执行)
  133 +
  134 +| 对象 | 位置 | 风险 |
  135 +|---|---|---|
  136 +| `server/services/goals.js` → `okr-service.js` | 文件重命名 | 低,改 require 点 |
  137 +| `server/routes/goals.js` → `okr.js` | 文件重命名 | 低 |
  138 +| `/api/goals/*`、`/api/company/:cid/goals/*`(共 9 条路径) | 公开 API | **中**:破坏外部调用方,需保留旧路径做别名过渡 |
  139 +| `data/companies/{cid}/goals/` → `okr/` | **磁盘数据目录** | **高**:需迁移脚本,涉及已有公司数据(如 `idearun`) |
  140 +| ID 前缀 `G-` → `O-` | 磁盘上已有的 `goal.json` 文件名与内容 | **高**:同上,且 `agent-chat.js` 的 task meta 里存了 `goalId` 反向引用 |
  141 +| `goalId` / `goalsDir` / `goalContext` 等 8 个字段名 | `goals.js`、`agent-chat.js` | 中:跨文件,且注入 agent prompt 的文本里也有 |
  142 +| `OkrDashboard.vue` | 前端 | ✅ 已经叫 OKR,改完后前后端才终于一致 |
  143 +
  144 +**建议**:与 §五 的 benchmark 改名同一策略 —— **新增代码一律用 `okr-`;存量代码保持现状直到单独排期做迁移**。27 系列文档全部使用新词汇。
  145 +
  146 +### 对 27 系列的直接影响
  147 +
  148 +[27-E2E](./27-e2e-task-execution-flow.md) §三 阶段 3 指出:27 号文档 `work_graphs.goal_task_id` 这个单字段不足以定位一个 OKR 节点(需要 `companyId` + 树 ID + 节点 ID 三元组)。改名后该字段应为:
  149 +
  150 +```sql
  151 +okr_node_id TEXT, -- 形如 'O-001.1.1',配合 company_id 定位
  152 +task_id TEXT, -- 形如 'T-xxx',system/tasklog/{taskId}.json
  153 +```
  154 +
  155 +---
  156 +
  157 +## 七、速查表
  158 +
  159 +| 不要用 | 要用 |
  160 +|---|---|
  161 +| `planner` | `code-planner` / `content-planner` / … |
  162 +| `validator` | `code-validator` / `content-validator` / … |
  163 +| `planning` | `orggraph-planning` / `workgraph-planning` / `brd-planning` / `prd-planning` / `sdd-planning` |
  164 +| `org-graph-creator` | `orggraph-planning` |
  165 +| `benchmark`(裸用) | `skill-benchmark` / `agent-benchmark` / `task-benchmark` |
  166 +| `test-data` / `test-script`(裸用) | `task-test-data` / `task-test-script` |
  167 +| `goal` / `goals`(指 OKR 体系时) | `okr` |
  168 +| `Goal`(指 OKR 第 1 层节点) | `Objective`(或 `Mission`,若保留四层) |
  169 +| `goalId` | `okrNodeId` |
  170 +| `G-001.1.1` | `O-001.1.1` |
... ...
docs/todo/system-design/27-p2-org-graph.md
1   -# 27-P2. Org Graph 落地 — 需求与设计文档
  1 +# 27-P2. Org Graph — 公司级 Workflow 的生成与落地
2 2  
3   -> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 Phase P2("Org Graph")
4   -> **版本**: v0.2(设计阶段,未实现;相比 v0.1 的核心变化见 §二末尾"为什么从『招聘时可选传 reportsTo』升级为『org-graph-creator 自动生成』")
  3 +> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 Phase P2
  4 +> **版本**: v0.4(**按执行流边模型重写**;v0.1-v0.3 把 Org Graph 建模为静态汇报结构,该模型已作废,见 §一)
5 5 > **日期**: 2026-08-15
6   -> **前置**: P0 地基已完工并提交(commit `8421f55`,`server/services/graph-db.js` + `graph-seed-service.js`)。**P1(虚拟引用寻址)目前仍是设计稿,未实现**——`server/services/graph-service.js` 尚不存在。§九说明 P2 为什么不需要等 P1 落地即可开工。
  6 +> **前置**: P0 地基已完工(commit `8421f55`)、**P1 虚拟引用寻址已实现**(commit `226489d`,`server/services/graph-service.js`)
  7 +> **命名**: 全文遵循 [27-NAMING 命名规范](./27-naming-conventions.md)。v0.3 的 `org-graph-creator` 在本版本统一改名为 **`orggraph-planning`**。
  8 +> **配套**: [27-E2E 端到端流程](./27-e2e-task-execution-flow.md)(本文档在整条链路里的位置)、[27-EXAMPLE 参考场景](./27-example-dev-company-walkthrough.md)(跑一遍具体的数)
7 9  
8 10 ---
9 11  
10   -## 一、目标与非目标
  12 +## 一、模型变更说明(v0.4 为什么重写)
11 13  
12   -### 目标(本阶段要交付什么)
  14 +v0.1-v0.3 把 Org Graph 建模为**静态汇报结构**:节点是角色引用,边是 `reports_to` / `collaborates_with`,"公司生命周期内相对稳定",不参与执行。这个建模已被推翻。
13 15  
14   -按 27 号文档 P2 行的原始定义(27-graph-agent-architecture.md:215):
  16 +**新模型**:
15 17  
16   -> `company.json.agents[]` 迁移为图结构。`org_graph_nodes`/`edges` 落地;HR/CEO 招聘走新表。验收标准:现有 Company Dashboard 前端能正确读出 org graph 并渲染(哪怕暂时按列表展示)。
  18 +> **Org Graph 是 company 级别生成的 workflow;Work Graph 是 org graph node 级别生成的 workflow。**
17 19  
18   -结合最新明确的方向——**org graph 不再是招聘过程中零散声明出来的副产品,而是由公司的第一个 agent「org-graph-creator」在公司创建之初,根据公司目标/描述 + 一套按公司类型区分的 org graph 模板 + 当前可招聘的 agent 模板库,一次性规划并执行出来的**——展开为四件具体的事:
  20 +两者是**同一种东西在不同层级上的递归**,不是两种不同的结构:
19 21  
20   -1. **补全边(edges)+ 提供写入机制**:P0 只回填了节点(来自 `company.json.agents[]` 的扁平角色名),`org_graph_edges` 表建了但从未写入过一行。P2 要让 `hire()` 支持声明 `reportsTo`,这是所有汇报关系(不管谁写的)最终落地的**唯一通道**。
21   -2. **org-graph-creator agent**:新增一个系统级 agent 模板,作为每家新公司创建后自动"入职"的第一个 agent。它读公司目标/描述、读 `system/org-graph-templates/*.json` 里的候选组织结构模板、读当前 `system/templates/*` 实际能招到的角色,综合判断出一套具体的组织结构,再按拓扑序(先上级后下级)依次调用 `hire()` 把这套结构真正建出来。
22   -3. **组织结构模板库**:新增 `system/org-graph-templates/`,用一组按"公司类型"归类的 JSON 骨架文件(开发型/内容型/运营型……)表达"这类公司通常长什么样",供 org-graph-creator 参考、裁剪、组合——不是每家公司都严格照抄某一个模板,模板只是起点。
23   -4. **提供一个可读的视图**:新增只读 REST 端点把 `(nodes, edges)` 拼成组织架构数据,Company Dashboard 至少能按"谁向谁汇报"分层列表展示。
  22 +| | Org Graph | Work Graph |
  23 +|---|---|---|
  24 +| 生成者 | `orggraph-planning` agent | `workgraph-planning` agent |
  25 +| 生成时机 | 建公司时,一次 | 每个 org node 接到任务时 |
  26 +| 节点指向 | org node agent(`code-planner` 等) | system agent 引用 |
  27 +| 边的语义 | **执行流**:`condition ∈ {success, fail}` | **执行流**:同左 |
  28 +| 执行引擎 | [27-P3/P4](./27-p3-p4-work-graph-console.md) 的同一套 | 同左 |
24 29  
25   -### 非目标(本阶段明确不做)
  30 +**由此带来的四处结构性改动**(这也是本次重写的主要内容):
26 31  
27   -- **不做**图形化组织架构图(连线画布、拖拽调整层级)——验收标准原文写明"哪怕暂时按列表展示"即可。
28   -- **不做** `company.json.agents[]` 的废弃或只读化——很多现有代码(`listAgents()`、心跳、Dashboard 概览卡)仍在直接读它,P2 阶段 `org_graph_nodes`/`edges` 是**追加写入**,不是替换现有读取路径。
29   -- **不做**存量公司历史雇佣关系的"猜测式"补边——见 §四.12。
30   -- **不做**已经跑过一次 org-graph-creator 的公司的"重新规划/推倒重来"——org-graph-creator 在 P2 阶段是**一次性的公司创建时动作**,不是可以随时对着一家已经有员工的公司重跑的东西。公司后续想调整组织结构(加人、换汇报对象),走的是 HR agent 走既有 `hire()`/`reportsTo` 机制做增量调整(§四.4),不是重新触发 org-graph-creator。重新规划一家运行中公司的组织结构涉及"已有节点怎么办、正在跑的任务怎么办",复杂度明显更高,留作 Open Question(§八.5)。
31   -- **不做**给 `company.json` 加一个枚举型的 `type` 字段——公司类型的判断是 org-graph-creator(LLM)基于目标/描述做的一次性推理,不是一个需要长期维护、需要建校验逻辑的结构化字段。org-graph-creator 执行完可以顺手把自己判断出的类型写成一段说明文字存档(§四.9),但那是审计用的自由文本,不是可枚举、可用于 if-else 分支的字段。
32   -- **不做** Work Graph(P3)、Loop-Validator(P4)——P2 只处理静态协作结构。
33   -- **不做** `system/agents/` 与 `system/templates/` 之间 6 个同名 id 的冲突解决——这是 27 号文档 §七遗留给"实现阶段之前"的产品决策,P1 §九 Q4 建议在排 P2 之前解决掉,本文档在 §八重申但不阻塞技术交付。
  32 +1. `org_graph_edges` 从 `relation_type` 改为 `condition` + `edge_kind`,与 `work_graph_edges` 同构(§三.2)。
  33 +2. 汇报关系降级为节点上的可空属性,不再是图的主干语义(§三.2)。
  34 +3. `hire()` **不再需要 `reportsTo` 参数** —— 图由 `orggraph-planning` 整体规划、整体写入,不是靠招聘时逐个累积出来的(§四.2,这反而让 P2 对 `company-service.js` 的侵入变小了)。
  35 +4. 前端不再自己写"按汇报关系分层渲染",直接复用 Work Graph 的画布组件(§四.5)。
34 36  
35 37 ---
36 38  
37   -## 二、背景:为什么"边"是空的,不是"没扫描到"
38   -
39   -27 号文档 §四数据模型草稿把 `org_graph_edges` 定义为"公司静态协作结构"的关系边,`relation_type` 取 `reports_to` / `collaborates_with`(27-graph-agent-architecture.md:154-160)。但走查现有代码后发现:**这不是"数据源里有汇报关系、只是没写代码去读",而是系统里从来没有任何地方产生过"谁向谁汇报"这条信息**:
  39 +## 二、目标与非目标
40 40  
41   -- `company.json` 的 schema(`company-service.js:50-69`)里 `agents` 是纯字符串数组,没有汇报关系字段。
42   -- HR Agent 的招聘 SOP(`system/templates/hr/agent.md:18-32`)里,CTO 给 HR 发的"招聘需求单"是一句自然语言,HR 执行的命令是 `node bin/lemon.js company hire --id <公司ID> --role <角色名> --type executor --name "岗位名称"`——**没有 `--reportsTo` / `--manager` 这类参数**,`bin/lemon.js` 的 `parseArgs` 选项列表(`bin/lemon.js:63-67`)里也确实没有。
43   -- `config.json` 里唯一带层级意味的字段是 `parentId`(`packages/agent-core/AgentScaffold.js:63`「上级 Agent ID」),但它**只在 `scaffoldNewAgent()`(培养路径,company-service.js:241-283)里被写入**——走模板复制的 `hire()`(招聘路径,绝大多数场景)压根不 touch `parentId`。
44   -- 各模板 `config.json` 的 `type` 字段本身就不构成一致的层级体系:实际扫描结果是 `cxo` / `specialist` / `executor` / `devloop` / `meta` / `quality_assurance` / `skill` 混杂(见 §三.3),`ceo` 模板目录甚至**没有 `config.json`**,导致 `hire()` 里"`type==='cxo'` 才设置 `company.ceo`"的逻辑(company-service.js:211-213)对字面意义上的 "ceo" 角色从不生效,反而任何被标 `type: 'cxo'` 的角色(`cto`/`coo`/`innovator`)招聘时会覆盖 `company.ceo`——这是一个和 P2 无关、但 org-graph-creator 大概率会踩到的既有 bug,见 §八.6。
  41 +### 目标
45 42  
46   -**结论一**(v0.1 就已确立,本次不变):没有任何历史数据可以无损映射成 `reports_to` 边,`type` 字段也不能作为可靠的层级推导依据。存量数据只补节点(P0 已完成),不自动猜边。
  43 +1. **`orggraph-planning` agent**:建公司后自动运行,读公司目标/描述 + org graph 模板库 + 可招聘角色清单,产出一张公司级 workflow,并执行招聘。
  44 +2. **图的持久化**:`org_graph_nodes` / `org_graph_edges` 落地为执行流图,schema 与 `work_graph_edges` 同构。
  45 +3. **图的读写 API**:整体写入(规划产物落库)、增量修改(后续 HR 调整)、只读查询(供前端与执行引擎消费)。
  46 +4. **前端可视化**:复用 Work Graph 画布组件渲染 org graph。
47 47  
48   -### 为什么从"招聘时可选传 reportsTo"升级为"org-graph-creator 自动生成"
  48 +### 非目标
49 49  
50   -v0.1 的方案是把 `reportsTo` 做成 `hire()` 的一个可选参数,指望 HR/CEO agent 在招聘时"顺手"传一下。这个方案本身没有错——它仍然是本版本的**底层写入机制**(§四.4 完整保留)——但作为唯一的产生边的路径,有一个结构性弱点:HR 是**被动响应**式地一个一个招人("CTO 说招三个人,HR 就招三个人"),从来不会有一个时刻"通盘看一眼这家公司应该长成什么样"。用户指出的改进方向是补上这个缺失的环节:公司创建时先有一个专职的 agent 站在全局视角,根据这家公司**要做什么**(目标/描述)参考"这类公司通常怎么搭"(模板库),一次性把骨架定下来,之后 HR 的增量招聘只是在这个骨架上加叶子节点。这不是推翻 v0.1,而是在它之上加一层"谁来负责最开始把 reportsTo 填对"的机制。
  50 +- **不做**图的执行 —— 执行引擎、启动/暂停/单步/重试、SSE 全部属于 [27-P3/P4](./27-p3-p4-work-graph-console.md)。P2 只负责"把图建出来、存好、能读能画"。
  51 +- **不做** `company.json.agents[]` 的废弃 —— 它仍是"这家公司雇了谁"的真相源;org graph 回答的是"任务怎么流过这些人",两者并存。
  52 +- **不做**存量公司的补图 —— 见 §六。
  53 +- **不做** `workgraph-planning` agent 本身的设计 —— 属于 P3。
51 54  
52 55 ---
53 56  
54   -## 三、现状代码走查(决定设计边界的关键证据)
55   -
56   -### 3.1 `org_graph_edges` 表已建但从未被写入
57   -
58   -`server/services/graph-db.js:54-60` 定义了表结构(`id` 是 `INTEGER PRIMARY KEY AUTOINCREMENT`,不是 27 号文档草稿里写的 `TEXT PRIMARY KEY`——P0 实现时改了主键类型,P2 沿用实际实现)。`server/services/graph-seed-service.js:202` 的注释明确写着"`org_graph_edges` 本阶段留空——company.json 目前没有汇报关系数据,是预期状态,不是 bug"。
59   -
60   -### 3.2 真正被前端调用的招聘/辞退路径,和 `CompanyService` 里同名方法不是一回事
  57 +## 三、数据模型
61 58  
62   -- **招聘**:`POST /:companyId/hire`(`server/routes/company.js:908`)调用的是 `companyService.hireWithTraining()`(`company-service.js:381-383`,内部转调 `this.hire()`),`opts` 会从 route 的 `req.body` 一路传到 `hire()`。
63   -- **辞退**:`POST /:companyId/fire`(`server/routes/company.js:1009-1055`)**没有调用 `companyService.fire()`**。它在路由 handler 里直接重新实现了一遍逻辑:停心跳 → 按 `archive` 参数决定归档还是删除 → 直接改 `company.agents` 数组。`CompanyService.fire()`(company-service.js:411-433)**唯一的调用方是测试脚本 `server/scripts/test-company-e2e.js:227`,是死代码路径**。
64   -- **公司创建**:`POST /create`(`server/routes/company.js:22-38`),目前只创建一个"没有员工的空壳公司",响应里写着"请通知 MasterBot 从 Template 库中雇佣 Agent"——**创建流程本身不会自动招任何 agent**,这是 P2 要打破的现状(见 §四.10)。
65   -
66   -给"辞退"加清理逻辑必须改 `/fire` 路由 handler 本体;给"创建"接上 org-graph-creator 必须改 `/create` 路由 handler 本体——两者是同一类问题:真正生效的代码在路由层,不在 service 层的对应方法里。
67   -
68   -### 3.3 `type` 字段现状扫描
69   -
70   -对 `system/templates/*/config.json` 逐个读取 `type` 字段(39 个模板目录,2026-08-15 现场扫描):
71   -
72   -| type 取值 | 数量 | 代表角色 |
73   -|---|---|---|
74   -| `specialist` | 22 | hr, digester, hunter, content-selector, ... |
75   -| `cxo` | 3 | cto, coo, innovator |
76   -| `devloop` | 6 | devloop-pm/architect/coder/tester/integrator/triager |
77   -| `executor` | 2 | evolver, scrapling_crawler |
78   -| `meta` | 1 | evolver-youtube-crawler |
79   -| `quality_assurance` | 1 | qa-judge |
80   -| `skill` | 1 | claude-youtube-skills |
81   -| 无 `config.json` | 1 | **ceo** |
  59 +### 3.1 现状(P0/P1 已落地,本次要改的基础)
82 60  
83   -没有任何字段能区分层级,`type` 只是粗粒度的职能分类。
  61 +`server/services/graph-db.js` 已建三表。`org_graph_edges` **建了但从未写入过一行**(`graph-seed-service.js:202` 注释:"company.json 目前没有汇报关系数据,是预期状态")。P1 的 `graph-service.js:44` 额外建了一个**全量**唯一索引 `idx_org_graph_nodes_company_role`。
84 62  
85   -### 3.4 `listTemplates()` 已经提供了 org-graph-creator 需要的"可招聘角色清单"
86   -
87   -`company-service.js:480-` 的 `listTemplates()`(`GET /api/company/templates`,HR 已经在用,`system/templates/hr/agent.md:17` 的 `node bin/lemon.js company templates`)对每个模板目录返回 `{id, name, type, description, benchmarkScore, ...}`。这正是 org-graph-creator 判断"模板里写的这个角色,现实里有没有对应的可招模板"所需要的原始数据——**不需要为 org-graph-creator 新开一个查询接口**,复用现成的 CLI 命令即可(见 §四.9)。
88   -
89   -### 3.5 Company Dashboard 现状:纯扁平列表,无层级渲染
90   -
91   -`web/src/views/CompanyDashboard.vue:212-229`("团队成员" tab)用 `companyAgents` 渲染一个 `agents-grid`,卡片之间没有连线也没有分组。P2 加"至少列表展示"是**新增**一块 UI。
92   -
93   ----
94   -
95   -## 四、设计方案
96   -
97   -### 4.1 总体架构与触发时机
98   -
99   -```
100   -POST /api/company/create
101   - │
102   - ├─ companyService.create():建空壳公司(company.json.agents=[],不变)
103   - │
104   - ├─ 【新】companyService.hire(companyId, 'org-graph-creator', {})
105   - │ org-graph-creator 是系统模板,招聘流程与其他角色完全一致(走 §3.2 已有的 hire() 路径)
106   - │ 不传 reportsTo —— 它是基础设施角色,不挂在业务汇报树里(见 §4.9 末尾)
107   - │
108   - └─ 【新】fire-and-forget 触发首轮对话(复用 /hire 路由里已有的
109   - fetch(`/api/agent/{agentId}/chat`) 异步模式,company.js:917-940)
110   - 消息内容:公司 id/name/description(见 §4.10)
111   - │
112   - ▼
113   - org-graph-creator(LLM agent,system/templates/org-graph-creator/)
114   - 1. 读 system/org-graph-templates/*.json(结构骨架候选集,见 §4.8)
115   - 2. 读 `node bin/lemon.js company templates`(现实里能招到的角色,见 §3.4)
116   - 3. 综合公司 description + 上面两份数据,判断/裁剪出一套具体组织结构
117   - 4. 把这套结构写成一份规划记录(workspace/org-plan.md,见 §4.9)
118   - 5. 按拓扑序(先上级、后下级)依次执行:
119   - node bin/lemon.js company hire --id <公司ID> --role <角色> --reportsTo <上级角色>
120   - 6. 全部招完后关闭自己的心跳,转入休眠(一次性任务,见 §4.9 末尾)
121   -```
122   -
123   -`hire()`/`org_graph_nodes`/`org_graph_edges`/`reportsTo` 校验规则(§4.4)是这套流程的**底层机制**,org-graph-creator 只是这套机制目前最重要的调用方——HR agent 后续做增量招聘时,走的是同一条 `hire()` + `reportsTo` 路径,不是另一套。
124   -
125   -### 4.2 数据模型改动(在 P0 schema 上追加,不改已有列)
  63 +### 3.2 目标 schema
126 64  
127 65 ```sql
128   --- org_graph_nodes:追加"是否仍在职"标记,供 fire() 软删除用
  66 +-- 节点:追加 status(软删除,供 fire 用)
129 67 ALTER TABLE org_graph_nodes ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
130   --- status: 'active' | 'removed'
  68 + -- 'active' | 'removed'
  69 +-- 可选:汇报关系降级为节点属性(不再是图的边)
  70 +ALTER TABLE org_graph_nodes ADD COLUMN reports_to_node_id INTEGER REFERENCES org_graph_nodes(id);
131 71  
  72 +-- ⚠️ P1 已落地一个同表的全量唯一索引,必须先 DROP,否则「fire 后重新 hire 同角色」写不进去
  73 +DROP INDEX IF EXISTS idx_org_graph_nodes_company_role;
132 74 CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role_active
133   - ON org_graph_nodes(company_id, role_name)
134   - WHERE status = 'active';
135   -
136   -CREATE TABLE IF NOT EXISTS org_graph_edges (
137   - id INTEGER PRIMARY KEY AUTOINCREMENT,
138   - company_id TEXT NOT NULL,
139   - from_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id),
140   - to_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id),
141   - relation_type TEXT NOT NULL CHECK (relation_type IN ('reports_to','collaborates_with')),
142   - status TEXT NOT NULL DEFAULT 'active',
143   - created_at TEXT NOT NULL
  75 + ON org_graph_nodes(company_id, role_name) WHERE status = 'active';
  76 +
  77 +-- 边:从 relation_type 改为执行流语义,与 work_graph_edges 同构
  78 +-- (P0 建的 org_graph_edges 从未写入过数据,可以直接重建,无迁移负担)
  79 +DROP TABLE IF EXISTS org_graph_edges;
  80 +CREATE TABLE org_graph_edges (
  81 + id INTEGER PRIMARY KEY AUTOINCREMENT,
  82 + company_id TEXT NOT NULL,
  83 + from_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id),
  84 + to_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id),
  85 + condition TEXT NOT NULL CHECK (condition IN ('success','fail')),
  86 + edge_kind TEXT NOT NULL DEFAULT 'forward' CHECK (edge_kind IN ('forward','backward')),
  87 + status TEXT NOT NULL DEFAULT 'active',
  88 + created_at TEXT NOT NULL
144 89 );
145 90 CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_edges_dedupe_active
146   - ON org_graph_edges(company_id, from_node_id, to_node_id, relation_type)
147   - WHERE status = 'active';
  91 + ON org_graph_edges(company_id, from_node_id, to_node_id, condition) WHERE status = 'active';
148 92 ```
149 93  
150   -放进 `server/services/graph-db.js` 的 `getDb()` 里,用 `CREATE ... IF NOT EXISTS` / 探测后 `ALTER TABLE ... ADD COLUMN`,跟 P0 一贯的"只加不改"风格一致。**为什么软删除**:`org_graph_nodes`/`edges` 是审计轨迹的一部分(类比 DevLoop `timeline.ndjson` 的 append-only 原则),物理删除会让"这家公司 3 个月前的组织架构长什么样"无法回答。
  94 +> **`org_graph_edges` 可以直接 DROP 重建**,因为它从建表到现在一行数据都没有——这是本次模型变更成本最低的一点。
151 95  
152   -### 4.3 新模块 `server/services/org-graph-service.js`(不复用/不等待 P1 的 `graph-service.js`)
  96 +### 3.3 图的合法性约束(写入前校验)
153 97  
154   -```js
155   -// server/services/org-graph-service.js
156   -// 职责:org_graph_nodes/edges 的读写,供 company-service.js 和 routes/company.js 调用。
157   -// 与假想中 P1 的 graph-service.js(管 agent.md 从哪读)职责不同、文件不同,都基于 graph-db.js 的
158   -// getDb(),若 P1 之后落地,两个文件共存,org_graph_nodes 表 schema(含本文档新加的 status 列)
159   -// 双方共享,实现前需互相 diff 一次 graph-db.js。
160   -
161   -function upsertNode(companyId, roleName, systemAgentId = roleName) { /* INSERT ... ON CONFLICT ... */ }
162   -function removeNode(companyId, roleName) { /* UPDATE status='removed',级联失效相关边 */ }
163   -function addEdge(companyId, fromRoleName, toRoleName, relationType) { /* 见 4.4 校验规则 */ }
164   -function getOrgGraph(companyId) { /* JOIN nodes + edges + system_agents */ }
165   -
166   -module.exports = { upsertNode, removeNode, addEdge, getOrgGraph };
167   -```
168   -
169   -### 4.4 `hire()` / `hireWithTraining()` 改动 —— 新增可选 `reportsTo`(org-graph-creator 与 HR 共用的机制)
  98 +与 work graph 的执行前校验同一套规则([27-P3/P4](./27-p3-p4-work-graph-console.md) §四.A):
170 99  
171   -`company-service.js:189` 的 `hire(companyId, roleName, opts = {})`:`opts` 新增可选字段 `reportsTo`(角色名字符串,指向同一家公司里另一个已存在的角色)。模板命中分支末尾追加:
  100 +1. **无环**(仅针对 `edge_kind='forward'`)—— 三色 DFS,复用 DevLoop `doc-store.js:167` 的 `hasCycle()` 实现。
  101 +2. **单一入口** —— 必须恰好有一个入度为 0 的 forward 节点(任务从这里进入)。多入口会让"任务从哪开始"无法确定。
  102 +3. **可达性** —— 每个节点都必须从入口可达,孤立节点直接拒绝。
  103 +4. **回退边必须显式标注** —— 任何指向"拓扑序更早节点"的边,`edge_kind` 必须是 `backward`;引擎独立复核,不信任标注(27 号文档 §3.4 第 5 点的同款兜底)。
172 104  
173   -```js
174   -try {
175   - orgGraphService.upsertNode(companyId, roleName);
176   - if (opts.reportsTo) {
177   - orgGraphService.addEdge(companyId, roleName, opts.reportsTo, 'reports_to');
178   - }
179   -} catch (e) {
180   - console.warn(`[Company] org_graph 写入失败(不影响招聘结果): ${e.message}`);
181   -}
182   -```
  105 +---
183 106  
184   -try/catch-not-fail:`org_graph_nodes`/`edges` 在 P2 阶段仍然是 `company.json.agents[]` 的旁路记录,写失败不能让招聘这个更重要的操作跟着失败——**这一点对 org-graph-creator 尤其重要**:它会在几十秒内连续调用 `hire()` 十几次,任何一次的 org-graph 旁路写入失败都不该让整个批量招聘链路中断。
  107 +## 四、设计方案
185 108  
186   -**`reportsTo` 校验规则**:
  109 +### 4.1 `orggraph-planning` agent
187 110  
188   -1. `reportsTo` 指向的角色必须已经是该公司 `org_graph_nodes` 里一条 `status='active'` 的节点——否则报错拒绝这次 `hire()`。**这一条对 org-graph-creator 是硬约束**:它必须按拓扑序(先招上级、后招下级)执行,见 §4.11。
189   -2. `reportsTo === roleName` 直接拒绝。
190   -3. 不做环检测——留到 P4 Loop-Validator 上线、或后续 Work Graph 需要拓扑排序时再补,刻意简化。
  111 +新增 system agent 模板 `system/templates/orggraph-planning/`(`config.json.type` 取 `meta-bootstrap`,与业务角色区分)。工作流:
191 112  
192   -`hireWithTraining()`(company-service.js:381-383)本身只是 `hire()` 的一层包装,`opts` 透传。**Route 层需要同步改**:`server/routes/company.js:908-914` 的 `/hire` handler 目前从 `req.body` 解构时没有 `reportsTo` 字段,需要加上。
  113 +1. **读输入**:公司 `id` / `name` / `description`(由 §4.4 的首轮消息带入)。
  114 +2. **读候选骨架**:`ls system/org-graph-templates/`,逐个读取,理解各自的 `whenToUse`。
  115 +3. **读可招角色**:`node bin/lemon.js company templates`(复用现成 CLI,`listTemplates()` 返回 id/name/type/description)。
  116 +4. **规划**:综合三者,产出一张 workflow —— 节点列表 + 执行流边。允许裁剪、替换、组合多个模板,也允许判断需要一个模板库里没有的新角色。
  117 +5. **落盘规划记录**:`workspace/orggraph-plan.md`(图 + 为什么这么设计的理由),供事后审计。
  118 +6. **提交图**:调用 §4.3 的整体写入 API,图通过 §3.3 校验后落库。
  119 +7. **执行招聘**:按拓扑序对每个节点调 `hire()`。模板不存在的角色走既有 `needs_training` 分支(自动触发 agent-creator + innovator,`company.js:917-940` 已实现)。
  120 +8. **收尾**:关闭自己的心跳转入休眠。后续增量调整归 HR。
193 121  
194   -### 4.5 `/fire` 路由改动(不是 `CompanyService.fire()`——见 §3.2)
  122 +`orggraph-planning` 自己**不出现在它规划的 org graph 里** —— 它是基础设施角色,类比 `agent-creator` / `innovator`。
195 123  
196   -`server/routes/company.js:1009-1055`,"从公司 agents 列表中移除"之后追加:
  124 +### 4.2 `hire()` 保持不变(v0.4 简化)
197 125  
198   -```js
199   -try {
200   - orgGraphService.removeNode(companyId, role);
201   -} catch (e) {
202   - console.warn(`[Fire] org_graph 清理失败(不影响辞退结果): ${e.message}`);
203   -}
204   -```
  126 +v0.2/v0.3 设计的 `hire(companyId, roleName, {reportsTo})` **不再需要**。原因:图现在是被整体规划、整体写入的产物,不是靠招聘动作逐次累积出来的。招聘只负责"把这个角色的目录建出来",与图的结构无关。
205 127  
206   -`removeNode()` 把该节点 `status` 置为 `removed`,同时把所有以它为 `from_node_id`/`to_node_id` 的 active 边一并置为 `removed`。`CompanyService.fire()` 同步做一样的改动(仅为不让测试脚本继续验证一个跟线上不一致的假实现)。
  128 +因此 P2 对 `company-service.js` 的改动只剩一处:`hire()` 成功后写一行 `org_graph_nodes`(幂等,try/catch 不阻断主流程)—— 这一行 P1 的 `graph-service.upsertOrgGraphNode()` 已经实现,**P2 直接复用,不需要新代码**。
207 129  
208   -### 4.6 只读 REST 端点:`GET /api/company/:companyId/org-graph`
  130 +### 4.3 API
209 131  
210   -```js
211   -router.get('/:companyId/org-graph', (req, res) => {
212   - res.json(orgGraphService.getOrgGraph(req.params.companyId));
213   -});
214   -```
  132 +| 端点 | 用途 |
  133 +|---|---|
  134 +| `POST /api/company/:companyId/org-graph` | **整体写入**:接收 `{nodes:[], edges:[]}`,过 §3.3 四项校验,事务性落库。校验失败返回 400 + 具体哪条规则不满足(供 `orggraph-planning` 读错误后自行修正重试) |
  135 +| `PATCH /api/company/:companyId/org-graph` | **增量修改**:加/删节点、加/删边(后续 HR 调整用)。同样过校验——改完之后整张图仍须合法 |
  136 +| `GET /api/company/:companyId/org-graph` | **只读**:返回 `{nodes, edges}`,只含 `status='active'` 的行 |
215 137  
216   -返回形状:
  138 +`GET` 返回形状:
217 139  
218   -```json
  140 +```jsonc
219 141 {
220 142 "nodes": [
221   - { "id": 12, "roleName": "cto", "systemAgentId": "cto", "status": "active" },
222   - { "id": 15, "roleName": "hr", "systemAgentId": "hr", "status": "active" }
  143 + { "id": 12, "roleName": "code-planner", "systemAgentId": "code-planner", "status": "active" },
  144 + { "id": 13, "roleName": "code-coder", "systemAgentId": "code-coder", "status": "active" },
  145 + { "id": 14, "roleName": "code-validator", "systemAgentId": "code-validator", "status": "active" }
223 146 ],
224 147 "edges": [
225   - { "fromRoleName": "hr", "toRoleName": "cto", "relationType": "reports_to" }
  148 + { "from": 12, "to": 13, "condition": "success", "edgeKind": "forward" },
  149 + { "from": 13, "to": 14, "condition": "success", "edgeKind": "forward" },
  150 + { "from": 14, "to": 13, "condition": "fail", "edgeKind": "backward" }
226 151 ]
227 152 }
228 153 ```
229 154  
230   -不在这一层拼装 `name`/`type`/心跳状态——那些字段权威来源始终是 `companyService.listAgents()`,前端按 `roleName` 去 join 已经在加载的 `companyAgents` 列表。
231   -
232   -### 4.7 前端:Company Dashboard 新增"组织架构"渲染
  155 +不在这一层拼装 `name` / `type` / 心跳状态 —— 那些的权威来源是 `companyService.listAgents()`,前端按 `roleName` join 即可,避免两个端点的字段定义漂移。
233 156  
234   -不新增 tab。在"团队成员" tab 的 `agents-grid` 上方插入按 `reports_to` 边分层的缩进列表;没有边数据的节点退化成当前的平铺 grid(保证存量公司 Dashboard 展示效果不变);不引入图形库依赖。
  157 +### 4.4 公司创建流程接入
235 158  
236   -### 4.8 `system/org-graph-templates/`:组织结构模板库
  159 +`server/routes/company.js:22-38` 的 `POST /create`,在 `companyService.create()` 之后追加:
237 160  
238   -新增目录,每个文件是一种"公司类型"的组织结构骨架,供 org-graph-creator 参考:
239   -
240   -```json
241   -// system/org-graph-templates/dev.json
242   -{
243   - "id": "dev",
244   - "label": "开发型公司(持续构建/维护软件产品)",
245   - "whenToUse": "公司目标包含实现某个产品/功能、需要跑代码开发-测试循环时优先参考",
246   - "roles": [
247   - { "role": "ceo", "reportsTo": null, "note": "公司负责人,制定目标" },
248   - { "role": "cto", "reportsTo": "ceo", "note": "技术负责人" },
249   - { "role": "hr", "reportsTo": "ceo", "note": "负责后续增量招聘" },
250   - { "role": "devloop-pm", "reportsTo": "cto" },
251   - { "role": "devloop-architect", "reportsTo": "devloop-pm" },
252   - { "role": "devloop-coder", "reportsTo": "devloop-architect" },
253   - { "role": "devloop-tester", "reportsTo": "devloop-coder" }
254   - ]
255   -}
256   -```
257   -
258   -```json
259   -// system/org-graph-templates/content.json
260   -{
261   - "id": "content",
262   - "label": "内容型公司(持续产出/发布内容)",
263   - "whenToUse": "公司目标是短视频/文章/直播等内容的选题-生产-发布流水线",
264   - "roles": [
265   - { "role": "ceo", "reportsTo": null },
266   - { "role": "coo", "reportsTo": "ceo", "note": "内容运营负责人" },
267   - { "role": "hr", "reportsTo": "ceo" },
268   - { "role": "trend-scout-cn", "reportsTo": "coo" },
269   - { "role": "story-architect", "reportsTo": "coo" },
270   - { "role": "hook-editor", "reportsTo": "story-architect" },
271   - { "role": "auto-publisher", "reportsTo": "coo" }
272   - ]
273   -}
274   -```
275   -
276   -```json
277   -// system/org-graph-templates/ops.json
278   -{
279   - "id": "ops",
280   - "label": "运营型公司(监控/增长/客户运营为主)",
281   - "whenToUse": "公司目标偏向数据监控、增长实验、社群/客户运营,不以持续研发或内容生产为主",
282   - "roles": [
283   - { "role": "ceo", "reportsTo": null },
284   - { "role": "coo", "reportsTo": "ceo" },
285   - { "role": "hr", "reportsTo": "ceo" },
286   - { "role": "community-manager", "reportsTo": "coo" },
287   - { "role": "strategy-reflector", "reportsTo": "coo" }
288   - ]
  161 +```js
  162 +const bootstrap = companyService.hire(id, 'orggraph-planning', {});
  163 +if (bootstrap.status === 'hired_from_template') {
  164 + fetch(`http://127.0.0.1:${PORT}/api/agent/orggraph-planning/chat`, {
  165 + method: 'POST', headers: { 'Content-Type': 'application/json' },
  166 + body: JSON.stringify({
  167 + message: `公司 ${id}(${name})已创建。描述:${description || '(未填写)'}\n请按 orggraph-planning 工作流规划并建立这家公司的 workflow。`,
  168 + conversationId: `orggraph-bootstrap-${id}-${Date.now().toString(36)}`,
  169 + }),
  170 + }).catch(e => console.error('[Create] orggraph-planning 触发失败:', e.message));
289 171 }
290 172 ```
291 173  
292   -设计要点:
293   -
294   -- **这是起点集合,不是穷举枚举**——目录里有几个 `.json` 就有几种候选,新增一种公司类型只需要加一个新文件,不需要改任何代码(对应用户选定的"固定模板文件 + LLM 选型"方案:org-graph-creator 在 prompt 里被告知"`ls system/org-graph-templates/` 看看有哪些候选,都读一遍,选最贴近或组合出一个新的",不是在代码里写死一个 `switch(type)`)。
295   -- `roles[].role` 里写的角色名是**建议**,不是强制——org-graph-creator 必须对照 §3.4 的 `company templates` 结果核实这个角色是否真的存在可用模板;如果没有,`hire()` 会走既有的 `needs_training` 分支(自动触发 agent-creator + innovator 培养,company.js:917-940 已有),org-graph-creator 不需要对此做特殊处理,只需要接受 `hire()` 的返回结果继续往下走。
296   -- 模板之间允许被"揉合":比如一家公司同时要做内容生产和一个配套小工具的开发,org-graph-creator 的 prompt 里应该允许它同时参考 `content.json` 和 `dev.json`、合并出一套结构,而不是只能二选一。这是把"选型"这个判断交给 LLM 的直接后果——好处是不需要穷举组合,代价见 §八.1。
297   -- P2 阶段先提供以上 3 个模板作为示例/起点(dev/content/ops),覆盖当前 `system/templates/*` 里能看到的三类角色画像(devloop-* 系列、trend-scout/story-architect/hook-editor 系列、community-manager/strategy-reflector),不追求一次性穷尽所有可能的公司类型。
  174 +放在路由层而非 `CompanyService.create()` 内部 —— 与 `/hire` 的训练触发逻辑(`company.js:917-940`)保持同一约定:依赖 HTTP 层上下文的代码不下沉到 service。
298 175  
299   -### 4.9 `system/templates/org-graph-creator/`:org-graph-creator agent 本体
  176 +**降级**:这一步失败不影响公司创建本身,退化为一家没有 workflow 的空壳公司(等同 P2 上线前的现状)。
300 177  
301   -新增一个系统模板(和 `hr`/`ceo` 同级),字段沿用现有模板骨架(`agent.md`/`soul.md`/`identity.md`/`tools.md`/`config.json`,`config.json.type` 取一个新值 `meta-bootstrap`,与其余"业务角色"的 `type` 区分开)。`agent.md` 的核心工作流大纲(不是完整文案,实现时按仓库现有模板的写作风格——参照 `system/templates/hr/agent.md` 的"招·留·管·退"体例——展开):
  178 +### 4.5 前端
302 179  
303   -1. **读取输入**:公司 `id`/`name`/`description`(由 §4.10 的首轮消息带入)。
304   -2. **读候选骨架**:`ls system/org-graph-templates/`,逐个读取,理解每个模板的 `whenToUse`。
305   -3. **读可招角色清单**:`node bin/lemon.js company templates`,拿到当前系统里真实存在的模板列表(id/name/type/description)。
306   -4. **规划**:综合 1/2/3,判断这家公司最贴近哪个(或哪几个)模板,裁剪/替换/新增角色,产出一份具体的"角色 + reportsTo"清单。**必须包含一个不挂靠任何模板、始终存在的 `hr` 节点**(如果模板里没有就补上)——它是这套结构未来能否被增量维护的前提,org-graph-creator 自己不会长期存在(见第 6 步)。
307   -5. **落盘规划记录**:把第 4 步的结果写成 `workspace/org-plan.md`(角色列表 + 汇报关系 + 为什么这么选的简短理由),供事后审计/调试——这不是给代码读的结构化数据(结构化数据是 DB 里的 `org_graph_nodes`/`edges`),是给人看的"这次规划是怎么想的"。
308   -6. **按拓扑序执行招聘**:从没有 `reportsTo` 的根节点开始,逐层往下,每个角色调用 `node bin/lemon.js company hire --id <公司ID> --role <角色> --reportsTo <上级角色(根节点省略)>`。见 §4.11 关于失败重试的约定。
309   -7. **收尾**:全部招聘执行完(或明确判断某些角色因为需要培养而转入异步流程后),把自己的 `heartbeat.enabled` 设为 `false`(复用现有心跳开关机制,`server/routes/company.js:774` 一带的 `/heartbeat` 端点),不主动退出/不被 fire——它是这家公司组织架构决策的历史记录持有者,保留在 `company.agents[]`/`org_graph_nodes` 里,但不再被心跳唤醒、不参与后续任何招聘(后续增量招聘是 HR 的职责)。
  180 +**不自己写渲染逻辑** —— org graph 与 work graph 同构,直接复用 [27-P3/P4](./27-p3-p4-work-graph-console.md) T3 的画布组件(其本身又是扩展自既有的 `web/src/components/devloop/UnitsDAG.vue`,已实现 SVG + Kahn 分波布局 + 状态着色 + 点击下钻)。
310 181  
311   -**org-graph-creator 自己在组织图里的位置**:不产生 `reportsTo` 出边(不属于它规划出来的业务汇报树),`getOrgGraph()` 会把它渲染成一个独立的"根"(和 CEO 一样是无入边——不,无出边——的顶层节点,§4.7 的前端渲染本来就允许多个顶层节点并存)。它是基础设施角色,类比 `agent-creator`/`innovator` 这些不出现在业务组织架构里、但确实活跃在系统里的 meta 角色。
  182 +这也意味着 **P2 的前端工作依赖 P3 的画布组件先做出来**。若想让 P2 独立上线,可以先在 Company Dashboard 用一个纯文本的拓扑序列表占位(`code-planner → code-coder → code-validator`),等 P3 画布就绪再替换。
312 183  
313   -### 4.10 公司创建流程改动
  184 +### 4.6 `system/org-graph-templates/`
314 185  
315   -`server/routes/company.js:22-38` 的 `POST /create`:`companyService.create()` 成功后,追加:
  186 +每个文件是一种公司类型的 workflow 骨架:
316 187  
317   -```js
318   -// 1. 同步招聘 org-graph-creator(跟其他角色一样走模板复制,很快)
319   -const bootstrapResult = companyService.hire(id, 'org-graph-creator', {});
320   -
321   -// 2. fire-and-forget 触发它的第一轮对话——复用 /hire 路由里已有的
322   -// fetch(`/api/agent/{agentId}/chat`, ...) 异步模式(company.js:917-940)
323   -if (bootstrapResult.status === 'hired_from_template') {
324   - fetch(`http://127.0.0.1:${PORT}/api/agent/org-graph-creator/chat`, {
325   - method: 'POST',
326   - headers: { 'Content-Type': 'application/json' },
327   - body: JSON.stringify({
328   - message: `公司 ${id}(${name})已创建。公司描述:${description || '(未填写)'}\n请你按照 org-graph-creator 的工作流规划并执行这家公司的初始组织结构。`,
329   - conversationId: `org-graph-bootstrap-${id}-${Date.now().toString(36)}`,
330   - }),
331   - }).catch(e => console.error('[Create] org-graph-creator 触发失败:', e.message));
  188 +```jsonc
  189 +// system/org-graph-templates/dev.json
  190 +{
  191 + "id": "dev",
  192 + "label": "开发型公司",
  193 + "whenToUse": "目标包含实现某个产品/功能、需要跑代码开发-测试循环时优先参考",
  194 + "nodes": ["code-planner", "code-coder", "code-validator"],
  195 + "edges": [
  196 + { "from": "code-planner", "to": "code-coder", "condition": "success" },
  197 + { "from": "code-coder", "to": "code-validator", "condition": "success" },
  198 + { "from": "code-validator", "to": "code-coder", "condition": "fail", "edgeKind": "backward" }
  199 + ]
332 200 }
333 201 ```
334 202  
335   -响应文案同步调整:不再是"请通知 MasterBot 从 Template 库中雇佣 Agent",改成"公司已创建,正在由 org-graph-creator 自动规划组织结构(异步进行中)"。
336   -
337   -**为什么是路由层而不是 `companyService.create()` 内部**:跟 §3.2/§4.5 同一个理由——`fetch` 调用自己的 HTTP 服务、依赖 `PORT` 等运行时上下文的代码,现有代码库的既有约定是放在路由层(`/hire` 的训练触发逻辑就是这么做的),`CompanyService` 保持不依赖 HTTP 层。
  203 +同理提供 `content.json`(`content-planner → content-writer → content-validator`)、`ops.json`。
338 204  
339   -**失败降级**:如果 `hire(id, 'org-graph-creator', {})` 本身失败(比如模板目录被误删),`create()` 的返回结果仍然是成功的空壳公司——这一步失败不应该让公司创建本身失败,跟现有的"招聘失败不影响主流程"原则一致。降级后的行为等同于 P2 上线前的现状:一个没有 org graph、需要人工手动招聘的空壳公司。
  205 +设计要点:
340 206  
341   -### 4.11 拓扑序执行失败与重试
  207 +- **是起点集合,不是穷举枚举** —— 加一种公司类型只需加一个 JSON 文件,不改任何代码。`orggraph-planning` 读全部模板后自行选型/裁剪/组合,不是代码里的 `switch(type)`。
  208 +- **`nodes` 里的角色名是建议**,`orggraph-planning` 必须对照 `company templates` 核实是否真有可招模板;没有则走 `needs_training` 培养分支。
  209 +- **模板里的角色名必须遵循 [27-NAMING](./27-naming-conventions.md) §三的 `{domain}-{职能}` 规则**,不允许裸用 `planner` / `validator`。
  210 +- ⚠️ **避开命名冲突 id**:`agent-creator` / `digester` / `evolver` / `evolver-youtube-crawler` / `hunter` / `innovator` 这 6 个 id 在 `system/agents/` 与 `system/templates/` 下同时存在且内容可能不同步(27 号文档 §七),模板里不要引用它们。
342 211  
343   -§4.4 的校验规则 1 要求 `reportsTo` 目标必须已经是 active 节点——这对一个由 LLM 驱动、顺序执行十几次 `hire()` 调用的 agent 来说,存在"记错顺序、先招了子节点"的现实风险。设计上不在 `hire()` 侧放宽这条校验(放宽会让"脏数据"更容易产生,参见 v0.1 §4.4 校验规则 1 的论证),而是让失败可恢复:
  212 +---
344 213  
345   -- `hire()` 对这种情况返回的错误信息要明确指出"角色 `<reportsTo>` 尚不存在于该公司,请先招聘它"(不是一个笼统的 500)——org-graph-creator 的 prompt 里要求它读到这类错误后,转去招那个缺失的上级角色,成功后再重试当前角色。
346   -- 每个角色的 `hire()` 调用天然幂等(`already_exists` 短路,company-service.js:194-196),所以"招错顺序 → 报错 → 补招上级 → 重试子节点"这个循环不会因为重复调用产生副作用。
347   -- 不引入一个专门的"依赖排序引擎"帮 org-graph-creator 提前算好正确顺序——§4.9 第 4 步产出的规划记录本身就是一份树状结构,从根遍历下去自然就是拓扑序,只要 org-graph-creator 老老实实按自己写的 `org-plan.md` 从上往下执行,这个问题不常发生;真发生时靠上面的重试机制兜底,不值得为小概率场景builds额外基础设施。
  214 +## 五、任务划分
348 215  
349   -### 4.12 存量数据 & 已建公司:明确不做的两件事
  216 +| # | 任务 | 文件 | 依赖 |
  217 +|---|---|---|---|
  218 +| **T1** | schema:节点加 `status` / `reports_to_node_id`;DROP 全量唯一索引改建部分索引;**DROP 重建 `org_graph_edges` 为执行流边** | `server/services/graph-db.js` | 无 |
  219 +| **T2** | `org-graph-service.js`:图的整体写入(含 §3.3 四项校验)、增量改、只读查询、节点软删除级联失效边 | `server/services/org-graph-service.js`(新增) | T1 |
  220 +| **T3** | 三个 API 端点(POST / PATCH / GET) | `server/routes/company.js` | T2 |
  221 +| **T4** | `system/org-graph-templates/{dev,content,ops}.json` | 新增 | 无,可并行 |
  222 +| **T5** | `system/templates/orggraph-planning/` agent 本体(含 §4.1 八步工作流) | 新增 | T3, T4 |
  223 +| **T6** | `/create` 路由接入自动招聘 + 异步触发 | `server/routes/company.js` | T5 |
  224 +| **T7** | `/fire` 路由接入节点软删除(改路由 handler 本体,**不是** `CompanyService.fire()`——后者只有测试脚本在用,是死代码路径) | `server/routes/company.js` | T2 |
  225 +| **T8** | 前端:复用 P3 画布组件渲染 org graph(或先上纯文本拓扑序占位) | `web/src/views/CompanyDashboard.vue` | T3(+ P3 的 T3 若要画布) |
  226 +| **T9** | 测试:`org-graph-service.test.js`(四项校验各一条负例、幂等写入、软删除级联)+ e2e 扩展 | `server/tests/unit/org-graph-service.test.js` | T2, T3 |
350 227  
351   -沿用 v0.1 的结论并扩展一条:
352   -
353   -| 场景 | 决定 | 理由 |
354   -|---|---|---|
355   -| P2 上线前已存在的公司(P0 只回填了节点,没有边) | 不补边,`org_graph_edges` 就是空的,前端退化展示 | 没有可靠数据源支撑(见 §二),按 `type`/`parentId` 反推都已被证伪,见 v0.1 版本 §四.7 的完整方案对比表 |
356   -| P2 上线后,一家已经跑过 org-graph-creator(或已经手动搭好组织架构)的公司,业务发生变化想要"重新规划" | 不支持——org-graph-creator 只在 §4.10 的创建时触发点运行一次 | 对一家已有员工、可能正在跑任务的公司做"推倒重来"式重新规划,涉及已有节点保留/替换、进行中任务归属等问题,复杂度显著高于"对着空壳公司规划一次",留 Open Question(§八.5),不在 P2 范围内 |
  228 +**顺序**:T1→T2→T3 是主干。T4 可最早并行。T5 依赖 T3/T4。T7 独立于 T5/T6,可并行。T8 视是否等 P3 画布决定。
357 229  
358 230 ---
359 231  
360   -## 五、任务划分(Task Breakdown)
361   -
362   -| # | 任务 | 涉及文件 | 依赖 | 验收方式 |
363   -|---|------|----------|------|----------|
364   -| **T1** | schema 追加:`org_graph_nodes.status` 列、两个唯一索引、`org_graph_edges.status`/`created_at` | `server/services/graph-db.js` | 无 | 单测:`PRAGMA table_info` 能看到新列;重复插入同 `(company,role)` 的 active 节点报唯一索引冲突 |
365   -| **T2** | 新建 `org-graph-service.js`:`upsertNode`/`removeNode`/`addEdge`/`getOrgGraph`,含 `reportsTo` 校验规则(§4.4) | `server/services/org-graph-service.js`(新增) | T1 | 单测覆盖幂等写入、非法 `reportsTo`(不存在/自环)报错且报错信息包含缺失的角色名(供 §4.11 重试逻辑解析)、`removeNode` 级联失效边 |
366   -| **T3** | `hire()` 接入:写 node + 可选 edge,try/catch 不阻断主流程 | `server/services/company-service.js` | T2 | 单测:`hire()` 传 `reportsTo` 后 DB 各新增一行;`org-graph-service` 内部抛错时 `hire()` 仍返回 `hired_from_template` |
367   -| **T4** | `/hire` 路由透传 `reportsTo`;`/fire` 路由接入 `removeNode()`;`CompanyService.fire()` 同步改 | `server/routes/company.js`、`server/services/company-service.js` | T2 | 集成测试:`POST /hire` body 带 `reportsTo` → 查 DB 有边;`POST /fire` 后节点与相关边均 `removed` |
368   -| **T5** | 新增只读端点 `GET /:companyId/org-graph` | `server/routes/company.js` | T2 | 路由测试:返回形状符合 §4.6 契约;不存在的公司返回空数组而非 500 |
369   -| **T6** | CLI:`bin/lemon.js company hire` 新增 `--reportsTo` 参数 | `bin/lemon.js` | T4 | 手工跑一次带 `--reportsTo` 的 hire,核对返回值与 DB |
370   -| **T7** | 新增 `system/org-graph-templates/`:至少 `dev.json`/`content.json`/`ops.json` 三个骨架 | `system/org-graph-templates/*.json`(新增) | 无(可与 T1-T6 并行) | 人工 review:每个文件的 `roles[].role` 都能在 `system/templates/*` 或 `system/agents/*` 里找到同名目录(避免规划出一个连培养都无从谈起的角色名) |
371   -| **T8** | 新增 `system/templates/org-graph-creator/`:agent.md/soul.md/identity.md/tools.md/config.json,落地 §4.9 工作流 | `system/templates/org-graph-creator/*`(新增) | T6, T7 | 人工 review + 手动跑一次完整对话,核对生成的 `org-plan.md` 与实际 `hire()` 调用序列一致 |
372   -| **T9** | `/create` 路由接入自动招聘 + fire-and-forget 触发首轮对话 | `server/routes/company.js` | T4, T8 | 集成测试:`POST /create` 后立即查 `company.agents` 含 `org-graph-creator`;等待异步对话完成后查 `org_graph_nodes`/`edges` 有业务角色数据 |
373   -| **T10** | HR agent 模板更新:明确"公司初建时的组织结构由 org-graph-creator 负责,HR 只做后续增量招聘"的职责边界 + `reportsTo` 用法 | `system/templates/hr/agent.md` | T6 | 人工 review,确认新增段落不与现有"强制工作流"章节冲突 |
374   -| **T11** | 前端:Dashboard 团队成员 tab 加分层列表渲染,退化态验证 | `web/src/views/CompanyDashboard.vue` | T5 | 手动验证:有 `reportsTo` 边的测试公司显示缩进层级;存量公司展示效果与改动前一致 |
375   -| **T12** | 回归测试 + 端到端测试:新建 `server/tests/unit/org-graph-service.test.js`;扩展 `server/scripts/test-company-e2e.js` 覆盖"建公司 → org-graph-creator 自动跑完 → 查 org-graph"全链路 | `server/tests/unit/org-graph-service.test.js`(新增)、`server/scripts/test-company-e2e.js` | T3, T4, T9 | `npm run test:unit` 全绿;e2e 脚本能跑通全链路(org-graph-creator 这一步用 mock 模式,参考 DevLoop 的 `ClaudeCliProtocol({mock:true})` 思路,不在自动化测试里真的起 Claude CLI 子进程) |
376   -
377   -**顺序建议**:T1→T2 是地基。T3/T4/T5 可在 T2 后并行。T7(组织结构模板库)不依赖任何代码改动,可以最早开工、和 T1-T6 并行写。T8(org-graph-creator agent 本体)依赖 T6(CLI 参数)和 T7(有模板可读)。T9(接入创建流程)依赖 T8。T10/T11 分别依赖 T6/T5。T12 贯穿全程,全链路部分要等 T9 完成。
  232 +## 六、存量公司
378 233  
379   ----
  234 +P0 的 `seedCompanyAgents()` 已把所有存量公司的角色回填成 `org_graph_nodes`(**无边**)。P2 上线时它们的 workflow 是空的。
380 235  
381   -## 六、验收标准
  236 +**决定:不自动补边。** 三个候选方案里前两个都被证伪:
382 237  
383   -沿用 27 号文档给出的 P2 验收标准(27-graph-agent-architecture.md:215),细化为可直接验证的检查项:
  238 +| 方案 | 为什么不选 |
  239 +|---|---|
  240 +| 按 `config.json.parentId` 反推 | 该字段只在 `scaffoldNewAgent()` 培养路径写入,模板招聘来的 agent 根本没有,覆盖率太低 |
  241 +| 按 `config.json.type` 推层级 | 实测 type 取值是 `specialist`(22) / `cxo`(3) / `devloop`(6) / `executor`(2) / `meta`(1) / `quality_assurance`(1) / `skill`(1) 混杂,且 `ceo` 模板**没有 config.json**——它不是层级体系,更不是执行顺序 |
  242 +| **不补,留空(选定)** | 没有数据来源支撑的边,不如没有边。存量公司要用新流程,就重新跑一次 `orggraph-planning` |
384 243  
385   -1. **端到端场景(核心)**:`POST /api/company/create` 创建一家 `description` 明确写"我们要做一个短视频内容自动化生产的公司"的测试公司,不做任何手动干预:
386   - - 几秒内 `company.agents` 出现 `org-graph-creator`
387   - - org-graph-creator 完成一轮对话后,`GET /:companyId/org-graph` 能看到至少 `ceo`/`hr` 加若干与内容生产相关的角色(不强制要求跟 `content.json` 模板逐字一致,因为允许 LLM 裁剪),且存在至少一条 `reports_to` 边
388   - - `workspace/org-plan.md`(org-graph-creator 私有目录下)存在且内容与实际 `hire()` 调用结果一致
389   -2. 对一家新建测试公司依次 `hire(companyId, 'cto', {})` → `hire(companyId, 'hr', {reportsTo: 'cto'})`:`org_graph_nodes` 新增两行,`org_graph_edges` 新增一行 `(hr → cto, reports_to, active)`。
390   -3. `hire(companyId, roleName, {reportsTo: 'nonexistent-role'})`:报错拒绝,不产生半成品数据,且错误信息包含缺失角色名(供 §4.11 重试逻辑验证)。
391   -4. `POST /:companyId/hire` 的 HTTP body 带 `reportsTo` 字段:验证请求确实透传到了 `hireWithTraining()`。
392   -5. `POST /:companyId/fire` 辞退某角色后:该节点与其相关边均 `status='removed'`,`GET /:companyId/org-graph` 不再返回它们。
393   -6. `GET /:companyId/org-graph` 对一家 P0 迁移进来、从未走过新流程的存量公司:返回 `edges: []`,`nodes` 数量与 `company.json.agents.length` 一致。
394   -7. Company Dashboard:有 `reportsTo` 数据的测试公司按层级缩进展示;存量公司展示效果与 P2 上线前一致。
395   -8. `npm run test:unit` 全绿,含新增的 `org-graph-service.test.js`。
  244 +**给存量公司补图的路径**:提供一个手动触发 `orggraph-planning` 的端点,让用户对已有公司显式重跑一次规划。这比自动猜测诚实,也给了用户选择权。
396 245  
397 246 ---
398 247  
399   -## 七、影响面 / Blast Radius
  248 +## 七、影响面
400 249  
401   -| 文件 | 改动类型 |
402   -|------|----------|
403   -| `server/services/graph-db.js` | 修改:追加 `status` 列 + 两个唯一索引 |
404   -| `server/services/org-graph-service.js` | 新增:`upsertNode`/`removeNode`/`addEdge`/`getOrgGraph` |
405   -| `server/services/company-service.js` | 修改:`hire()` 接入 org-graph 写入;`fire()` 同步加 `removeNode()` |
406   -| `server/routes/company.js` | 修改:`/create` 接入自动招聘 org-graph-creator + 异步触发对话;`/hire` 透传 `reportsTo`;`/fire` 接入 `removeNode()`;新增 `GET /:companyId/org-graph` |
407   -| `bin/lemon.js` | 修改:`company hire` 命令新增 `--reportsTo` 选项 |
408   -| `system/org-graph-templates/*.json` | 新增:组织结构模板库(至少 dev/content/ops 三份) |
409   -| `system/templates/org-graph-creator/*` | 新增:新的系统模板(agent.md/soul.md/identity.md/tools.md/config.json) |
410   -| `system/templates/hr/agent.md` | 修改:明确与 org-graph-creator 的职责边界 |
411   -| `web/src/views/CompanyDashboard.vue` | 修改:团队成员 tab 加分层渲染逻辑 |
  250 +| 文件 | 改动 |
  251 +|---|---|
  252 +| `server/services/graph-db.js` | 节点加列、索引重建、`org_graph_edges` 重建 |
  253 +| `server/services/org-graph-service.js` | 新增 |
  254 +| `server/routes/company.js` | `/create` 接入;三个 org-graph 端点;`/fire` 接入软删除 |
  255 +| `system/org-graph-templates/*.json` | 新增 |
  256 +| `system/templates/orggraph-planning/*` | 新增 |
  257 +| `web/src/views/CompanyDashboard.vue` | 渲染入口 |
412 258  
413   -**明确不改**:`company.json` 的 schema(`agents` 数组继续是唯一真相源)、`AgentManager`/`PromptBuilder`(P2 不碰运行时寻址)、`CompanyService.listAgents()`。
  259 +**明确不改**:`company.json` schema、`hire()` 的签名(v0.4 简化后不再需要 `reportsTo`)、`AgentManager` / `PromptBuilder`、P1 的 `graph-service.js`(**唯一例外**是它建的那个全量唯一索引必须 DROP,见 T1)。
414 260  
415 261 ---
416 262  
417 263 ## 八、风险 / Open Questions
418 264  
419   -1. **LLM 选型/裁剪质量没有硬性校验**:org-graph-creator 选模板、裁剪角色、判断 `reportsTo` 全靠一次 LLM 推理,没有像 `hire()` 校验规则那样的确定性把关(只保证"图结构合法",不保证"这套结构适合这家公司")。缓解:§4.9 第 5 步的 `org-plan.md` 提供了事后可读的决策记录,公司创建者发现规划不合理时,可以走现有 `fire()`/`hire()` 手动调整——**这也是为什么 §一非目标里明确不支持自动重新规划**:与其让 org-graph-creator 自己判断"要不要推倒重来",不如交给人看着 `org-plan.md` 做增量修正。
420   -2. **一次公司创建多一轮 LLM 对话的成本与延迟**:以前建公司是纯本地文件操作(毫秒级),现在多了一次真实的 Claude CLI 子进程对话(几十秒到几分钟,还有 token 成本)。`POST /create` 保持同步返回(不等 org-graph-creator 跑完,走 fire-and-forget,见 §4.10),前端体验上公司创建本身不受影响,但"什么时候能看到组织结构"变成异步——Dashboard 需要一个"组织结构规划中"的过渡态提示(T11 范围内可以顺手加,不需要单独立项)。
421   -3. **模板揉合没有边界**:§4.8 允许 org-graph-creator 同时参考多个模板文件自由组合,理论上可能拼出一个奇怪的结构(比如把三个模板的角色全招一遍)。P2 阶段不加约束(不限制招聘角色数量上限),如果上线后观察到滥招问题,可以在 org-graph-creator 的 prompt 里加"单次规划角色数不超过 N 个"这类软性约束,不需要代码层面的强制校验。
422   -4. **6 个命名冲突 id 对 org-graph-creator 选型的影响**:P1 §九 Q4 指出的 `agent-creator`/`digester`/`evolver`/`evolver-youtube-crawler`/`hunter`/`innovator` 命名空间冲突问题,现在从"HR 偶尔招到"变成"org-graph-creator 系统性地对照 `company templates` 列表做选型,遇到这批 id 的概率显著提高"。不阻塞 P2 技术交付(`hire()` 本身对这批 id 的处理逻辑不受影响),但建议在 P2 上线前,至少把这 6 个 id 从 `system/org-graph-templates/*.json` 的候选角色名单里排除(在模板文件里就不引用它们),避免规划阶段就把歧义引进来。
423   -5. **已有公司的组织结构再规划**:§4.12 明确列为非目标,但这是一个真实存在的后续需求(公司业务转型、组织结构随规模变化)。留待后续阶段设计——可能的方向是"org-graph-creator 支持增量模式:读现有 org graph + 新目标,只补差集",但这需要先观察 P2 上线后 HR 的增量招聘(§4.4 机制)能不能覆盖大部分场景,不值得在还没有真实使用数据时就设计。
424   -6. **`hire()` 里 `company.ceo` 判定逻辑本身有既有 bug**(§二已指出:`ceo` 模板没有 `config.json`,`type==='cxo'` 判定命中的其实是最后一个被雇的 cxo 类型角色)——org-graph-creator 大概率会招一个字面意义的 `ceo` 角色,这个 bug 会被更频繁地触发(`company.ceo` 显示的可能是 `cto`/`coo` 而不是真正的 `ceo`)。这是 P2 之外的既有缺陷,本文档记录下来供后续排期,不在 P2 任务列表里顺带修(避免这次改动的 diff 范围扩大到不相关的既有逻辑)。
  265 +1. **单一入口约束是否过严**(§3.3 第 2 条):要求恰好一个入度为 0 的节点。如果将来出现"两类任务从不同节点进入同一家公司"的需求,这条要放宽为"允许多入口 + 任务携带指定入口"。当前按单入口设计,因为它让"任务从哪开始"无需额外信息。
  266 +2. **`orggraph-planning` 的规划质量没有硬校验**:§3.3 只保证图**结构合法**,不保证它**适合这家公司**。缓解:`workspace/orggraph-plan.md` 留下决策记录供人工复核;发现不合理时走 §4.3 的 PATCH 增量修正。
  267 +3. **一次建公司多一轮 LLM 对话**:从毫秒级文件操作变成几十秒到几分钟的真实 Claude CLI 调用。`POST /create` 保持同步返回(fire-and-forget),但前端需要一个"workflow 规划中"的过渡态。
  268 +4. **6 个命名冲突 id**(27 号文档 §七):本文档 §4.6 已要求模板避开它们,但如果 `orggraph-planning` 自主判断需要其中某个角色,仍会撞上"该读哪份 `agent.md`"的歧义。建议在 P2 上线前把这个产品决策做掉。
  269 +5. **P2 前端依赖 P3 画布**(§4.5):若两者不同期上线,P2 需要接受纯文本占位形态。
425 270  
426 271 ---
427 272  
428   -## 九、与 P1 的关系(现状澄清)
429   -
430   -P1(`27-p1-virtual-reference.md`)**目前仍处于设计阶段,代码未落地**(`server/services/graph-service.js` 不存在,`company-service.js` 里也没有任何 `systemAgentRef`/`companyContextPath` 相关代码)。27 号文档 §六的强顺序声明(27-graph-agent-architecture.md:221)只显式要求 P3 依赖 P1,没有把 P2 排进这条链:
431   -
432   -- P2 直接基于 P0 的 `graph-db.js`/`org_graph_nodes` 表施工,不需要 P1 的虚拟引用寻址先行。
433   -- P2 新建的 `org-graph-service.js` 与 P1 设计稿里的 `graph-service.js` 是两个独立文件、不同职责。
434   -- 唯一需要协调的是**共享的 `org_graph_nodes` 表 schema**:本文档 §4.2 给这张表加了 `status` 列。如果 P1 之后落地,P1 §4.4 里 `upsertOrgGraphNode()` 的实现需要感知这个新列(默认 `status='active'` 即可)。
435   -- org-graph-creator 本身招聘自己所需要的所有角色时,走的是**当前**(P1 未落地时)的 `hire()` 实现——也就是说 org-graph-creator 招出来的每个角色,`agent.md` 仍然是物理拷贝,不是 P1 设想的虚拟引用。这一点不影响 P2 的正确性,只是意味着 P1 一旦落地,"批量自动招聘"这个新增的调用模式会让"同一个模板被拷贝 N 份"的问题(P1 §二要解决的问题)被更快地放大——这是支持"P1 应该尽早提上日程"的一个新论据,但不是 P2 必须等 P1 的理由。
  273 +## 九、与其他阶段的关系
436 274  
437   -如果团队希望先补上 P1 再做 P2,也完全可以调换顺序——本文档在设计上不依赖那个决定。
  275 +- **P0/P1**:均已实现。P2 唯一需要动 P1 代码的地方是 DROP 它建的全量唯一索引(T1)。
  276 +- **P3/P4**:P2 产出图,P3/P4 执行图。**两者共用同一套 schema 形态、执行引擎和画布组件** —— 这是 v0.4 新模型最大的收益。P2 不含任何执行逻辑。
  277 +- **端到端**:本文档在整条链路里的位置见 [27-E2E](./27-e2e-task-execution-flow.md) §三 阶段 2,具体跑一遍的样子见 [27-EXAMPLE](./27-example-dev-company-walkthrough.md) 阶段 2。
... ...
docs/todo/system-design/27-p3-p4-work-graph-console.md 0 → 100644
  1 +# 27-P3/P4. Work Graph 执行控制台 — 需求与设计文档
  2 +
  3 +> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 Phase **P3(Work Graph 最小闭环)+ P4(回退 + Loop-Validator)**,外加一块 27 号文档原始路线图里没有单独立项的新增交付物——**一个新页面**:展示公司 Org Graph、以及每个 org node 当前/历史跑过的 Work Graph,并提供启动/下一步/暂停/停止/重试的执行控制、节点日志、边的输入输出、循环回退连线、主流程/异常流程可视化、子流程(agent 对话记录 + 嵌套子 Work Graph)下钻。
  4 +> **版本**: v0.1(设计阶段,未实现)
  5 +> **日期**: 2026-08-15
  6 +> **前置**: [27-P2 Org Graph 落地](./27-p2-org-graph.md)(`org_graph_nodes` 是本设计里"每个 org node"的数据来源,本文档假定 P2 已落地)。P1(虚拟引用寻址)**已实现并提交**(commit `226489d`),与本设计无强依赖,见 §九。
  7 +>
  8 +> ⚠️ **v0.2 修正(2026-08-15)**:本文档 v0.1 有三处基于过期或不完整调研的陈述,端到端流程梳理时被推翻,**动工前必读** [27-E2E 端到端任务执行流程](./27-e2e-task-execution-flow.md) §六与§九:
  9 +> 1. §三.1 说 `runProject` 是 501 未实现 —— **它已完整实现**(`loop-controller.js:576`),含 Kahn 拓扑分波 `_groupUnitsByWave()`、L2 闸门、Integrator、BugTriager 四分支调度、预算熔断、全套 SSE 埋点。
  10 +> 2. §四.B.2 / §六 T3 把"手写分层布局算法"当成新工作量 —— **`web/src/components/devloop/UnitsDAG.vue` 已经实现了** SVG + Kahn 分波布局 + 节点状态着色 + 点击下钻(107 行),应改为扩展既有组件。同目录下 `LiveAgentStream.vue`(带 Last-Event-ID 断线重连的 SSE 消费)、`UnitDrawer.vue`(节点详情抽屉)同样可复用。
  11 +> 3. 本文档整体把"回退/循环/预算"当作全新设计 —— **`docs/prd/newfunction/_spec/loops-{overview,l1,l2,l3}.md` + `sdd-process.md` P-1/P-2/P-7 已经有一套完整的三层循环规格**(含四分类失败归因、差异化预算消耗、`contract_hash` 闸门、增量 unchanged 免重跑),本文档 §四.D 的设计应当从它抽象而来,而不是平行发明。
  12 +> **设计方法论**:按用户要求,正文用 **Model / View / Control / Process(MVCP)** 四段组织核心设计(§四),前置背景与走查、后置任务划分与验收标准按仓库既有文档惯例编排。
  13 +
  14 +---
  15 +
  16 +## 一、目标与非目标
  17 +
  18 +### 目标
  19 +
  20 +1. **一个新页面**:给定一家公司,左侧展示它的 Org Graph(复用 [27-P2 §4.7](./27-p2-org-graph.md) 的分层列表设计),点击任意一个 org node,右侧展示这个节点当前正在跑的 / 历史跑过的 Work Graph。
  21 +2. **执行控制**:启动、下一步、暂停、停止、重试五个操作,且要同时支持**手动**(每次点"下一步"才推进一个节点)和**自动**(一键授权后从头跑到尾,一个节点成功自动跳转执行下一个节点)两种模式——这是本文档动笔前用户明确的一条硬需求,直接决定了 §四.C 的状态机设计不能走 DevLoop 现有 `runUnit()` 那种"一次函数调用跑到底、外部无法中途插手"的模型(见 §三.1 走查结论)。
  22 +3. **节点运行日志**:每个 Work Graph 节点对应一次 agent 调用,要能看到这次调用的完整过程(不只是成功/失败两个字)。
  23 +4. **边的输入输出展示**:一条边连接的两个节点之间,上游节点产出了什么、下游节点实际收到了什么,要可视化出来。
  24 +5. **循环回退连线**:Loop-Validator 相关的回退边(27 号文档 §3.5/§3.4 第 4/5 点)要在图上可视化成一条区别于正常前进边的连线,并显示当前是第几次循环、硬顶还剩几次。
  25 +6. **主流程 / 异常流程处理**:节点成功后按 `success` 边前进是主流程;节点失败后按 `fail` 边路由(可能进 Loop-Validator 回退,也可能是死路进入需要人工介入的状态)是异常流程,两者在引擎状态机和 UI 上都要有明确区分。
  26 +7. **子流程下钻**:点开一个节点,能看到两类下钻内容——① 这次 agent 调用内部的对话/工具调用记录(类似现有 `AgentChat.vue` 的 sub-agent 面板);② 如果这个节点本身触发了一张更小的嵌套 Work Graph,能在同一个页面里逐层下钻查看子图的执行状态(用户已确认两种下钻语义都要)。
  27 +
  28 +### 非目标(本阶段明确不做)
  29 +
  30 +- **不做** `workgraph-planning` agent 本身的详细设计("一张 Work Graph 的节点/边最初是怎么被规划出来的")——27 号文档 §六已经把这个交付物列在 P3 里,本文档假定图已经规划好、满足 27 号文档 §3.4 第 5 点的执行前结构校验,只负责"图规划完之后,怎么跑、怎么控制、怎么看"。这不是回避,是刻意收窄范围:规划("这张图长什么样")和执行控制("这张图怎么被跑起来、怎么被人盯着")是两个复杂度都不低的问题,硬塞进一份文档会让两边都设计得不扎实。
  31 +- **不做**对 DevLoop 现有 `loop-controller.js`/`unit-registry.js` 的收编或改造——27 号文档 §五把这个列为 P6(可选),本文档的执行引擎是全新的、只服务 Work Graph,不改 DevLoop 一行代码。§三.1 会说明为什么 DevLoop 现有引擎的执行模型不能直接套用到本文档的需求上。
  32 +- **不做**多人协同编辑同一张 Work Graph(比如两个人同时点"下一步")的并发控制之外的复杂协作特性(评论、@提醒等)——并发控制本身要做(§四.A 幂等设计),但"协作"停留在"不冲突",不做"多人分工"这类产品特性。
  33 +- **不做**跨公司/跨 Org Graph 的全局视图——页面粒度是"一家公司的一个 org node 的一张 Work Graph",不做"看所有公司所有 Work Graph"的总览大屏。
  34 +- **不做**节点执行中途的"硬中断后从中间状态精确恢复"(比如 Coder 写到一半被强杀,下次重试不是从头写而是接着上次的文件状态续写)——重试语义是"整个节点重新执行一次",不是"断点续传",见 §四.C 重试小节的说明。
  35 +
  36 +---
  37 +
  38 +## 二、背景
  39 +
  40 +27 号文档 §六路线图把 P3 定义为"Work Graph 最小闭环:单向执行,先不做回退"(顺序执行器,`success → next,fail → 停止/标记 blocked`),P4 才引入回退和 Loop-Validator。本文档没有严格切两份文档去对应这两个 phase,原因是:**执行引擎的状态机一旦要支持"暂停/单步/停止/重试"这种可以被外部随时打断和摆布的控制语义,"先做单向执行、再加回退"这种分阶段方式反而会让第一版的状态机设计得过窄,P4 加回退时必须回来重新改数据模型和引擎主循环**。所以本文档一次性把 P3(正向执行)+ P4(回退 + Loop-Validator)的数据模型和引擎设计完,落地时仍然可以分两个迭代交付(任务划分 §六 里 T1-T6 是 P3 范围、T7-T9 是 P4 范围),但底层 schema 和状态机不需要中途推倒重来。
  41 +
  42 +---
  43 +
  44 +## 三、现状代码走查(决定设计边界的关键证据)
  45 +
  46 +调研了仓库里两处最相关的既有实现,用于确定新引擎"该抄什么、不该抄什么"。
  47 +
  48 +### 3.1 DevLoop `runUnit()`:一个反面参照——展示了"为什么不能照抄"
  49 +
  50 +> ⚠️ **v0.2 修正**:本节原文说"`runProject` 目前直接 501,注释写明 P3 才补全"——**这条已过期**。`runProject` 已完整实现(`loop-controller.js:576`),且它内部就是一个跨 unit 的图执行器:`_groupUnitsByWave()`(行 523)做 Kahn 拓扑分波、L2 闸门判定、Integrator、BugTriager 四分支调度、`l2_budget` 熔断。下面对 `runUnit()`「不可中断」的分析仍然成立(`runProject` 同样是一次调用跑到底),但"跨 unit 执行器不存在"这个判断是错的。另注:`runProject` 的波内**实际是串行**执行的(行 609 注释 `// 串行跑(mock 同步快,不需并行)`),分波结构在、并行没做。
  51 +
  52 +`server/src/core/devloop/loop-controller.js:345-431` 的 `runUnit()`,本质是一个**同步的 `while (attempt < retry_budget)` 循环**:一次函数调用里,Coder → Tester → 失败则 `_buildRetryContext()` 拼装上次失败摘要 → 重跑,直到测试通过或耗尽预算(默认 5 次)。`server/routes/devloop.js` 里 `POST /projects/:pid/run?only=<unitId>` 直接 `await loopController.runUnit(...)` 再吐响应——**这意味着整个 retry 循环(最多 10 次 agent 调用)全部塞在一次 HTTP 请求-响应周期里,外部在它跑完之前完全没有插手的机会**。这套模型没有任何"暂停"/"单步"/"停止"的概念,`runUnit()` 一旦被调用,只能等它跑完或者进程崩溃。
  53 +
  54 +配套的 `unit-registry.js` 状态机(`TRANSITIONS = {pending:[coding], coding:[testing,failed], testing:[passed,failed], failed:[coding,blocked], passed:[pending], blocked:[pending]}`,行 30-37)设计得很干净,**这部分值得直接借鉴**——校验非法转换、每次合法转换 emit SSE 事件(见 3.3)——但状态机是"谁在跑循环"这件事的记录者,不是驱动者,驱动者是那个不可中断的 `while` 循环本身。**结论:新引擎的状态机设计可以照抄 `unit-registry.js` 的模式(显式转换表 + 非法转换报错 + emit 事件),但主循环绝不能照抄 `runUnit()` 的"一次函数调用跑到底"模型**。
  55 +
  56 +### 3.2 Pipeline Service:正确的参照模型
  57 +
  58 +`server/services/pipeline-service.js` 的 `startRun()`(144-181 行)只做状态机初始化(第一步 `status='ready'`,其余 `'waiting'`),**不会同步执行任何 agent**。真正推进流程靠外部显式调用 `advanceStep(runId, stepIndex, {output, status})`:`status:'failed'` 直接把整个 run 标 `failed` 终止;否则把 `currentStep` 前移、下一步置 `ready`;全部完成则 `status:'completed'`。`rejectStep()` 是现成的"退回重做"语义——把某一步打回 `ready`,历史记入 `step.rejections[]`。
  59 +
  60 +**这正是本文档需要的执行模型**:状态是持久化数据,"推进一步"是一次显式、幂等、外部可随时调用的动作,不是一个占着调用栈不放的循环。手动模式下"下一步"按钮 = 用户点一次调一次 `advance`;自动模式下,引擎自己在每次 `advance` 成功返回后,检查"还要不要继续",是就立刻自己再调一次 `advance`——**手动和自动共用同一个 `advance` 原语,区别只在于"谁来触发下一次调用"**(§四.C 详细展开)。
  61 +
  62 +**两处不能照抄的坑**(写新代码时要主动避开):
  63 +
  64 +1. `CompanyDashboard.vue:1019-1024` 的 `loadPipelineDetail()` 请求的是流水线**定义**,不是某次 run 的**实例**状态,但模板里 `:class="{'step-current': selectedPipeline.currentStep === idx}"`(行 258)引用的 `currentStep` 只存在于 run 实例对象上,定义对象里根本没有——这个"当前步骤高亮"视觉效果实际上从未生效过。新页面的数据契约必须让"图的静态定义"和"某次执行的运行时状态"是两个明确分开、各自完整的对象,不能像这样把运行时字段错绑到定义对象上。
  65 +2. `retryPipelineRun()`(CompanyDashboard.vue:1034-1040)调用 `POST .../advance` 但完全没传 `stepIndex`,而路由端有 `if (stepIndex === undefined) return res.status(400)` 的校验——现状这个"重试"按钮点了必定 400。新设计的"重试"必须是一个**独立、参数自解释的端点**(§四.C),不能依赖调用方正确拼出一个复用端点的隐藏参数组合。
  66 +
  67 +### 3.3 SSE 基础设施:可以直接复用
  68 +
  69 +`server/services/devloop/sse-broker.js`:每个项目一个广播通道,`subscribe(pid, res, {replayFromOffset})` 支持基于 `Last-Event-ID` 的断线重放(从磁盘 `stream.log` 按序号过滤),`emit(pid, event, data)` 同时落盘 + 广播,25s 心跳防连接被中间层判死。这是一套成熟、可直接复用的多订阅者 SSE 基础设施,本文档按 `work_graph_id` 复用同一套实现(§四.A 的 SSE 小节)。**发现的既有缺口**:DevLoop 里 `timeline-logger.js` 的审计事件和 `sse-broker.js` 的实时推送是两条独立的轨,没有自动桥接——`loop-controller.js` 往 timeline 里追加一行,不会顺带 emit 一个 SSE 事件。本文档的引擎明确要求**状态转换、timeline 写入、SSE 广播三者在同一次原子操作里一起发生**(§四.A),不重复这个缺口。
  70 +
  71 +### 3.4 子流程展示:`AgentChat.vue` 的双轨折叠卡片模式,直接复用
  72 +
  73 +`web/src/views/AgentChat.vue` 第四列 "ACP Events" 面板已经实现了本文档 §四.B 需要的下钻 UI 模式:
  74 +
  75 +- `subAgents`(以 `callId` 为 key 的响应式 map)承载**正在跑**的子调用,每项 `{agent, status:'running'|'success'|'error', content, collapsed}`,靠流式事件(`chunk`/`start`/`done`)持续追加内容、更新终态;卡片 header 是状态 emoji + agent 名 + `LIVE` 徽标 + 折叠箭头,展开后是等宽字体滚动日志区。
  76 +- `subLogList` 是**已完成/历史**的日志卡片列表,外观一致但没有 `LIVE` 徽标。
  77 +
  78 +这套"live map(进行中)+ history list(已完成)"的双轨模型,跟本文档"节点运行日志"需要的东西完全对应,直接搬过来用,不重新发明。
  79 +
  80 +### 3.5 进程级取消能力:已经存在,但调用链需要在实现时二次确认
  81 +
  82 +`server/src/engine/process-guard.js` 的 `ProcessGuard.cancel()`(82-93 行)会对底层 Claude CLI 子进程先发 `SIGTERM`、超时后 `SIGKILL`,`server/src/engine/run-registry.js` 的 `RunRegistry` 按 `runId` 登记每次调用(`get(runId)`/`listByAgent(agentName)`),`AgentManager` 是这套机制的调用方之一。这意味着"停止一个正在执行的节点"理论上可以做到**真正打断子进程**,不是只能等它自然跑完。但本文档写作时**没有逐行确认** `RunRegistry` 的记录里是否直接暴露了对应 `ProcessGuard` 实例的引用(`run-registry.js` 目前看到的公开方法是 `add/get/markState/touchOutput/listActive/listByBackend/listByAgent/list/remove/stats/clear`,没看到 `cancel`——取消动作大概率是调用方自己持有 `ProcessGuard` 引用后直接调用,`RunRegistry` 只做登记和查询)。这一点列入 §六 T5 的实现前置核实项,不在设计阶段假设死。
  83 +
  84 +---
  85 +
  86 +## 四、设计方案(Model / View / Control / Process)
  87 +
  88 +### A. Model(数据模型)
  89 +
  90 +#### A.1 表结构(在 P0/P2 的 `graph.sqlite` 上追加,复用同一个 DB 文件)
  91 +
  92 +```sql
  93 +-- 一次具体的 Work Graph 执行实例(图的定义和运行时状态在同一行——
  94 +-- 与 §3.2 指出的"定义 vs 实例分离"原则不矛盾:Work Graph 本来就是"规划完即执行、
  95 +-- 执行完即归档"的一次性产物(27 号文档 §二术语定义),不像 Pipeline 那样"定义可以脱离某次
  96 +-- run 单独被复用",所以这里定义即实例,没有必要拆两张表)
  97 +CREATE TABLE IF NOT EXISTS work_graphs (
  98 + id TEXT PRIMARY KEY, -- wg_<nanoid>
  99 + company_id TEXT NOT NULL,
  100 + triggered_by_org_node_id INTEGER REFERENCES org_graph_nodes(id), -- 对应"每个 org node 运行的 work graph"
  101 + parent_node_id TEXT REFERENCES work_graph_nodes(id), -- 非空 = 这是另一个节点下钻出来的嵌套子图(§四.D.3)
  102 + okr_node_id TEXT, -- 形如 O-001.1.1
  103 + task_id TEXT, -- 形如 T-xxx
  104 + execution_mode TEXT NOT NULL DEFAULT 'manual' CHECK (execution_mode IN ('manual','auto')),
  105 + run_status TEXT NOT NULL DEFAULT 'planning',
  106 + -- planning | ready | running | paused | blocked | completed | failed | stopped
  107 + pause_requested INTEGER NOT NULL DEFAULT 0, -- 0/1,auto 模式下"暂停"先置位,下一个节点边界生效
  108 + stop_requested INTEGER NOT NULL DEFAULT 0, -- 0/1,同上,"停止"的软信号
  109 + created_at TEXT NOT NULL,
  110 + updated_at TEXT NOT NULL
  111 +);
  112 +
  113 +CREATE TABLE IF NOT EXISTS work_graph_nodes (
  114 + id TEXT PRIMARY KEY, -- wgn_<nanoid>
  115 + work_graph_id TEXT NOT NULL REFERENCES work_graphs(id),
  116 + system_agent_id TEXT NOT NULL REFERENCES system_agents(id),
  117 + node_kind TEXT NOT NULL DEFAULT 'task' CHECK (node_kind IN ('task','loop_validator')),
  118 + status TEXT NOT NULL DEFAULT 'pending',
  119 + -- pending | ready | running | success | fail | blocked | skipped
  120 + attempt INTEGER NOT NULL DEFAULT 0,
  121 + agent_run_id TEXT, -- 本次 attempt 对应的 RunRegistry runId,用于"停止"时定位要 cancel 哪个子进程(§三.5)
  122 + has_subgraph INTEGER NOT NULL DEFAULT 0, -- 0/1,非 0 时应存在一行 work_graphs.parent_node_id = 本节点 id
  123 + output_ref TEXT, -- 产出物相对路径,见 A.2 路径约定,不是 DB 存内容本身
  124 + started_at TEXT,
  125 + completed_at TEXT,
  126 + created_at TEXT NOT NULL,
  127 + updated_at TEXT NOT NULL
  128 +);
  129 +
  130 +CREATE TABLE IF NOT EXISTS work_graph_edges (
  131 + id TEXT PRIMARY KEY,
  132 + work_graph_id TEXT NOT NULL REFERENCES work_graphs(id),
  133 + from_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id),
  134 + to_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id),
  135 + condition TEXT NOT NULL CHECK (condition IN ('success','fail')),
  136 + edge_kind TEXT NOT NULL DEFAULT 'forward' CHECK (edge_kind IN ('forward','backward')),
  137 + created_at TEXT NOT NULL
  138 +);
  139 +
  140 +-- Loop-Validator 每次"备案-审计-修复方案传递校验"的记录(27 号文档 §3.5 三步),
  141 +-- 是"循环划线"上显示"第 N/3 次、上次判断"这类信息的数据来源,不是从 work_graph_nodes.attempt 反推
  142 +CREATE TABLE IF NOT EXISTS work_graph_loop_records (
  143 + id TEXT PRIMARY KEY,
  144 + work_graph_id TEXT NOT NULL REFERENCES work_graphs(id),
  145 + loop_validator_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id),
  146 + target_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id), -- 回退目标节点
  147 + loop_count INTEGER NOT NULL, -- 这是第几次回退(1-based)
  148 + verdict TEXT NOT NULL CHECK (verdict IN ('registered','fixed','not_fixed','force_terminated')),
  149 + remediation_note TEXT, -- "应该如何修复"的方案文本,供下一轮读取
  150 + created_at TEXT NOT NULL
  151 +);
  152 +```
  153 +
  154 +节点/边的**内容**(任务描述、日志、上下文)依旧不进 DB——沿用 27 号文档 §四已经定下的原则:`output_ref` 只存相对路径,指向 System Agent 目录下按 §3.2 已定义的物理位置:`system/agents/{agentId}/companies/{companyId}/history/{workGraphId}-{nodeId}-{attempt}.md`,本文档不新造一套路径约定,直接复用。
  155 +
  156 +#### A.2 状态机(照抄 `unit-registry.js` 的转换表模式,不照抄它的驱动方式)
  157 +
  158 +```
  159 +work_graph_nodes.status 转换表:
  160 + pending → ready (上游全部满足 / 无上游依赖,进入可执行队列)
  161 + ready → running (被 advance 拾取,开始调用 agent)
  162 + running → success | fail (agent 调用结束)
  163 + fail → ready (loop_validator 回退命中) | blocked (无回退边/回退耗尽) | ready (人工点"重试")
  164 + success → (终态,除非人工"重试"强制打回 ready)
  165 + blocked → ready (人工点"重试",唯一走出 blocked 的路)
  166 + * → skipped (run 被"停止"时,所有仍处于 pending/ready 的节点)
  167 +```
  168 +
  169 +非法转换(比如对一个 `success` 节点直接调"下一步"执行它)在服务层直接拒绝并返回明确错误,模式与 `unit-registry.js` 的 `InvalidTransitionError` 一致。
  170 +
  171 +```
  172 +work_graphs.run_status 转换表:
  173 + planning → ready (图规划完成 + 通过 27 号文档 §3.4 第 5 点的结构校验)
  174 + ready → running (点"启动")
  175 + running → paused (auto 模式下点"暂停",软信号,下一个节点边界生效)
  176 + paused → running (点"启动"或"下一步"恢复)
  177 + running → blocked (某节点进入 blocked 且不是被 stop 打断)
  178 + blocked → running (对 blocked 节点点"重试"后)
  179 + running/paused/blocked → stopped (点"停止")
  180 + running → completed (所有可达节点都到达终态,且没有 pending/ready 的悬空节点)
  181 +```
  182 +
  183 +#### A.3 幂等与并发
  184 +
  185 +`advance` 类操作(§四.C)必须幂等:同一节点被并发触发两次"下一步"(比如自动驱动器和人工同时点),第二次调用检测到节点 `status` 已经不是 `ready` 就直接返回当前状态而不是报错——这是 §一非目标里"不做复杂协作、但要做到不冲突"的具体实现方式,不需要引入分布式锁,SQLite 的行级事务 + `status` 字段的 `WHERE status='ready'` 条件更新(受影响行数=0 即视为"已经被别人拿走了")就够。
  186 +
  187 +---
  188 +
  189 +### B. View(界面设计)
  190 +
  191 +#### B.1 页面结构
  192 +
  193 +新页面(新路由,不挂在 `CompanyDashboard.vue` 已有的 tab 体系下——这个页面信息密度和交互复杂度明显超过一个 tab 该承载的量,参照仓库里 `PipelineEditor.vue` 独立成页的先例):
  194 +
  195 +```
  196 +┌─────────────────────────────────────────────────────────────────┐
  197 +│ <company name> 组织与执行 │
  198 +├───────────────┬─────────────────────────────────┬─────────────────┤
  199 +│ Org Graph │ Work Graph 画布 │ 节点详情面板 │
  200 +│ (左栏,复用 │ (选中的 org node 当前/历史 │ (点某个 work │
  201 +│ 27-P2 §4.7 │ Work Graph 的 DAG 可视化, │ graph node 后 │
  202 +│ 分层列表) │ 见 B.2) │ 展开,见 B.3) │
  203 +│ │ │ │
  204 +│ [CTO] │ 控制栏: ▶启动 ⏭下一步 ⏸暂停 │ 状态/耗时/attempt │
  205 +│ └ [HR] │ ⏹停止 🔁重试 │ [概览|输入输出| │
  206 +│ └ [PM] │ │ 运行日志|子图] │
  207 +│ │ ●pending ●running ●success │ │
  208 +│ │ ●fail ●blocked ●skipped │ │
  209 +│ │ ┄┄▶ 循环回退边 │ │
  210 +└───────────────┴─────────────────────────────────┴─────────────────┘
  211 +```
  212 +
  213 +左栏点击某个 org node:右侧主画布加载该节点关联的 Work Graph 列表(一个 org node 生命周期里可能触发过多张 Work Graph,每张对应一次 goal/task,默认显示最新一张,提供下拉切换历史)。
  214 +
  215 +#### B.2 Work Graph 画布
  216 +
  217 +- **节点**:矩形卡片,标题是 `system_agent_id`(如 `coder`),右上角小圆点按 `status` 着色(沿用仓库既有色彩语义:绿=success、蓝=running、灰=pending/ready、红=fail、橙=blocked、浅灰斜纹=skipped)。`loop_validator` 节点用菱形(区别于普通 `task` 节点的矩形),视觉上直接点名"这是一个决策节点,不是执行节点"。
  218 +- **边**:`edge_kind='forward'` 是实线箭头;`edge_kind='backward'` 是**虚线曲线箭头**,颜色区别于前进边(比如统一用琥珀色),且在曲线中点位置渲染一个小徽标,内容取自 `work_graph_loop_records` 里该 `(loop_validator_node_id, target_node_id)` 组合最新一条记录的 `loop_count`,格式 `2/3`——这直接对应用户提出的"loop 的划线"需求:不是随便画一条回退线,是带着"当前循环到第几次、硬顶是几"这个上下文的线。
  219 +- **布局**:⚠️ **v0.2 修正——不要从零写**。`web/src/components/devloop/UnitsDAG.vue`(107 行)已经实现了完全符合这里需求的方案:SVG + `<marker>` 箭头 + **Kahn 分波拓扑布局**(`waves` computed,按 `depends_on` 逐波剥离,带 safety 上限防死循环)+ 按 `status` 着色的节点矩形 + 点击 emit 下钻。P3/P4 应当扩展它(加 `loop_validator` 菱形节点、`edge_kind='backward'` 的虚线曲线、循环徽标),而不是平行实现一个。仍然不引入图形库依赖——既有实现已经证明这个规模的图手写 SVG 足够。真正的新增工作量只剩**回退边的曲线路由**(避免与其他节点重叠),这一块是新的。
  220 +- **主流程 vs 异常流程的视觉区分**:正常的 `success` 前进边和节点是画布里的"主干";任何和 `fail` 条件相关的边、`blocked` 状态的节点、`loop_validator` 节点及其回退边,整体降低主干部分的视觉权重(比如统一给异常相关元素加一层浅色背景框),让人一眼能分清"这张图正常应该怎么走"和"这次实际上因为哪里失败绕了远路"。
  221 +
  222 +#### B.3 节点详情面板(四个 tab)
  223 +
  224 +1. **概览**:`status`、`attempt`/最大重试预算、`started_at`/`completed_at`/耗时、如果是 `loop_validator` 触发的回退,展示 `work_graph_loop_records` 里这次相关的完整记录(`verdict`/`remediation_note`)。
  225 +2. **输入输出**:读 `output_ref` 指向的 markdown/JSON 文件渲染展示("边的输入输出"需求的落地点——一条边的"输出"就是它 `from_node` 的 `output_ref` 内容,"输入"就是 `to_node` 实际执行时读取的上下文,两者在**大多数正向边**上是同一份内容,只有经过 Loop-Validator 回退、`_buildRetryContext()` 类似逻辑拼接了"上次失败摘要"的场景下才会不同——这个 tab 需要能同时展示"原始产出"和"实际被下游消费时是否被加工过"两种视图,不能假设两者总是相同)。
  226 +3. **运行日志**:复用 §3.4 的 `AgentChat.vue` 双轨折叠卡片模式——节点正在 `running` 时走 live 面板(流式追加),`success`/`fail` 之后切换成历史日志视图。这一层展示的是**这次 agent 调用内部**的对话/工具调用记录(用户确认的下钻语义①)。
  227 +4. **子图**:仅当 `has_subgraph=1` 时出现这个 tab,内容是**递归复用同一个 Work Graph 画布组件**,指向 `work_graphs.parent_node_id = 本节点id` 的那张子图,画布上方有面包屑(`公司 › CTO 组织 › PM 的任务 › 子任务`)支持逐层下钻和返回(用户确认的下钻语义②)。
  228 +
  229 +#### B.4 存量/降级展示
  230 +
  231 +一个 org node 从未触发过 Work Graph(P3 上线初期,或者这个角色本身不承担会触发 Work Graph 的任务):右侧主画布显示空态提示,不报错、不留白页面。
  232 +
  233 +---
  234 +
  235 +### C. Control(控制操作)
  236 +
  237 +| 操作 | 可用条件(`run_status` / `execution_mode`) | 引擎侧动作 | 对"正在跑的节点"的影响 |
  238 +|---|---|---|---|
  239 +| **启动** | `run_status ∈ {ready, paused, blocked}` | `ready→running`;若 `execution_mode='auto'` 立即调用一次内部 `advance()` 并让其在每次成功后自我调度下一次(见下);若 `manual`,只是把 run 标成 running,停在原地等"下一步" | 无(这个操作只在没有节点在跑的时候可点) |
  240 +| **下一步** | `run_status='running'` 且 `execution_mode='manual'`(或 `auto` 但已 `paused`——暂停后允许人工手动单步推进) | 拾取一个当前 `status='ready'` 的节点(如果有多个并行 ready 的节点,一次"下一步"只推进**一个**,需要在 UI 上明确标出"接下来要跑哪个",避免用户以为点一下会推进所有分支),执行完(`success`/`fail`)后评估出边,把满足条件的下游节点置 `ready`,**不继续往下跑**,停在这里等下一次点击 | 会真正发起一次 agent 调用,不可中途取消(§三.5 的取消能力用在"停止"上,不是给"下一步"配的) |
  241 +| **暂停** | `run_status='running'` 且 `execution_mode='auto'` | 置 `pause_requested=1`。**不打断当前正在 `running` 的节点**(agent 调用一旦发起就让它跑完,理由见下方说明),当前节点完成、准备自动调度下一个节点之前检查这个标志位,若为真则停止自我调度,`run_status→paused` | 无——这是"软暂停",生效点是下一个节点的调度前,不是立即打断 |
  242 +| **停止** | `run_status ∈ {running, paused, blocked}` | 置 `stop_requested=1`;所有仍处于 `pending`/`ready` 的节点批量置 `skipped`;若此刻有节点正 `running`,尝试通过 §三.5 的机制硬取消对应子进程(**取消能力需要 §六 T5 在实现前二次确认调用链,设计上按"能取消"设计,实现时如果确认做不到就退化成"等它跑完、记录结果、但不再调度后续节点"**);`run_status→stopped` | 尝试硬取消,退化路径是"软停止" |
  243 +| **重试** | 节点 `status ∈ {fail, blocked}` | 把该节点 `status` 打回 `ready`,`attempt+1`;**默认只重置这一个节点,不级联重置下游**——因为下游节点在这次重试产生新结果之前根本还没执行到(`fail`/`blocked` 节点的下游按定义还是 `pending`),除非是"节点曾经 success 过、后来因为上游数据变化想强制重跑"这种场景才需要级联,这种场景本版本不支持(见 §八.3) | 无(`fail`/`blocked` 节点本身已经不在 `running`) |
  244 +
  245 +**手动/自动的统一原语**:无论手动点"下一步"还是自动模式下引擎自己驱动,落到服务层都是同一个 `advanceOne(workGraphId)` 函数调用——区别只在于**谁在调用它、调用节奏由谁决定**:手动模式下调用方是收到用户点击的路由 handler,每次一下;自动模式下调用方是引擎自己的一个"节点完成回调",只要 `pause_requested`/`stop_requested` 都是假,就在上一次 `advanceOne` 的 promise resolve 之后立刻发起下一次。这个设计直接对应 §三.2 里"手动和自动共用同一个 advance 原语"的结论,也是本设计避免"两套并行的执行逻辑互相踩"的关键——**只有一套推进逻辑,自动模式只是一个不停调用它的循环,不是另一套实现**。
  246 +
  247 +---
  248 +
  249 +### D. Process(主流程 / 异常流程 / 子流程处理)
  250 +
  251 +#### D.1 主流程
  252 +
  253 +```
  254 +节点进入 ready
  255 + → advanceOne 拾取 → running → 调 AgentManager 执行对应 system_agent
  256 + → 调用结束,产出写入 output_ref
  257 + → success:遍历该节点的出边中 condition='success' 的边,目标节点若所有前置条件(可能有多条入边,
  258 + 需要"任意一条满足即可"还是"全部满足才能进入 ready"——采用"全部满足"语义,与 27 号文档 §3.4 的
  259 + 图规划约定一致,多入边节点代表"汇合点")满足,置 ready
  260 + → 所有节点都到达终态(success/fail 被消化成 blocked 或被路由掉/skipped)且没有 ready/pending 悬空
  261 + → work_graphs.run_status = completed
  262 +```
  263 +
  264 +#### D.2 异常流程
  265 +
  266 +```
  267 +节点 running → fail
  268 + → 遍历出边中 condition='fail' 的边
  269 + ├─ 存在一条 → to_node:
  270 + │ ├─ to_node.node_kind='loop_validator'(27 号文档 §3.4 第 4 点的结构要求,非法图在规划阶段已被
  271 + │ │ §3.4 第 5 点的执行前校验拦截,本引擎不需要再兜底校验合法性,只需要按图执行)
  272 + │ │ → 进入 Loop-Validator 流程(D.2.1)
  273 + │ └─ to_node 是普通 task 节点(无回退语义的显式失败分支,比如"失败就转人工审核节点")
  274 + │ → 正常按主流程把 to_node 置 ready
  275 + └─ 不存在 fail 边(死路)→ 该节点 status='blocked',run_status 视其余节点情况可能同步变
  276 + 'blocked'(如果没有其他节点还在 ready/running)——这是需要人工点"重试"或人工介入的终止态,
  277 + 不是系统自动判定"整个 run 失败"(区别于"停止"是人工主动终止,"blocked" 是系统探测到走不下去)
  278 +```
  279 +
  280 +**D.2.1 Loop-Validator 流程**(落实 27 号文档 §3.5 的三步 + 硬顶):
  281 +
  282 +1. **备案**:进入 `loop_validator` 节点时,插入一条 `work_graph_loop_records`(`verdict='registered'`),`loop_count` = 该 `(validator, target)` 组合已有记录数 + 1。
  283 +2. **硬顶检查**:`loop_count > 3` → 不再调用 LLM 判断,直接 `verdict='force_terminated'`,`loop_validator` 节点本身 `status='fail'`(且它没有 `fail` 出边或出边同样是死路),整条链路收敛到 `blocked`,等待人工介入。这一条判断是纯代码逻辑(比较整数),不依赖 agent 调用,保证"熔断"不会被 LLM 的自由发挥绕过。
  284 +3. **审计 + 修复方案传递校验**(`loop_count ≤ 3` 时才发生):调用 `loop_validator` 对应的 system agent(本身是一次 agent 调用,`node_kind='loop_validator'` 不改变"它也要 running→success/fail"这条状态机路径),产出 `verdict ∈ {fixed, not_fixed}` + `remediation_note`,写回同一条记录。`fixed` → 正常按 `success` 边继续(回到主流程);`not_fixed` → 按该节点的 `fail` 出边指向的**回退目标节点**(`target_node_id`)重新置 `ready`,`remediation_note` 作为这次重跑的上下文补充写入目标节点下一次 attempt 的 `output_ref`(沿用 D.1 里"产出物路径"约定,附加一段"上一轮 Loop-Validator 给出的修复建议")。
  285 +
  286 +#### D.3 子流程(下钻)
  287 +
  288 +两类下钻,数据结构上分别对应:
  289 +
  290 +- **Agent 对话记录**(B.3 第 3 个 tab):不建新表,直接关联现有会话持久化(`server/conversations/`,CLAUDE.md 已列出的既有目录)——`work_graph_nodes` 不需要冗余存对话内容,`agent_run_id`/节点执行时机足以反查到对应的会话记录,实现时确认现有会话持久化的查询接口能否直接按"这次特定 attempt 的调用"过滤(如果不能精确过滤到单次 attempt,退化到按时间窗口近似匹配,列入 §六 T4 的实现细节)。
  291 +- **嵌套子 Work Graph**(B.3 第 4 个 tab):`work_graph_nodes.has_subgraph=1` 的节点,触发时机是这个节点对应的 agent 在执行过程中自己判断"这个任务需要再规划一张子图"(比如一个复杂 Coder 任务内部拆成多个子步骤)——**子图的创建和执行,复用本文档设计的同一套 `work_graphs`/`advanceOne`/控制操作**,只是 `parent_node_id` 非空、`triggered_by_org_node_id` 可以为空(子图不是直接被某个 org node 触发的,是被上一层 work graph node 触发的)。子图有自己独立的 `execution_mode`/`run_status`,默认继承父图当前的 `execution_mode`(父图 auto,子图默认也 auto;用户可以在下钻进去之后单独改子图的模式,两者互不影响——即"暂停父图"不会自动暂停正在跑的子图,这是刻意的简化,见 §八.4)。
  292 +
  293 +---
  294 +
  295 +## 五、任务划分(Task Breakdown)
  296 +
  297 +| # | 任务 | 涉及文件/模块 | 依赖 | 对应 Phase |
  298 +|---|---|---|---|---|
  299 +| **T1** | schema:`work_graphs`/`work_graph_nodes`/`work_graph_edges`/`work_graph_loop_records` 四张表,状态转换表实现(照抄 `unit-registry.js` 模式) | `server/services/graph-db.js`(追加)或新文件 `server/services/work-graph-db.js` | 27-P2 已落地(`org_graph_nodes` 存在) | P3 |
  300 +| **T2** | `work-graph-engine.js`:`advanceOne()` 核心原语(拾取 ready 节点→执行→按边推进)+ 幂等更新(A.3) | `server/services/work-graph-engine.js`(新增) | T1 | P3 |
  301 +| **T3** | Work Graph 画布前端组件:**扩展既有 `UnitsDAG.vue`**(Kahn 分波布局 + SVG 渲染已具备),新增 `loop_validator` 菱形节点、回退边虚线曲线路由、循环徽标 | `web/src/views/WorkGraphConsole.vue`(新增)、`web/src/components/devloop/UnitsDAG.vue`(抽取复用或拷贝改造) | T5(先有数据能画) | P3 |
  302 +| **T4** | 节点详情面板四个 tab(概览/输入输出/运行日志/子图),运行日志 tab 复用 `AgentChat.vue` 的双轨折叠卡片模式 | 同上 + 会话记录查询接口确认 | T2, T6 | P3 |
  303 +| **T5** | SSE:按 `work_graph_id` 复用 `sse-broker.js` 模式,状态转换 + timeline 写入 + SSE 广播三者原子化 | `server/services/work-graph-sse.js`(新增,参照 `devloop/sse-broker.js`) | T2 | P3 |
  304 +| **T6** | REST API:`POST /work-graphs/:id/start`、`/next`、`/pause`、`/stop`、`/retry`(node 级)、`GET /work-graphs/:id`(含 nodes/edges 全量状态)、`GET /org-graph-nodes/:id/work-graphs`(某 org node 的历史 Work Graph 列表) | `server/routes/work-graph.js`(新增) | T2 | P3 |
  305 +| **T7** | Loop-Validator 流程:D.2.1 三步 + 硬顶,`work_graph_loop_records` 读写 | `server/services/work-graph-engine.js` 扩展 | T2 | P4 |
  306 +| **T8** | 回退边可视化:循环徽标(`N/3`)、异常流程视觉降权(B.2 最后一条) | T3 组件扩展 | T7 | P4 |
  307 +| **T9** | §三.5 取消能力的实现前核实 + 落地:确认 `RunRegistry`/`ProcessGuard` 的调用链,"停止"操作接入硬取消(或确认做不到、落地降级方案并更新本文档 §四.C 的表述) | `server/src/engine/`(只读研究 + 必要时补一个查询接口,不改现有取消逻辑本身) | 无(可以提前做,独立于 T1-T8) | P3(阻塞"停止"的完整语义,但不阻塞其余功能先上线) |
  308 +| **T10** | 测试:`work-graph-engine.test.js`(状态机非法转换、幂等 advance、Loop-Validator 硬顶);一条端到端脚本(建图→启动→若干次 advance→触发一次回退→硬顶终止→查最终状态) | `server/tests/unit/work-graph-engine.test.js`(新增) | T2, T7 | P3+P4 |
  309 +
  310 +**顺序建议**:T1→T2 是地基。T9(取消能力核实)可以最早独立开工,不阻塞其他任务,但要在 T6 的"停止"端点定稿前给出结论。T5/T6 依赖 T2,可并行。T3 依赖有真实数据可画,建议先用 T1 的 schema 手写几行测试数据跑通画布,再接 T6 的真实端点。T7/T8(P4)在 T2/T6 稳定后开始,不阻塞 P3 先上线一个"只有正向执行、没有回退"的初版(这也是为什么 §二解释了"一次设计、分两次交付")。
  311 +
  312 +---
  313 +
  314 +## 六、验收标准
  315 +
  316 +1. 对一张只有正向边、3-4 个节点的 Work Graph:手动模式下连续点"下一步"能逐节点推进到 `completed`,每次点击只推进一个节点(不会因为下游 ready 了就自动继续)。
  317 +2. 同一张图切到自动模式点"启动":不再需要任何人工点击,自动跑到 `completed`。
  318 +3. 自动模式运行中点"暂停":当前正在 `running` 的节点跑完后,`run_status` 变成 `paused`,不再自动调度下一个节点;此时点"下一步"能手动继续单步推进;点"启动"能恢复自动模式。
  319 +4. 构造一个"总是失败"的节点、其 `fail` 边指向一个 `loop_validator` 节点、`loop_validator` 的 `fail` 边指回该节点自身的上游:验证第 1-3 次回退能正常触发(`work_graph_loop_records` 各有一行 `verdict∈{fixed,not_fixed}`),第 4 次触发时不再调用 agent、直接 `force_terminated`,图收敛到 `blocked`。
  320 +5. 对一个 `blocked` 节点点"重试":节点回到 `ready`,后续能继续正常推进(不需要重建整张图)。
  321 +6. 点"停止":所有 `pending`/`ready` 节点变 `skipped`,`run_status` 变 `stopped`;如果 T9 确认取消能力可用,正在 `running` 的节点被中断且状态被正确记录为某种可区分于正常 `fail` 的终止态(本设计的 `skipped`/`stopped` 组合已经覆盖,具体是否需要给"被强制取消"单独一个 status 值,留给 T9 落地时决定)。
  322 +7. Work Graph 画布:回退边渲染为虚线曲线且显示 `N/3` 循环徽标,随着 D.2.1 流程推进徽标数字正确递增。
  323 +8. 点开一个节点的"运行日志" tab:`running` 状态下看到实时追加的内容(对应 `AgentChat.vue` 的 live 面板行为),`success`/`fail` 后切换成历史视图,内容不丢失。
  324 +9. 对一个 `has_subgraph=1` 的节点:出现"子图" tab,点进去是完整可交互(能点"下一步"等)的嵌套 Work Graph 画布,面包屑能返回上一层。
  325 +10. Org Graph 左栏点击一个从未触发过 Work Graph 的 org node:右侧显示空态提示,不报错。
  326 +
  327 +---
  328 +
  329 +## 七、影响面 / Blast Radius
  330 +
  331 +| 文件 | 改动类型 |
  332 +|---|---|
  333 +| `server/services/graph-db.js`(或新 `work-graph-db.js`) | 新增四张表 |
  334 +| `server/services/work-graph-engine.js` | 新增:状态机 + `advanceOne` + Loop-Validator 流程 |
  335 +| `server/services/work-graph-sse.js` | 新增:参照 `sse-broker.js` |
  336 +| `server/routes/work-graph.js` | 新增:控制类 + 只读查询类端点 |
  337 +| `web/src/views/WorkGraphConsole.vue` | 新增:主页面 |
  338 +| `web/src/components/WorkGraphCanvas.vue`(视实现拆分情况) | 新增:可复用的画布组件(父图/子图共用,见 §四.D.3) |
  339 +| `server/src/engine/`(`run-registry.js` 相关) | 只读研究为主,T9 视核实结果决定是否需要补一个小的查询接口,不改现有取消逻辑 |
  340 +
  341 +**明确不改**:DevLoop 全部现有代码、`27-P2` 已落地的 `org_graph_nodes`/`edges`(本设计只读取,通过 `triggered_by_org_node_id` 引用,不写入)、`CompanyDashboard.vue`(新页面独立存在,不改造现有 tab,只是从 Dashboard 某处加一个跳转入口)。
  342 +
  343 +---
  344 +
  345 +## 八、风险 / Open Questions
  346 +
  347 +1. **`RunRegistry`/`ProcessGuard` 的取消调用链没有 100% 核实**(§三.5)——"停止"操作里"硬取消正在跑的节点"这条能力,设计上按能做出发,但 T9 是这个功能能不能达到设计预期的真正决定因素。如果核实后发现做不到干净取消,退化方案("停止"只影响未开始的节点,正在跑的等它自然结束)已经在 §四.C 写明,不会导致整个设计推倒重来,但用户体验会打折扣(点了停止还要再等一会)。
  348 +2. **Work Graph 画布的分层布局 + 回退边曲线路由是一块不小的手写前端工程量**,没有引入图形库(符合仓库一贯风格),但意味着这部分工时不能按"随便拿个组件库拼一下"估计。如果实现过程中发现手写布局的边界情况(比如多条回退边交叉)处理成本远超预期,值得回来重新评估是否值得为此引入一个轻量布局库(如 dagre 的纯布局计算部分,不需要它的渲染层)——本文档不预先下结论,留给实现阶段视实际复杂度决定。
  349 +3. ~~**"重试"默认不级联重置下游**~~ ✅ **v0.2 已由既有规格关闭**:`_spec/loops-l3.md` 的增量算法已经给出标准答案——按 `contract_hash` 把节点分成 added / changed / removed / **unchanged** 四类,`unchanged`(id 在且 hash 不变)**保持 passed、无须重跑**,只有 hash 变了的才回退到 pending。也就是说"要不要级联"不该由重试动作决定,而由**产出契约是否真的变了**决定。P3/P4 实现时直接采用这套判据。下面保留原始论述作为背景:**重试默认不级联重置下游**(§四.C 表格备注)——如果实际使用中发现"节点 A 失败重试后产出变了,但 A 的下游 B 早就用旧产出跑完了"这种场景大量出现(比如 B 是在 A 曾经短暂 success 又被人工强制重跑的情况下执行的),现在的设计没有处理"重跑一个已经有下游消费过它产出的节点"这条路径。P2 阶段(本文档当前范围)刻意不支持这个场景(`fail`/`blocked` 节点的下游按定义还没跑过,不存在这个问题;真正的场景是"对一个 `success` 节点也允许点重试",当前设计没开放这个入口)。如果后续需要支持,涉及"下游要不要级联失效"的产品决策,留待有真实需求时再设计。
  350 +4. **子图默认不继承父图的暂停/停止状态**(§四.D.3)——"暂停父图"不会暂停正在跑的子图,是刻意简化,避免一次"暂停"要级联遍历整棵下钻树。如果实际使用中这个不一致体验造成困惑(用户以为暂停了父图,子图还在偷偷跑),需要重新评估是否要做级联传播,工作量不小(需要引擎知道"我有没有子图、子图现在什么状态",目前的 `parent_node_id` 只是单向引用,没有双向感知机制)。
  351 +5. **本文档假定"图已经规划好"**,不覆盖 `workgraph-planning` agent 本身怎么工作(§一非目标已声明)——这意味着在 planning agent 真正设计出来之前,T1-T10 的验收(§六)只能用手写测试数据构造图,不能端到端验证"从一句自然语言目标到一张可执行的图"这条链路。这是刻意的范围切分,不是遗漏,但要求 27 号文档后续单独出一份"`workgraph-planning` agent"设计文档,两者共用本文档定义的 schema(§四.A)。
  352 +
  353 +---
  354 +
  355 +## 九、与其他阶段的关系
  356 +
  357 +- **与 P2(Org Graph)**:强依赖,`triggered_by_org_node_id` 直接引用 `org_graph_nodes(id)`,本文档假定 [27-P2](./27-p2-org-graph.md) 已经落地——如果 P2 还没上线,`GET /org-graph-nodes/:id/work-graphs` 这类按 org node 反查的端点没有数据可读,但 Work Graph 引擎本身(T1-T2/T7 的状态机和 Loop-Validator 逻辑)不依赖 P2 的任何具体实现细节,理论上可以先把引擎和画布用一个假的 `triggered_by_org_node_id` 跑通,P2 上线后再接上真实数据——两者顺序不是互相阻塞的硬依赖,只是"最终要接起来"。
  358 +- **与 P1(虚拟引用寻址)**:无直接依赖,且 P1 **已实现并提交**(commit `226489d`)。Work Graph 节点执行时调用 `AgentManager` 的方式不受影响(P1 改的是 `prompt` 字段从哪来,不改调用接口)。
  359 +- **与端到端流程**:本文档设计的是"一张 Work Graph 怎么被执行和控制",但一个真实任务会有**三层图**(任务级 → org node 级 → 并行子图级)。第三层之上的两层、任务入口、验收收敛条件,见 [27-E2E 端到端任务执行流程](./27-e2e-task-execution-flow.md)。该文档同时指出本文档需要补的四项:`node_kind='org_node'`(让 work graph 节点能指向 org node)、fan-out 并行 + 同 agent 多实例隔离、跨层级共享的任务级总预算、三层暂停/停止的级联语义。
  360 +- **与 27 号文档 P3/P4 原始路线图**:本文档实质上把这两个 phase 的交付物做了细化和合并(理由见 §二),且额外交付了 27 号文档路线图里没有单独列出的"执行控制台页面"。建议 27 号文档 §六路线图表的 P3/P4 两行补充链接指向本文档,与 [27-P2](./27-p2-org-graph.md) 现有的链接方式一致。
... ...