27. Graph Agent 架构 — Org Graph / Work Graph 与 System Agent 统一寻址
版本: v0.3(P0/P1 已实现;Org Graph 建模在 v0.3 修正为 company 级 workflow) 日期: 2026-08-15 状态: P0 ✅ 已实现(
graph-db.js+graph-seed-service.js,见第七节);P1 ✅ 已实现(commit226489d,graph-service.js);P2 起 📝 设计中/未实现 命名: 新增 agent id / 表名 / 字段名前先读 27-NAMING 命名规范 前置: 02 四层 Agent 架构、24 公司层架构、22 Agent 记忆系统、server/src/core/devloop/loop-controller.js(现有 retry-with-reflection 实现)
一、核心问题
现有模型(24 号文档)的局限
24 号文档确立的模型是「System Agent(全局单例)+ Agent Template(可复用蓝图)+ Company Agent(雇佣时从模板 文件拷贝 出来的实例)」。这套模型跑通了 Multi-Tenant 公司隔离,但暴露出四个结构性问题:
- 状态碎片化:每次
hire()都是一次fs.copyFileSync,同一角色在 10 家公司名下就有 10 份互不相干的agent.md/memory.md。模板更新后,已雇佣的实例不会同步,也无法双向学习——A 公司的 QA 遇到的坑,B 公司的 QA 完全学不到。 - 无法自我进化:Agent 的"智能"被锁在一次性拷贝的静态文件里,没有一个机制让它在被反复调用后越来越准。唯一的进化路径是
depositTemplate()——训练出一个新 agent,跑 benchmark,≥95 分才存回模板库,这是"生个孩子"而不是"自己变强"。 - 协作关系是扁平列表:
company.json.agents[]只是一个角色名数组,没有边、没有汇报关系,更没有"谁的输出喂给谁"的显式结构。 - 失败恢复局限在单一场景:目前唯一的循环恢复机制是 DevLoop 的
runUnit(Coder ↔ Tester 两节点、固定方向、retry_budget硬顶 5 次),无法泛化到"任意节点失败后,带着上下文跳回图中任意历史节点重跑"这种通用场景。
设计目标
把「company 拥有并复制 agent」改造为「company 引用全局 system agent,并在其上定义静态协作结构(Org Graph)与动态任务执行路径(Work Graph)」:
- Agent 是跨公司复用、持续自我进化的一等公民,不是一次性文件拷贝。
- 公司级 workflow(Org Graph,建公司时由
orggraph-planning生成一次)与 org node 级 workflow(Work Graph,每次接到任务时由workgraph-planning生成)分层:两者同构、共用同一套引擎,只是生成时机与节点指向不同。 - 失败恢复不再局限于"重跑同一个节点":任意节点失败可以携带上下文回退到图中任意历史节点,同时有引擎级别的硬性防死循环机制。
二、术语定义
| 术语 | 定义 |
|---|---|
| System Agent | 全局唯一的可执行 Agent 实体。自带 prompt 定义、公共记忆(memory.md)、按公司分区的私有历史。是 24 号文档里"System Agent"与"Agent Template"两个概念的合并——不再区分"模板"和"实例",只有"这个 agent,被谁调用过"。 |
| scope | System Agent 的可见性:public(跨公司共享,记忆公共累积)或 company(私有,只属于 owner_company_id 指定的一家公司)。 |
| 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 §一。 |
| Work Graph | org graph node 级别生成的 workflow。某个 org node 收到任务后,现场调用 workgraph-planning agent 针对这一具体任务规划出。节点 = 对某个 system agent 的一次调用;边的语义与 Org Graph 完全同构。执行完归档,不复用到下一个任务。 |
| 两者关系 | Org Graph 与 Work Graph 是同一种东西在不同层级上的递归——同一套 schema 形态、同一个执行引擎、同一套控制台 UI,区别只在生成者、生成时机、节点指向。 |
| Loop-Validator | 一个专职的 system agent,内置带硬编码熔断逻辑的 skill。所有"回退到历史节点"的边必须先经过它:备案 → 审计上次修复是否生效 → 判断修复方案是否被正确传递 → 循环数 > 3 强制终止。 |
| Node 内容 / Edge 内容 | 图结构本身(节点、边、状态、指针)存 SQLite;节点的输入输出内容、日志、上下文,依旧是 Markdown 文件,物理落在对应 system agent 的目录下。 |
三、架构设计
3.1 三层模型总览
┌─────────────────────────────────────┐
│ System Agent 池 │
│ (全局唯一,可跨公司复用,自我进化) │
│ │
│ [ceo] [coder] [qa-judge] [...] │
│ ▲ ▲ ▲ │
└─────┼───────┼─────────┼────────────────┘
│ │ │ (引用,非复制)
┌────────────┴───────┴─────────┴──────────────┐
│ Company: {companyId} │
│ │
│ Org Graph(company 级 workflow,建公司时生成)│
│ [code-planner] --success--> [code-coder] │
│ ↑ │ │
│ └──── fail(backward) ───┘ │
│ │
│ Work Graph(org node 级 workflow,per-task) │
│ [node1:coder] --success--> [node2:tester] │
│ --fail-----> [loop-validator]│
│ │ │
│ ▼ │
│ [回退到 node1] │
└────────────────────────────────────────────────┘
3.2 System Agent:唯一执行体与数据隔离
目录结构(取代现有 system/agents/ 与 system/templates/ 的分离):
system/agents/{agentId}/
agent.md / soul.md / identity.md / tools.md # 定义本体,全局唯一
memory.md # 公共记忆:自我迭代蒸馏出的能力/经验,跨公司共享持续增长
config.json # { scope: public|company, owner_company_id, status, benchmark_score }
companies/
{companyId}/
memory.md # 该公司私有工作记忆(目标、上下文、汇报关系)
history/
{workGraphId}-{nodeId}-{attempt}.md # 该公司触发的每次执行原始记录
logs/
- company-scoped(私有):
companies/下永远只有 owner 一个子目录,代码路径与 public 完全一致,不做特殊分支。 - public(公共):
companies/下有多个公司各自的隔离子目录。
读取隔离原则:PromptBuilder 组装某次调用的上下文时,只按 (agentId, companyId) 构造路径——公共部分(agent.md/memory.md)+ 这一家公司自己的 companies/{companyId}/。别的公司的目录从物理路径上就不会被触达,跟现有 DevLoop CONVENTIONS.md §12「跨 unit 只能读 ../{otherUnit}/contract.json」是同一种"靠构造方式天然隔离"的思路,不是运行时权限过滤。
写入分级:私有 companies/{id}/memory.md 每次调用后可自由写,风险局限在一家公司内。公共 memory.md 的更新(自我进化)风险更高——写错会污染所有后续调用方——因此需要走一次自省校验(复用 loop-validator 或类似机制),并且像 DevLoop 项目一样纳入 git 版本管理,坏的自我迭代可以回滚。
新建与升级治理:Work-graph 执行中如果需要的 system agent 不存在,直接触发创建,默认 scope=company(私有)。是否升级为 public,复用现有 hireWithTraining() → agent-creator/innovator 训练 → benchmark ≥95 → 升级的门槛链路,只是终点从"写文件到 system/templates/"改为"更新 DB 里的 scope 字段"。不新增治理机制,只改落点。
3.3 Org Graph:company 级 workflow
⚠️ v0.3 修正:本节 v0.2 的内容("公司静态结构、边是汇报/协作关系、招聘时由 CEO 或 HR 维护")已作废。完整设计见 27-P2 v0.4。
- 节点:对某个 org node agent 的引用(
org_graph_nodes)。 - 边:执行流(
org_graph_edges:condition ∈ {success, fail}、edge_kind ∈ {forward, backward}),与work_graph_edges同构。 - 谁生成:
orggraph-planningagent,在建公司时整体规划、整体写入一次——不是靠招聘动作逐次累积出来的。 - "招聘"语义:只负责把这个角色的 agent 目录建出来(走 P1 的虚拟引用,不再拷贝
agent.md),与图的结构无关。 - 任务如何进入:从图中入度为 0 的节点开始,按边的
condition逐节点推进;每个 org node 被激活后再调workgraph-planning规划自己那一层的 work graph。
3.4 Work Graph:任务动态执行图
- 某 org 节点收到一个 goal/task。
- 该节点调用
workgraph-planningagent,针对这个具体任务规划出一张图:节点指向要执行的 system agent,边带success/fail条件。 - 目标节点范围不受 org graph 约束,只受 system agent 池约束——planning agent 可以调用任意 public agent、company 自己私有的 agent,或判断需要新建一个(走 3.2 的创建+治理链路)。
- 回退边必须显式经过 loop-validator 节点——即"节点A --fail--> loop-validator --> 节点B",而不是"节点A --fail(隐藏回退属性)--> 节点B"。这是画图时的结构要求,不是引擎运行时才附加的隐藏逻辑。
- 执行前结构校验:图规划完成后、进入执行前,引擎做一次静态校验——任何边如果目标节点在图中已存在其他指向它的边(即会构成"回到历史节点"),但没有经过 loop-validator 节点,直接拒绝这张图,不进入执行。类比现有
npm run spec:check的"结构先校验,再允许跑"模式。这是对"planning agent 应该自觉画上 validator"这条规则的引擎级兜底,防止规划疏漏导致死循环绕过审计。
3.5 Loop-Validator:死循环兜底
- 本身是一个 system agent(可以是 public,因为"判断循环是否收敛"这个能力本质上与具体业务无关,值得跨公司共享进化)。
- 自带 skill,skill 内部包含硬编码的计数器/熔断逻辑,不是纯靠一次 LLM 推理决定"要不要停":
- 备案:任意节点即将进入回退循环前,先向 loop-validator 登记这次尝试。
- 审计:目标节点重跑完成后,loop-validator 比对这次结果与上次失败原因,判断是否真的修复。
- 修复方案传递校验:如果没修复,检查这次回退时是否把"应该如何修复"的方案正确带给了下一轮,而不是盲目重跑。
- 硬顶:循环次数 > 3,无论 LLM 判断如何,skill 内的计数器强制终止,不进入第 4 次回退。
- 引擎级不变量:任何回退边必须路由经过 loop-validator,这一点不能被 planning agent 在规划阶段跳过——由 3.4 第 5 点的执行前校验保证。
3.6 与执行链路的关系
节点执行本质上仍是现有 AgentManager.callAgent() 的调用,只是 getAgentConfig() 的输入从"companyId:roleName 路径拼接"变成"(system_agent_id, companyId) 的引用查表",PromptBuilder.build() 在装配 prompt 时按 3.2 的隔离规则拼装公共定义 + 私有语境。执行引擎(session-pool、runtime-resolver、stream-executor)不需要改,改的是"config 从哪来"这一层。
四、数据模型(Schema 草稿)
DB 只存关系与状态指针,节点/边的具体内容(任务描述、输出、日志)始终是 Markdown 文件,按 3.2 的目录约定物理落在对应 system agent 目录下(不在 DB 里存文件路径字符串,路径由 (system_agent_id, company_id, work_graph_id, node_id, attempt) 确定性推导,避免路径漂移)。
-- 全局 system agent 注册表
CREATE TABLE system_agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
scope TEXT NOT NULL CHECK (scope IN ('public', 'company')),
owner_company_id TEXT, -- scope='company' 时必填
status TEXT NOT NULL DEFAULT 'active', -- active | training | deprecated
benchmark_score REAL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- 公司静态协作结构
CREATE TABLE org_graph_nodes (
id TEXT PRIMARY KEY,
company_id TEXT NOT NULL,
system_agent_id TEXT NOT NULL REFERENCES system_agents(id),
role_name TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE TABLE org_graph_edges (
id TEXT PRIMARY KEY,
company_id TEXT NOT NULL,
from_node_id TEXT NOT NULL REFERENCES org_graph_nodes(id),
to_node_id TEXT NOT NULL REFERENCES org_graph_nodes(id),
relation_type TEXT NOT NULL -- reports_to | collaborates_with
);
-- 单次任务的动态执行图
CREATE TABLE work_graphs (
id TEXT PRIMARY KEY,
company_id TEXT NOT NULL,
triggered_by_org_node_id TEXT REFERENCES org_graph_nodes(id),
goal_id TEXT, -- 一次人类需求描述(唯一,验收边界)
okr_node_id TEXT, -- 形如 O-001.1.1,配合 company_id 定位
task_id TEXT, -- 形如 T-xxx
status TEXT NOT NULL DEFAULT 'planning', -- planning | running | passed | failed | blocked
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE work_graph_nodes (
id TEXT PRIMARY KEY,
work_graph_id TEXT NOT NULL REFERENCES work_graphs(id),
system_agent_id TEXT NOT NULL REFERENCES system_agents(id),
node_kind TEXT NOT NULL DEFAULT 'task', -- task | loop_validator
status TEXT NOT NULL DEFAULT 'pending', -- pending | running | success | fail | blocked
attempt INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE work_graph_edges (
id TEXT PRIMARY KEY,
work_graph_id TEXT NOT NULL REFERENCES work_graphs(id),
from_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id),
to_node_id TEXT NOT NULL REFERENCES work_graph_nodes(id),
condition TEXT NOT NULL CHECK (condition IN ('success', 'fail')),
edge_kind TEXT NOT NULL DEFAULT 'forward' -- forward | backward(backward 必须 to_node 是 loop_validator 或来自 loop_validator)
);
edge_kind='backward' 由 planning agent 在画图时显式标注;执行前校验(3.4 第 5 点)会独立复核——不信任标注本身,而是遍历图结构确认任何指向"已在图中更早出现过"的节点的边,必须满足"经过 node_kind='loop_validator'"这一条件,双重保险。
五、与现有系统的关系 / 迁移策略
| 现有组件 | 变化 |
|---|---|
server/services/company-service.js 的 hire() / _copyTemplate() |
废弃文件拷贝,替换为"创建/复用 system agent + 在 org_graph 加引用节点" |
system/templates/ |
逐步废弃,内容并入 system_agents(scope=public),_registry.json 被 DB 表取代 |
AgentManager.getAgentConfig() |
输入从路径拼接改为 (system_agent_id, company_id) 查表 + 引用 3.2 目录约定 |
PromptBuilder.build() |
增加"公共定义 + 私有语境"两段拼装逻辑 |
hireWithTraining() / agent-creator / innovator / depositTemplate() |
保留,只改落点(写 DB scope 字段而非写模板目录) |
DevLoop loop-controller.js 的 runUnit |
可视为 Work Graph 的一个特例(固定两节点、单方向重试)。本次不强制收编,留作后续 Phase 6 的可选项 |
六、开发计划(Phased Roadmap)
| Phase | 目标 | 交付物 | 验收标准 |
|---|---|---|---|
| P0 地基 ✅ | 建表 + 现有数据迁移,不改变任何现有执行路径 | system_agents/org_graph_* 表;把现有 system/agents/ + system/templates/ 目录扫描导入为种子数据(system_agents 行,scope 按当前所属推断) |
迁移脚本跑完,system_agents 表行数与现有 agent+模板目录数一致(实际行数与本文档最初估计不同,见第七节);现有 company-service.js 行为不变(并行只读验证,已确认零改动) |
| P1 虚拟引用 | 停止文件拷贝,改为引用 | 改造 AgentManager.getAgentConfig() + PromptBuilder.build() 支持 (agentId, companyId) 寻址;company-service.hire() 改为创建 org_graph 引用节点而非 _copyTemplate()。细化需求与设计见 27-P1 虚拟引用寻址 |
新雇佣的 agent 不再产生文件拷贝,调用时能正确拼装公共+私有上下文;旧公司(仍是拷贝模式)继续可跑,不强制迁移存量数据 |
| P2 Org Graph | company.json.agents[] 迁移为图结构 |
org_graph_nodes/edges 落地;HR/CEO 招聘走新表。细化需求与设计见 27-P2 Org Graph 落地 |
现有 Company Dashboard 前端能正确读出 org graph 并渲染(哪怕暂时按列表展示) |
| P3 Work Graph 最小闭环 | 单向执行,先不做回退 | workgraph-planning agent(system agent,新建);work_graphs/nodes/edges 表;顺序执行器(success → next,fail → 停止/标记 blocked,暂不回退)。P3+P4 已合并细化为一份需求与设计(含新增的执行控制台页面),见 27-P3/P4 Work Graph 执行控制台 |
一个 goal 能被规划成 2-3 节点的图并顺序跑完,状态正确落库 |
| P4 回退 + Loop-Validator | 引入死循环防护 | loop-validator system agent + 内置熔断 skill;edge_kind=backward 支持;3.4 第 5 点的执行前结构校验。细化需求与设计同上,见 27-P3/P4 Work Graph 执行控制台 |
人为构造一个"总是失败"的节点,验证第 4 次回退被 loop-validator 强制终止,且校验能拒绝"回退边未经过 validator"的非法图 |
| P5 自我进化 | 公共记忆蒸馏机制 | 公共 memory.md 写入走自省校验 + git 版本化;company-scoped → public 的升级门槛复用现有 benchmark ≥95 链路 |
同一 public agent 被两家公司调用后,memory.md 有可观察的增量更新,且能 git revert 到升级前版本 |
| P6( |
统一循环逻辑 | 把 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 §二结论 2 |
DevLoop 现有测试套件(server/tests/devloop/**)在新引擎上全部通过,无需单独维护两套循环代码 |
依赖顺序是强顺序的:P1 依赖 P0 的数据先迁移进 DB;P3 的执行器依赖 P1 的引用寻址跑通;P4 的回退依赖 P3 的顺序执行先稳定。P5、P6 相对独立,可以在 P4 稳定后并行推进或往后延。
每个 Phase 建议保留旧路径作为 fallback(不删除 _copyTemplate()/system/templates/ 扫描逻辑),直到下一 Phase 验收通过,避免中途出问题时无法回退。
七、P0 实施发现(2026-08-15)
P0 已实现(server/services/graph-db.js + server/services/graph-seed-service.js + scripts/graph-seed.js,npm run graph:seed 可跑),经 code review 通过。实施过程中对真实仓库数据做了独立核实,几个数字和本文档最初的估计(来自第一轮调研)不一致,记录如下,供 P1/P2 设计时参考:
| 项 | 本文档最初估计 | 真实数据 |
|---|---|---|
system/agents/ 里真正的 agent(含 agent.md) |
10 | 8 —— screenwriter/、youtube-crawler/ 两个目录没有 agent.md,不满足"是一个可执行 agent"的最低条件,被迁移脚本正确排除 |
system/templates/ 目录数 |
34 | 39 |
最终落库的 source_kind='template' 行数 |
34 | 33(39 减去下面的命名空间重叠问题里被 global_agent 占用的 6 个) |
_registry.json 里 benchmark_score 非空的条目 |
9 | 3(ceo=97/qa-judge=95/base=90)——另外 6 个 devloop-* 虽然在 registry 里能匹配上 id,但 registry 原始数据里 benchmarkScore 本来就是 null,不是脚本漏读 |
idearun 公司 6 个角色里"公司专属、无对应模板"的数量 |
4 | 0 —— 6 个角色现在全部能在 system/templates/* 里找到同名目录,全部复用已有行,没有触发新建 scope='company' 记录 |
关键发现:system/agents/ 与 system/templates/ 命名空间不是互斥的
有 6 个 id(agent-creator/digester/evolver/evolver-youtube-crawler/hunter/innovator)在两个目录下都存在,描述的是概念上"同一个" agent,但物理上是两份独立的文件(内容很可能已经不同步)。这一点在最初两轮只读调研(用于写这份文档正文)里没有被发现,是 P0 实际扫描真实目录时才暴露出来的。
P0 阶段的处理方式是迁移脚本层面的临时保护:system_agents.id 是跨 source_kind 共享的单一 TEXT 主键,seedTemplates() 遇到一个已经被 seedGlobalAgents() 占用的 id 时会跳过写入(不覆盖),保证 global_agent 优先、幂等可重跑。这只是避免了"数据被静默覆盖"的坏结果,没有回答"这 6 个 agent 到底该以哪份文件为准"这个产品问题——system/agents/{id}/agent.md 和 system/templates/{id}/agent.md 目前很可能内容不同,P1 做虚拟引用寻址、P2 做 org graph 招聘的时候都会撞到这批 id,需要在那之前决定:是人工合并成一份、保留 global_agent 版本视为权威并废弃 template 版本,还是干脆认为这 6 个本来就是两个不同的东西(比如一个是"基础设施 agent"、一个是"可招聘的角色模板")、id 冲突只是命名巧合。这个决定应该在 P1 动手改 AgentManager/PromptBuilder 之前做,否则虚拟引用会读到不确定是哪一份的 agent.md。
关联文档
- 27-REQ 原始需求 — 需求源头:用户原话照搬 + 需求→设计落点速查。其余文档都是对它的转述,出现"当初到底怎么说的"争议时以这份为准。
- 27-E2E 端到端任务执行流程 — 跨阶段总览:从建公司 → 下达任务 → org graph 逐节点执行 → work graph 规划与循环 → 验收收敛。建议先读这一份,它指出了本文档体系与已有 DevLoop 实现之间的对应关系、三处语义冲突和若干空白。
- 27-NAMING 命名规范 — 跨文档命名约定(
{作用域}-{职能})。新增 agent id / 表名 / 字段名前必读,避免与既有的benchmark/goal等命名撞车。 - 27-EXAMPLE 参考场景走查 — 把上面那套架构在一家具体公司上跑一遍数(含各阶段真实形态的 JSON / 图 / 成本估算 / 断点清单)。实现时的靶子与端到端验收场景。
- 27-P1 虚拟引用寻址 — 已实现(commit
226489d) - 27-P2 Org Graph 落地 — 设计稿
- 27-P3/P4 Work Graph 执行控制台 — 设计稿