# 27-P2. Org Graph — 公司级 Workflow 的生成与落地 > **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 Phase P2 > **版本**: v0.4(**按执行流边模型重写**;v0.1-v0.3 把 Org Graph 建模为静态汇报结构,该模型已作废,见 §一) > **日期**: 2026-08-15 > **前置**: P0 地基已完工(commit `8421f55`)、**P1 虚拟引用寻址已实现**(commit `226489d`,`server/services/graph-service.js`) > **命名**: 全文遵循 [27-NAMING 命名规范](./27-naming-conventions.md)。v0.3 的 `org-graph-creator` 在本版本统一改名为 **`orggraph-planning`**。 > **配套**: [27-E2E 端到端流程](./27-e2e-task-execution-flow.md)(本文档在整条链路里的位置)、[27-EXAMPLE 参考场景](./27-example-dev-company-walkthrough.md)(跑一遍具体的数) --- ## 一、模型变更说明(v0.4 为什么重写) v0.1-v0.3 把 Org Graph 建模为**静态汇报结构**:节点是角色引用,边是 `reports_to` / `collaborates_with`,"公司生命周期内相对稳定",不参与执行。这个建模已被推翻。 **新模型**: > **Org Graph 是 company 级别生成的 workflow;Work Graph 是 org graph node 级别生成的 workflow。** 两者是**同一种东西在不同层级上的递归**,不是两种不同的结构: | | Org Graph | Work Graph | |---|---|---| | 生成者 | `orggraph-planning` agent | `workgraph-planning` agent | | 生成时机 | 建公司时,一次 | 每个 org node 接到任务时 | | 节点指向 | org node agent(`code-planner` 等) | system agent 引用 | | 边的语义 | **执行流**:`condition ∈ {success, fail}` | **执行流**:同左 | | 执行引擎 | [27-P3/P4](./27-p3-p4-work-graph-console.md) 的同一套 | 同左 | **由此带来的四处结构性改动**(这也是本次重写的主要内容): 1. `org_graph_edges` 从 `relation_type` 改为 `condition` + `edge_kind`,与 `work_graph_edges` 同构(§三.2)。 2. 汇报关系降级为节点上的可空属性,不再是图的主干语义(§三.2)。 3. `hire()` **不再需要 `reportsTo` 参数** —— 图由 `orggraph-planning` 整体规划、整体写入,不是靠招聘时逐个累积出来的(§四.2,这反而让 P2 对 `company-service.js` 的侵入变小了)。 4. 前端不再自己写"按汇报关系分层渲染",直接复用 Work Graph 的画布组件(§四.5)。 --- ## 二、目标与非目标 ### 目标 1. **`orggraph-planning` agent**:建公司后自动运行,读公司目标/描述 + org graph 模板库 + 可招聘角色清单,产出一张公司级 workflow,并执行招聘。 2. **图的持久化**:`org_graph_nodes` / `org_graph_edges` 落地为执行流图,schema 与 `work_graph_edges` 同构。 3. **图的读写 API**:整体写入(规划产物落库)、增量修改(后续 HR 调整)、只读查询(供前端与执行引擎消费)。 4. **前端可视化**:复用 Work Graph 画布组件渲染 org graph。 ### 非目标 - **不做**图的执行 —— 执行引擎、启动/暂停/单步/重试、SSE 全部属于 [27-P3/P4](./27-p3-p4-work-graph-console.md)。P2 只负责"把图建出来、存好、能读能画"。 - **不做** `company.json.agents[]` 的废弃 —— 它仍是"这家公司雇了谁"的真相源;org graph 回答的是"任务怎么流过这些人",两者并存。 - **不做**存量公司的补图 —— 见 §六。 - **不做** `workgraph-planning` agent 本身的设计 —— 属于 P3。 --- ## 三、数据模型 ### 3.1 现状(P0/P1 已落地,本次要改的基础) `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`。 ### 3.2 目标 schema ```sql -- 节点:追加 status(软删除,供 fire 用) ALTER TABLE org_graph_nodes ADD COLUMN status TEXT NOT NULL DEFAULT 'active'; -- 'active' | 'removed' -- 可选:汇报关系降级为节点属性(不再是图的边) ALTER TABLE org_graph_nodes ADD COLUMN reports_to_node_id INTEGER REFERENCES org_graph_nodes(id); -- ⚠️ P1 已落地一个同表的全量唯一索引,必须先 DROP,否则「fire 后重新 hire 同角色」写不进去 DROP INDEX IF EXISTS idx_org_graph_nodes_company_role; CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role_active ON org_graph_nodes(company_id, role_name) WHERE status = 'active'; -- 边:从 relation_type 改为执行流语义,与 work_graph_edges 同构 -- (P0 建的 org_graph_edges 从未写入过数据,可以直接重建,无迁移负担) DROP TABLE IF EXISTS org_graph_edges; CREATE TABLE org_graph_edges ( id INTEGER PRIMARY KEY AUTOINCREMENT, company_id TEXT NOT NULL, from_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id), to_node_id INTEGER NOT NULL REFERENCES org_graph_nodes(id), condition TEXT NOT NULL CHECK (condition IN ('success','fail')), edge_kind TEXT NOT NULL DEFAULT 'forward' CHECK (edge_kind IN ('forward','backward')), status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_edges_dedupe_active ON org_graph_edges(company_id, from_node_id, to_node_id, condition) WHERE status = 'active'; ``` > **`org_graph_edges` 可以直接 DROP 重建**,因为它从建表到现在一行数据都没有——这是本次模型变更成本最低的一点。 ### 3.3 图的合法性约束(写入前校验) 与 work graph 的执行前校验同一套规则([27-P3/P4](./27-p3-p4-work-graph-console.md) §四.A): 1. **无环**(仅针对 `edge_kind='forward'`)—— 三色 DFS,复用 DevLoop `doc-store.js:167` 的 `hasCycle()` 实现。 2. **单一入口**(✅ 已定案,见 [27-REQ](./27-requirement.md) R-13)—— 必须恰好有一个入度为 0 的 forward 节点,任务从这里进入。多入口会让"任务从哪开始"无法确定,且需要任务入口多带一个参数。 3. **可达性** —— 每个节点都必须从入口可达,孤立节点直接拒绝。 4. **回退边必须显式标注** —— 任何指向"拓扑序更早节点"的边,`edge_kind` 必须是 `backward`;引擎独立复核,不信任标注(27 号文档 §3.4 第 5 点的同款兜底)。 --- ## 四、设计方案 ### 4.1 `orggraph-planning` agent 新增 system agent 模板 `system/templates/orggraph-planning/`(`config.json.type` 取 `meta-bootstrap`,与业务角色区分)。工作流: 1. **读输入**:公司 `id` / `name` / **`goal`(company-goal)** / `description`(由 §4.4 的首轮消息带入)。 `company-goal` 是「这家公司要达成什么」,`description` 是「这家公司是干什么的」——前者是规划流水线的主依据,后者是背景。见 [27-NAMING](./27-naming-conventions.md) §四。 2. **读候选骨架**:`ls system/org-graph-templates/`,逐个读取,理解各自的 `whenToUse`。 3. **读可招角色**:`node bin/lemon.js company templates`(复用现成 CLI,`listTemplates()` 返回 id/name/type/description)。 4. **规划**:综合三者,产出一张 workflow —— 节点列表 + 执行流边。允许裁剪、替换、组合多个模板,也允许判断需要一个模板库里没有的新角色。 5. **落盘规划记录**:`workspace/orggraph-plan.md`(图 + 为什么这么设计的理由),供事后审计。 6. **提交图**:调用 §4.3 的整体写入 API,图通过 §3.3 校验后落库。 7. **执行招聘**:按拓扑序对每个节点调 `hire()`。 - **模板已存在** → 走 P1 虚拟引用寻址,不拷贝 `agent.md`。 - **模板不存在** → **从 `system/templates/base/` 元 agent 继承出骨架**(自动带上 base 的 `RESEARCH → PLAN → EXECUTE → VERIFY` 四段式生命周期),再由 `agent-creator` 填充该角色特有的定义。这是 [27-E2E](./27-e2e-task-execution-flow.md) §八bis「所有 node agent 由元 agent 继承生成」在 P2 的落地点——**不是**直接走现有的 `needs_training` 全量培养分支。 - **新建 agent 的 `scope`**:一律默认 `scope='company'`、`owner_company_id` 为本公司(27 号文档 §3.2)。升级为 `public` 走既有的 benchmark ≥95 门槛,不在 P2 范围内。 - **训练是可选的后置步骤**:建成后可手动触发 `innovator` 培养,也可按公司需求配置为自动触发。P2 只保证「角色能被建出来且带四段式骨架」,不强制等训练完成。 8. **收尾**:关闭自己的心跳转入休眠。后续增量调整归 HR。 `orggraph-planning` 自己**不出现在它规划的 org graph 里** —— 它是基础设施角色,类比 `agent-creator` / `innovator`。 ### 4.2 `hire()` 保持不变(v0.4 简化) v0.2/v0.3 设计的 `hire(companyId, roleName, {reportsTo})` **不再需要**。原因:图现在是被整体规划、整体写入的产物,不是靠招聘动作逐次累积出来的。招聘只负责"把这个角色的目录建出来",与图的结构无关。 因此 P2 对 `company-service.js` 的改动只剩一处:`hire()` 成功后写一行 `org_graph_nodes`(幂等,try/catch 不阻断主流程)—— 这一行 P1 的 `graph-service.upsertOrgGraphNode()` 已经实现,**P2 直接复用,不需要新代码**。 ### 4.3 API | 端点 | 用途 | |---|---| | `POST /api/company/:companyId/org-graph` | **整体写入**:接收 `{nodes:[], edges:[]}`,过 §3.3 四项校验,事务性落库。校验失败返回 400 + 具体哪条规则不满足(供 `orggraph-planning` 读错误后自行修正重试) | | `PATCH /api/company/:companyId/org-graph` | **增量修改**:加/删节点、加/删边(后续 HR 调整用)。同样过校验——改完之后整张图仍须合法 | | `GET /api/company/:companyId/org-graph` | **只读**:返回 `{nodes, edges}`,只含 `status='active'` 的行 | | `POST /api/company/:companyId/org-graph/replan` | **重跑规划**:对已有公司手动再触发一次 `orggraph-planning`(§六 存量公司补图的入口)。异步,立即返回;agent 跑完后通过上面的 POST 写入新图 | `GET` 返回形状: ```jsonc { "nodes": [ { "id": 12, "roleName": "code-planner", "systemAgentId": "code-planner", "status": "active" }, { "id": 13, "roleName": "code-coder", "systemAgentId": "code-coder", "status": "active" }, { "id": 14, "roleName": "code-validator", "systemAgentId": "code-validator", "status": "active" } ], "edges": [ { "from": 12, "to": 13, "condition": "success", "edgeKind": "forward" }, { "from": 13, "to": 14, "condition": "success", "edgeKind": "forward" }, { "from": 14, "to": 13, "condition": "fail", "edgeKind": "backward" } ] } ``` 不在这一层拼装 `name` / `type` / 心跳状态 —— 那些的权威来源是 `companyService.listAgents()`,前端按 `roleName` join 即可,避免两个端点的字段定义漂移。 ### 4.4 公司创建流程接入 `server/routes/company.js:22-38` 的 `POST /create`,在 `companyService.create()` 之后追加: ```js const bootstrap = companyService.hire(id, 'orggraph-planning', {}); if (bootstrap.status === 'hired_from_template') { fetch(`http://127.0.0.1:${PORT}/api/agent/orggraph-planning/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: `公司 ${id}(${name})已创建。\n公司目标(company-goal):${goal || '(未填写)'}\n公司描述:${description || '(未填写)'}\n请按 orggraph-planning 工作流规划并建立这家公司的 workflow。`, conversationId: `orggraph-bootstrap-${id}-${Date.now().toString(36)}`, }), }).catch(e => console.error('[Create] orggraph-planning 触发失败:', e.message)); } ``` 放在路由层而非 `CompanyService.create()` 内部 —— 与 `/hire` 的训练触发逻辑(`company.js:917-940`)保持同一约定:依赖 HTTP 层上下文的代码不下沉到 service。 **降级**:这一步失败不影响公司创建本身,退化为一家没有 workflow 的空壳公司(等同 P2 上线前的现状)。 ### 4.5 前端 **不自己写渲染逻辑** —— org graph 与 work graph 同构,直接复用 [27-P3/P4](./27-p3-p4-work-graph-console.md) T3 的画布组件(其本身又是扩展自既有的 `web/src/components/devloop/UnitsDAG.vue`,已实现 SVG + Kahn 分波布局 + 状态着色 + 点击下钻)。 这也意味着 **P2 的前端工作依赖 P3 的画布组件先做出来**。若想让 P2 独立上线,可以先在 Company Dashboard 用一个纯文本的拓扑序列表占位(`code-planner → code-coder → code-validator`),等 P3 画布就绪再替换。 ### 4.6 `system/org-graph-templates/` 每个文件是一种公司类型的 workflow 骨架: ```jsonc // system/org-graph-templates/dev.json { "id": "dev", "label": "开发型公司", "whenToUse": "目标包含实现某个产品/功能、需要跑代码开发-测试循环时优先参考", "nodes": ["code-planner", "code-coder", "code-validator"], "edges": [ { "from": "code-planner", "to": "code-coder", "condition": "success" }, { "from": "code-coder", "to": "code-validator", "condition": "success" }, { "from": "code-validator", "to": "code-coder", "condition": "fail", "edgeKind": "backward" } ] } ``` 同理提供 `content.json`(`content-planner → content-writer → content-validator`)、`ops.json`。 设计要点: - **是起点集合,不是穷举枚举** —— 加一种公司类型只需加一个 JSON 文件,不改任何代码。`orggraph-planning` 读全部模板后自行选型/裁剪/组合,不是代码里的 `switch(type)`。 - **`nodes` 里的角色名是建议**,`orggraph-planning` 必须对照 `company templates` 核实是否真有可招模板;没有则走 `needs_training` 培养分支。 - **模板里的角色名必须遵循 [27-NAMING](./27-naming-conventions.md) §三的 `{domain}-{职能}` 规则**,不允许裸用 `planner` / `validator`。 - ⚠️ **避开命名冲突 id**:`agent-creator` / `digester` / `evolver` / `evolver-youtube-crawler` / `hunter` / `innovator` 这 6 个 id 在 `system/agents/` 与 `system/templates/` 下同时存在且内容可能不同步(27 号文档 §七),模板里不要引用它们。 --- ## 五、任务划分 | # | 任务 | 文件 | 依赖 | |---|---|---|---| | **T0** | `company.json` 新增 `goal`(company-goal)字段;`create({id,name,goal,description})`;`POST /create` 与前端建公司表单同步 | `server/services/company-service.js`、`server/routes/company.js`、前端建公司表单 | 无 | | **T1** | schema:节点加 `status` / `reports_to_node_id`;DROP 全量唯一索引改建部分索引;**DROP 重建 `org_graph_edges` 为执行流边** | `server/services/graph-db.js` | 无 | | **T2** | `org-graph-service.js`:图的整体写入(含 §3.3 四项校验)、增量改、只读查询、节点软删除级联失效边 | `server/services/org-graph-service.js`(新增) | T1 | | **T3** | 四个 API 端点(POST / PATCH / GET / POST …/replan) | `server/routes/company.js` | T2 | | **T4** | `system/org-graph-templates/{dev,content,ops}.json` | 新增 | 无,可并行 | | **T5** | `system/templates/orggraph-planning/` agent 本体(含 §4.1 八步工作流) | 新增 | T3, T4 | | **T5b** | 「从 base 元 agent 继承 + agent-creator 填充」的招聘分支(§4.1 第 7 步),含新建 agent 的 `scope='company'` 落库 | `server/services/company-service.js`、`server/services/graph-service.js` | T1 | | **T6** | `/create` 路由接入自动招聘 + 异步触发 | `server/routes/company.js` | T5 | | **T7** | `/fire` 路由接入节点软删除(改路由 handler 本体,**不是** `CompanyService.fire()`——后者只有测试脚本在用,是死代码路径) | `server/routes/company.js` | T2 | | **T8** | 前端:复用 P3 画布组件渲染 org graph(或先上纯文本拓扑序占位) | `web/src/views/CompanyDashboard.vue` | T3(+ P3 的 T3 若要画布) | | **T9** | 测试:`org-graph-service.test.js`(四项校验各一条负例、幂等写入、软删除级联)+ e2e 扩展 | `server/tests/unit/org-graph-service.test.js` | T2, T3 | ### 三个可独立验收的阶段 11 个任务按依赖收敛为 3 个阶段,**每个阶段都能单独验收、单独上线**: | 阶段 | 含 | 交付什么 | 怎么验收 | 是否涉及 agent | |---|---|---|---|---| | **A · 数据层** | T1 → T2 → T3、T7、T9 | schema 改造完成、`org-graph-service` 可读写、四个 API 端点通 | 手工 POST 一张合法图 → GET 读回;POST 一张非法图(有环 / 多入口 / 孤立节点 / 回退边未标注)→ 400 并报出违反 §3.3 哪一条 | ❌ 纯后端,不起任何 LLM | | **B · 自动生成** | T4 → T5 → T5b → T6 | 模板库、`orggraph-planning` agent、元 agent 继承招聘分支、建公司自动触发 | `POST /create` 带 `company-goal` → 秒级返回且 `agents` 出现 `orggraph-planning` → 异步跑完后 `GET org-graph` 拿到一张合法图 | ✅ 端到端跑真实 agent | | **C · 展示** | T8 | Company Dashboard 渲染 org graph | 有图的公司能看到拓扑;无图的公司显示空态不报错 | ❌ | **前置**:**T0(`company-goal` 字段)✅ 已实现**(commit `2255f09`)——它是阶段 B 的输入前提,不属于任何阶段,先做掉了。 **阶段内顺序**:A 内部 T1→T2→T3 是硬串行,T7/T9 依赖 T2 后可并行;B 内部 T4 可最早开工(不依赖任何代码),T5b 只依赖 T1 因此可以提到 A 阶段并行做。 **阶段间可以停**:只做完 A,系统就有了一个「能手工建图、能校验、能读」的 org graph;B 才让它自动化;C 只是让人看得见。**如果要最快看到端到端效果,A→B 两个阶段即可,C 可以无限期后延**(阶段 C 还依赖 P3 的画布组件,见 §4.5)。 --- ## 六、存量公司 P0 的 `seedCompanyAgents()` 已把所有存量公司的角色回填成 `org_graph_nodes`(**无边**)。P2 上线时它们的 workflow 是空的。 **决定:不自动补边。** 三个候选方案里前两个都被证伪: | 方案 | 为什么不选 | |---|---| | 按 `config.json.parentId` 反推 | 该字段只在 `scaffoldNewAgent()` 培养路径写入,模板招聘来的 agent 根本没有,覆盖率太低 | | 按 `config.json.type` 推层级 | 实测 type 取值是 `specialist`(22) / `cxo`(3) / `devloop`(6) / `executor`(2) / `meta`(1) / `quality_assurance`(1) / `skill`(1) 混杂,且 `ceo` 模板**没有 config.json**——它不是层级体系,更不是执行顺序 | | **不补,留空(选定)** | 没有数据来源支撑的边,不如没有边。存量公司要用新流程,就重新跑一次 `orggraph-planning` | **给存量公司补图的路径**:提供一个手动触发 `orggraph-planning` 的端点,让用户对已有公司显式重跑一次规划。这比自动猜测诚实,也给了用户选择权。 --- ## 七、影响面 | 文件 | 改动 | |---|---| | `server/services/graph-db.js` | 节点加列、索引重建、`org_graph_edges` 重建 | | `server/services/org-graph-service.js` | 新增 | | `server/routes/company.js` | `/create` 接入;三个 org-graph 端点;`/fire` 接入软删除 | | `system/org-graph-templates/*.json` | 新增 | | `system/templates/orggraph-planning/*` | 新增 | | `web/src/views/CompanyDashboard.vue` | 渲染入口 | **明确不改**:`company.json` schema、`hire()` 的签名(v0.4 简化后不再需要 `reportsTo`)、`AgentManager` / `PromptBuilder`、P1 的 `graph-service.js`(**唯一例外**是它建的那个全量唯一索引必须 DROP,见 T1)。 --- ## 八、风险 / Open Questions 1. **单一入口约束是否过严**(§3.3 第 2 条):要求恰好一个入度为 0 的节点。如果将来出现"两类任务从不同节点进入同一家公司"的需求,这条要放宽为"允许多入口 + 任务携带指定入口"。当前按单入口设计,因为它让"任务从哪开始"无需额外信息。 2. **`orggraph-planning` 的规划质量没有硬校验**:§3.3 只保证图**结构合法**,不保证它**适合这家公司**。缓解:`workspace/orggraph-plan.md` 留下决策记录供人工复核;发现不合理时走 §4.3 的 PATCH 增量修正。 3. **一次建公司多一轮 LLM 对话**:从毫秒级文件操作变成几十秒到几分钟的真实 Claude CLI 调用。`POST /create` 保持同步返回(fire-and-forget),但前端需要一个"workflow 规划中"的过渡态。 4. **6 个命名冲突 id**(27 号文档 §七):本文档 §4.6 已要求模板避开它们,但如果 `orggraph-planning` 自主判断需要其中某个角色,仍会撞上"该读哪份 `agent.md`"的歧义。建议在 P2 上线前把这个产品决策做掉。 5. **P2 前端依赖 P3 画布**(§4.5):若两者不同期上线,P2 需要接受纯文本占位形态。 --- ## 九、与其他阶段的关系 - **P0/P1**:均已实现。P2 唯一需要动 P1 代码的地方是 DROP 它建的全量唯一索引(T1)。 - **P3/P4**:P2 产出图,P3/P4 执行图。**两者共用同一套 schema 形态、执行引擎和画布组件** —— 这是 v0.4 新模型最大的收益。P2 不含任何执行逻辑。 - **端到端**:本文档在整条链路里的位置见 [27-E2E](./27-e2e-task-execution-flow.md) §三 阶段 2,具体跑一遍的样子见 [27-EXAMPLE](./27-example-dev-company-walkthrough.md) 阶段 2。