Commit b43106fc8d264219e415383b358913db894437bc
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>
Showing
6 changed files
with
1475 additions
and
355 deletions
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) 现有的链接方式一致。 | ... | ... |