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 虚拟引用寻址已实现(commit226489d,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 的同一套 | 同左 |
由此带来的四处结构性改动(这也是本次重写的主要内容):
org_graph_edges从relation_type改为condition+edge_kind,与work_graph_edges同构(§三.2)。- 汇报关系降级为节点上的可空属性,不再是图的主干语义(§三.2)。
hire()不再需要reportsTo参数 —— 图由orggraph-planning整体规划、整体写入,不是靠招聘时逐个累积出来的(§四.2,这反而让 P2 对company-service.js的侵入变小了)。- 前端不再自己写"按汇报关系分层渲染",直接复用 Work Graph 的画布组件(§四.5)。
二、目标与非目标
目标
orggraph-planningagent:建公司后自动运行,读公司目标/描述 + org graph 模板库 + 可招聘角色清单,产出一张公司级 workflow,并执行招聘。- 图的持久化:
org_graph_nodes/org_graph_edges落地为执行流图,schema 与work_graph_edges同构。 - 图的读写 API:整体写入(规划产物落库)、增量修改(后续 HR 调整)、只读查询(供前端与执行引擎消费)。
- 前端可视化:复用 Work Graph 画布组件渲染 org graph。
非目标
- 不做图的执行 —— 执行引擎、启动/暂停/单步/重试、SSE 全部属于 27-P3/P4。P2 只负责"把图建出来、存好、能读能画"。
- 不做
company.json.agents[]的废弃 —— 它仍是"这家公司雇了谁"的真相源;org graph 回答的是"任务怎么流过这些人",两者并存。 - 不做存量公司的补图 —— 见 §六。
- 不做
workgraph-planningagent 本身的设计 —— 属于 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):
- 无环(仅针对
edge_kind='forward')—— 三色 DFS,复用 DevLoopdoc-store.js:167的hasCycle()实现。 - 单一入口(✅ 已定案,见 27-REQ R-13)—— 必须恰好有一个入度为 0 的 forward 节点,任务从这里进入。多入口会让"任务从哪开始"无法确定,且需要任务入口多带一个参数。
- 可达性 —— 每个节点都必须从入口可达,孤立节点直接拒绝。
- 回退边必须显式标注 —— 任何指向"拓扑序更早节点"的边,
edge_kind必须是backward;引擎独立复核,不信任标注(27 号文档 §3.4 第 5 点的同款兜底)。
四、设计方案
4.1 orggraph-planning agent
新增 system agent 模板 system/templates/orggraph-planning/(config.json.type 取 meta-bootstrap,与业务角色区分)。工作流:
- 读输入:公司
id/name/goal(company-goal) /description(由 §4.4 的首轮消息带入)。company-goal是「这家公司要达成什么」,description是「这家公司是干什么的」——前者是规划流水线的主依据,后者是背景。见 27-NAMING §四。 - 读候选骨架:
ls system/org-graph-templates/,逐个读取,理解各自的whenToUse。 - 读可招角色:
node bin/lemon.js company templates(复用现成 CLI,listTemplates()返回 id/name/type/description)。 - 规划:综合三者,产出一张 workflow —— 节点列表 + 执行流边。允许裁剪、替换、组合多个模板,也允许判断需要一个模板库里没有的新角色。
- 落盘规划记录:
workspace/orggraph-plan.md(图 + 为什么这么设计的理由),供事后审计。 - 提交图:调用 §4.3 的整体写入 API,图通过 §3.3 校验后落库。
- 执行招聘:按拓扑序对每个节点调
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 只保证「角色能被建出来且带四段式骨架」,不强制等训练完成。
- 模板已存在 → 走 P1 虚拟引用寻址,不拷贝
- 收尾:关闭自己的心跳转入休眠。后续增量调整归 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
单一入口约束是否过严✅ 已定案(27-REQ R-13.4):强制单入口。保留原始论证:如果将来出现"两类任务从不同节点进入同一家公司"的需求(如 bugfix 直接从 coder 进入),需放宽为"允许多入口 + 任务携带指定入口",届时任务入口要多带一个参数。orggraph-planning的规划质量没有硬校验:§3.3 只保证图结构合法,不保证它适合这家公司。缓解:workspace/orggraph-plan.md留下决策记录供人工复核;发现不合理时走 §4.3 的 PATCH 增量修正。- 一次建公司多一轮 LLM 对话:✅ 实测约 30 秒(
p2-verify验证公司,4 节点 5 边 + 4 个角色招聘)。POST /create保持同步返回(fire-and-forget)。⚠️ 前端空态文案已提及"存量公司或规划仍在进行中",但没有真正区分这两种状态——要区分需要轮询/org-graph或给公司加一个orgGraphStatus字段。当前是刻意的简化。 - 6 个命名冲突 id(27 号文档 §七):本文档 §4.6 已要求模板避开它们,但如果
orggraph-planning自主判断需要其中某个角色,仍会撞上"该读哪份agent.md"的歧义。建议在 P2 上线前把这个产品决策做掉。 - 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。