27-p2-org-graph.md 22.8 KB

27-P2. Org Graph — 公司级 Workflow 的生成与落地

所属: 27 Graph Agent 架构 的 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 命名规范。v0.3 的 org-graph-creator 在本版本统一改名为 orggraph-planning。 配套: 27-E2E 端到端流程(本文档在整条链路里的位置)、27-EXAMPLE 参考场景(跑一遍具体的数)


一、模型变更说明(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(org-planner 等) system agent 引用
边的语义 执行流:condition ∈ {success, fail} 执行流:同左
执行引擎 27-P3/P4 的同一套 同左

由此带来的四处结构性改动(这也是本次重写的主要内容):

  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。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

-- 节点:追加 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 §四.A):

  1. 无环(仅针对 edge_kind='forward')—— 三色 DFS,复用 DevLoop doc-store.js:167 的 hasCycle() 实现。
  2. 单一入口(✅ 已定案,见 27-REQ 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 §四。
  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 §八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 返回形状:

{
  "nodes": [
    { "id": 12, "roleName": "org-planner",   "systemAgentId": "org-planner",   "status": "active" },
    { "id": 13, "roleName": "org-coder",     "systemAgentId": "org-coder",     "status": "active" },
    { "id": 14, "roleName": "org-validator", "systemAgentId": "org-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() 之后追加:

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 T3 的画布组件(其本身又是扩展自既有的 web/src/components/devloop/UnitsDAG.vue,已实现 SVG + Kahn 分波布局 + 状态着色 + 点击下钻)。

这也意味着 P2 的前端工作依赖 P3 的画布组件先做出来。若想让 P2 独立上线,可以先在 Company Dashboard 用一个纯文本的拓扑序列表占位(org-planner → org-coder → org-validator),等 P3 画布就绪再替换。

4.6 system/org-graph-templates/

每个文件是一种公司类型的 workflow 骨架:

// system/org-graph-templates/dev.json
{
  "id": "dev",
  "label": "开发型公司",
  "whenToUse": "目标包含实现某个产品/功能、需要跑代码开发-测试循环时优先参考",
  "nodes": ["org-planner", "org-coder", "org-validator"],
  "edges": [
    { "from": "org-planner",   "to": "org-coder",     "condition": "success" },
    { "from": "org-coder",     "to": "org-validator", "condition": "success" },
    { "from": "org-validator", "to": "org-planner",   "condition": "fail",
      "failReason": "input_underspecified", "edgeKind": "backward" },
    { "from": "org-validator", "to": "org-coder",     "condition": "fail", "edgeKind": "backward" }
  ]
}

同理提供 content.json(org-planner → org-writer → org-validator)、ops.json。三张图首尾节点同名, 只有中段执行节点不同——命名轴是节点所在图的层级,不是业务领域(27-NAMING §3.1)。

dev.json 那两条 fail 边是 P3 的 fail_reason 细分路由(27-P3/P4 §四.D.2.2): 带 failReason 的边按归因结果精确匹配,不带 failReason 的那条是兜底边, 任何有 fail 出边的节点都必须留一条,否则归因产出图上没有的取值时会落到死路。 content.json / ops.json 目前只有兜底边。

设计要点:

  • 是起点集合,不是穷举枚举 —— 加一种公司类型只需加一个 JSON 文件,不改任何代码。orggraph-planning 读全部模板后自行选型/裁剪/组合,不是代码里的 switch(type)。
  • nodes 里的角色名是建议,orggraph-planning 必须对照 company templates 核实是否真有可招模板;没有则走 needs_training 培养分支。
  • 模板里的角色名必须遵循 27-NAMING §3.1 的 org-{职能} 规则,不允许裸用 planner / validator,也不再按业务领域起 code- / content- 这类前缀(v0.2 的写法,已作废)。
  • ⚠️ 避开命名冲突 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. 单一入口约束是否过严 ✅ 已定案(27-REQ R-13.4):强制单入口。保留原始论证:如果将来出现"两类任务从不同节点进入同一家公司"的需求(如 bugfix 直接从 coder 进入),需放宽为"允许多入口 + 任务携带指定入口",届时任务入口要多带一个参数。
  2. orggraph-planning 的规划质量没有硬校验:§3.3 只保证图结构合法,不保证它适合这家公司。缓解:workspace/orggraph-plan.md 留下决策记录供人工复核;发现不合理时走 §4.3 的 PATCH 增量修正。
  3. 一次建公司多一轮 LLM 对话:✅ 实测约 30 秒(p2-verify 验证公司,4 节点 5 边 + 4 个角色招聘)。POST /create 保持同步返回(fire-and-forget)。⚠️ 前端空态文案已提及"存量公司或规划仍在进行中",但没有真正区分这两种状态——要区分需要轮询 /org-graph 或给公司加一个 orgGraphStatus 字段。当前是刻意的简化。
  4. 6 个命名冲突 id(27 号文档 §七):本文档 §4.6 已要求模板避开它们,但如果 orggraph-planning 自主判断需要其中某个角色,仍会撞上"该读哪份 agent.md"的歧义。建议在 P2 上线前把这个产品决策做掉。
  5. P2 前端依赖 P3 画布(§4.5):✅ 已按占位形态交付——T8 实现的是拓扑链文本渲染(含环检测降级、join 不到 agent 的节点也能显示)。P3 画布就绪后替换即可,不阻塞 P2。

九、与其他阶段的关系

  • P0/P1:均已实现。P2 唯一需要动 P1 代码的地方是 DROP 它建的全量唯一索引(T1)。
  • P3/P4:P2 产出图,P3/P4 执行图。两者共用同一套 schema 形态、执行引擎和画布组件 —— 这是 v0.4 新模型最大的收益。P2 不含任何执行逻辑。
  • 端到端:本文档在整条链路里的位置见 27-E2E §三 阶段 2,具体跑一遍的样子见 27-EXAMPLE 阶段 2。