From cd6f294016eb6e25c437406100b4a3f00f8bac10 Mon Sep 17 00:00:00 2001 From: yibo Date: Sun, 16 Aug 2026 21:17:21 +0800 Subject: [PATCH] docs: 已完成阶段文档归档到 done/,README 索引补全 --- docs/README.md | 16 +++++++++++++--- docs/done/system-design/27-p1-virtual-reference.md | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ docs/done/system-design/27-p2-org-graph.md | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ docs/todo/system-design/27-e2e-task-execution-flow.md | 12 ++++++------ docs/todo/system-design/27-example-dev-company-walkthrough.md | 10 +++++----- docs/todo/system-design/27-graph-agent-architecture.md | 12 ++++++------ docs/todo/system-design/27-p1-virtual-reference.md | 257 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- docs/todo/system-design/27-p2-org-graph.md | 307 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 8 files changed, 594 insertions(+), 584 deletions(-) create mode 100644 docs/done/system-design/27-p1-virtual-reference.md create mode 100644 docs/done/system-design/27-p2-org-graph.md delete mode 100644 docs/todo/system-design/27-p1-virtual-reference.md delete mode 100644 docs/todo/system-design/27-p2-org-graph.md diff --git a/docs/README.md b/docs/README.md index 093b5db..c7cc476 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ 判断依据:文档正文的显式「状态」标记(✅ 已完成 / 已实现 vs 📝 设计中 / 未实现)、未勾选的 checklist,以及与代码库实际实现的交叉核对(见各条目备注)。 -> **当前主线**:Graph Agent 架构重构([27-graph-agent-architecture.md](./todo/system-design/27-graph-agent-architecture.md))。`todo/` 下只保留这一份,其余待办已移入 `todo/future/`,避免和主线抢注意力。 +> **当前主线**:Graph Agent 架构重构([27-graph-agent-architecture.md](./todo/system-design/27-graph-agent-architecture.md))。该系列已完成的阶段文档(P1/P2)移入 `done/`,其余非主线待办在 `todo/future/`。 ## done/ @@ -21,14 +21,24 @@ | [qa_judge_req.md](./done/qa_judge_req.md) | QA Judge Agent 需求文档 — 已在 `system/templates/qa-judge/` 落地 | | [task_timeline_design.md](./done/task_timeline_design.md) | Task Timeline View 设计文档 — 已实现(`server/routes/tasks.js`、`server/services/task-service.js`、`web/src/views/TaskTimeline.vue`) | | [user-guides/08-base-agent-4-phase-guide.md](./done/user-guides/08-base-agent-4-phase-guide.md) | Base Agent 四阶段使用指南 | +| [system-design/27-p1-virtual-reference.md](./done/system-design/27-p1-virtual-reference.md) | P1 虚拟引用寻址 — ✅ 已实现(commit `226489d`,`server/services/graph-service.js`):`agent.md` 不再逐公司拷贝,改为 `config.json.systemAgentRef` 引用 + `company-context.md` 承载公司语境 | +| [system-design/27-p2-org-graph.md](./done/system-design/27-p2-org-graph.md) | P2 Org Graph — ✅ 已实现(`70a4727` + `f20d0d8`):org graph 定位为 company 级 workflow(执行流边),`orggraph-planning` agent 建公司时自动规划并招聘。真实建公司端到端验证通过 | | [system-design/01-26](./done/system-design/) | 系统设计正文 01~26 章(14、25-harness-engineering-improvements 例外,见 todo/future/;27 例外,见 todo/),各章节状态见文内「状态」标记 | ## todo/(当前主线) +Graph Agent 架构重构(27 系列)。**已完成的阶段文档已移入 `done/`**(P1、P2),下面是仍在推进或长期有效的部分。 + | 文件 | 说明 | |------|------| -| [system-design/27-graph-agent-architecture.md](./todo/system-design/27-graph-agent-architecture.md) | Graph Agent 架构,状态:📝 设计中,未实现。P0-P6 分阶段路线图,当前唯一在推进的重构 | -| [system-design/27-p1-virtual-reference.md](./todo/system-design/27-p1-virtual-reference.md) | P1(虚拟引用)细化需求与设计文档:`getAgentConfig()`/`PromptBuilder.build()`/`hire()` 的具体改法、验收标准、blast radius、未决问题 | +| [27-graph-agent-architecture.md](./todo/system-design/27-graph-agent-architecture.md) | **总纲**。P0-P6 分阶段路线图与整体架构。P0-P3+P4 已实现,P4(新)/P5/P6 待做 | +| [27-requirement.md](./todo/system-design/27-requirement.md) | **需求源头**。用户原话照搬(R-1..R-16)+ 需求→设计落点速查。其余文档都是对它的转述,出现分歧以此为准 | +| [27-naming-conventions.md](./todo/system-design/27-naming-conventions.md) | **命名规范**。`{作用域}-{职能}`;图节点按层级轴命名;节点名 ≠ 本体名;三种 benchmark 消歧;OKR 正名。新增 agent id / 表名 / 字段名前必读 | +| [27-e2e-task-execution-flow.md](./todo/system-design/27-e2e-task-execution-flow.md) | 端到端流程总览:建公司 → 下达任务 → org graph 逐节点执行 → work graph 规划与循环 → 验收收敛。含与 DevLoop 既有实现的对应关系分析 | +| [27-example-dev-company-walkthrough.md](./todo/system-design/27-example-dev-company-walkthrough.md) | 参考场景走查(虚构的「配图工厂」),把架构跑一遍具体的数:各阶段 JSON 形态、成本估算、断点清单 | +| [27-p3-p4-work-graph-console.md](./todo/system-design/27-p3-p4-work-graph-console.md) | P3+P4(原 P4 回退+Loop-Validator 已并入)图执行引擎与执行控制台 — ✅ 已实现(`c3abf4a`),文档仍在补充中 | +| [27-goal-contract-format.md](./todo/system-design/27-goal-contract-format.md) | 验收契约格式(`goal-benchmark` / `goal-test-data` / `goal-test-script` 的产物形态与跨节点传递) | +| [27-p4-parallel-and-artifacts.md](./todo/system-design/27-p4-parallel-and-artifacts.md) | **P4(回收编号)并行执行与产物索引** — 📝 设计中。fan-out 真并发 + 同 agent 多实例隔离 + `files.json` 产物索引。三个 Phase,C 可插队 | ## todo/future/(暂缓,非当前重心) diff --git a/docs/done/system-design/27-p1-virtual-reference.md b/docs/done/system-design/27-p1-virtual-reference.md new file mode 100644 index 0000000..98e755c --- /dev/null +++ b/docs/done/system-design/27-p1-virtual-reference.md @@ -0,0 +1,257 @@ +# 27-P1. 虚拟引用寻址 — 需求与设计文档 + +> **所属**: [27 Graph Agent 架构](../../todo/system-design/27-graph-agent-architecture.md) 的 Phase P1("虚拟引用") +> **版本**: v0.2(设计阶段,未实现;因 P0 完工同步更新) +> **日期**: 2026-08-15 +> **前置**: P0 地基已**全部完工**并提交(commit `8421f55`)——`seedGlobalAgents()`/`seedTemplates()`/`seedCompanyAgents()`/`verifyCounts()` 四步全部实现,`system_agents.legacy_path` 可用,`org_graph_nodes` 已经把**现有全部公司雇佣关系**回填完毕(不再是空表)。P0 实施过程中发现一个直接影响 P1 设计的问题:**`system/agents/` 与 `system/templates/` 存在 6 个同名但内容可能不同步的 id**,详见 [27 号文档 §7](../../todo/system-design/27-graph-agent-architecture.md#七p0-实施发现2026-08-15) 与下文 §4.6——本版本已针对这个问题修正了查表逻辑。 + +--- + +## 一、目标与非目标 + +### 目标(本阶段要交付什么) + +按 27 号文档 P1 行的原始定义: + +> 停止文件拷贝,改为引用。改造 `AgentManager.getAgentConfig()` + `PromptBuilder.build()` 支持 `(agentId, companyId)` 寻址;`company-service.hire()` 改为创建 org_graph 引用节点而非 `_copyTemplate()`。 + +经过对现有代码的走查(见第三节),这句话在真正落地时需要收窄——不是"完全停止拷贝",而是**只让 `agent.md` 这一份文件摆脱物理拷贝**,理由见第四节的关键约束分析。 + +### 非目标(本阶段明确不做) + +- **不做** `soul.md` / `identity.md` / `tools.md` / `.claude/skills/` 的去重——原因是运行时 CLI 进程按 `cwd` 相对路径读取它们(证据见 §3.3),贸然去重会在 runtime 直接读不到文件。这些文件的去重留给后续阶段(等这些模板文件内部的相对引用被统一改写为绝对共享路径之后再做)。 +- **不做** Org Graph 前端展示、`company.json.agents[]` 的废弃——`org_graph_nodes` 在 P1 只是**追加写入**,不替换任何现有读取路径。 +- **不做** `memory-service.js` / `heartbeat-service.js` / `task-service.js` / `agent-chat.js` 里重复的 `id.includes(':')` 寻址逻辑的统一收编——P1 刻意不碰这些文件,把 blast radius 限制在 `agent-manager.js` + `prompt-builder.js` + `company-service.js` 三个文件内(见第八节)。 +- **不做**存量公司数据迁移——旧公司(P1 上线前已 `hire()` 过的 agent)永远走原有的物理拷贝路径,不强制转换。 +- **不做** `system/agents/` 与 `system/templates/` 之间 6 个同名 id 的内容合并决策——这是 27 号文档 §7 抛出的产品问题(哪份文件才是权威版本),P1 只做到"绕开风险、不读错文件",不做"解决冲突"。见 §4.6。 + +--- + +## 二、背景:为什么只能先做 agent.md + +27 号文档 §1 列的四个问题里,P1 直接对应的是第 1 条"状态碎片化": + +> 同一角色在 10 家公司名下就有 10 份互不相干的 `agent.md`/`memory.md`。模板更新后,已雇佣的实例不会同步。 + +但 `memory.md` 是运行时持续写入的**私有**状态(每家公司的 Agent 记忆本来就该不同),不在去重范围内——27 号文档自己在 §3.2 也是这么定义 `scope=public` 的 `memory.md`(跨公司共享的是"能力"记忆,不是任意私有工作记忆)。真正符合"应该共享但目前被拷贝成 N 份"定义的,是**角色定义文件**:`agent.md`/`soul.md`/`identity.md`/`tools.md`。 + +P1 要解决的具体问题:Innovator 把某个角色的 `agent.md` evolve 到 v1.4,但已经雇佣过 v1.1 版本的 5 家公司,实例文件永远停在 v1.1,除非手动逐个覆盖。 + +--- + +## 三、现状代码走查(决定设计边界的关键证据) + +### 3.1 寻址入口:`AgentManager.getAgentConfig()` + +`server/src/core/agent-manager.js:124-202`。对 `"companyId:role"` 格式的地址,走的是"动态兜底"分支(第 131-191 行),不是 `this.config.agents` 静态配置——company-hired 的 agent **从来没有**进入过静态配置,每次都靠这个分支现场拼出 `agentConfig`: + +```js +agentConfig = { + type: 'orchestrator', protocol: 'claude-cli', ..., + prompt: path.join(agentDir, 'agent.md'), // ← P1 要改的就是这一行的来源 + workDir: agentDir, // ← 这一行必须保持不变(见 3.3) + ... +}; +``` + +拼出来的结果会缓存进 `this.config.agents[agentName]`(第 187-190 行),下一步 `PromptBuilder.build()` 直接读这个缓存,**不会**重新解析地址。这意味着 P1 的核心改动点只有一个:**这个分支怎么决定 `prompt` 字段指向哪。** + +### 3.2 `PromptBuilder.build()` 不需要感知寻址 + +`server/src/core/prompt-builder.js:130-177`。它读的是 `this.config.agents[agentName].prompt`,对这个路径来自哪里完全无感。也就是说:**只要 agent-manager.js 把 `prompt` 字段指对地方,PromptBuilder 本身理论上不用改——除非要把"公共 agent.md + 私有 company-context"拼成两段(见 3.4)。** + +### 3.3 关键约束:`workDir` 决定了哪些文件必须留在私有目录 + +`system/templates/qa-judge/agent.md:483-489`:"参考文档"一节用的是相对路径: + +``` +- `soul.md` - 判官人格与思维方式 +- `identity.md` - 身份信息与核心原则 +- `../../workspace/innovator/qa_judge_ideal_model.md` - 理想能力模型 +``` + +Claude CLI 子进程以 `workDir`(= `agentDir`,私有目录)为 `cwd` 启动,之后自己用文件工具按相对路径读取 `soul.md`。**如果这些文件不物理存在于私有目录,agent 运行时直接读不到。** 这就排除了"把整个 agent 目录指向共享位置"这个最简单的方案——那样会导致同一个共享目录被多家公司的 CLI 进程并发写入/读取,且 Claude CLI 自身的项目级状态(存在 `~/.claude/projects/<按路径 hash>/`)也会被多家公司共用同一路径而串数据。**结论:`workDir` 必须继续指向私有目录,不能改。** + +### 3.4 隐藏耦合:`agent.md` 今天不是纯共享文件,会被写脏 + +`server/services/company-service.js:687-704`(`_injectCompanyContext` 第 2 步):`hire()` 复制模板后,**直接把公司归属信息追加进复制出来的 `agent.md`**: + +```js +const companyContext = ['', '---', '', '## 所属公司', `- **公司ID**: ${companyId}`, ...].join('\n'); +fs.writeFileSync(agentMdPath, content + companyContext, 'utf-8'); +``` + +这意味着如果简单粗暴地让 `prompt` 直接指向共享 `legacy_path/agent.md`,会丢失这段"我是哪家公司的员工"的上下文——而这段上下文对 Agent 的行为是必需的(决定它向谁汇报、服务于什么使命)。**P1 必须把这段追加内容从"写死进文件"改成"运行时拼接"**,这正好对应 27 号文档 §3.6 说的"PromptBuilder 装配公共定义 + 私有语境"。 + +`user.md` 不受影响:它是**整份重写**(`company-service.js:707-729`),不是追加共享内容,本来就是纯私有文件,P1 不用动它。 + +--- + +## 四、设计方案 + +### 4.1 数据流总览 + +``` +hire(companyId, role) + │ + ├─ 查 system_agents 表: SELECT * WHERE id = role AND source_kind = 'template' + │ (注意:只匹配 'template',不匹配 'global_agent'——原因见 §4.6) + │ + ├─ 命中 + │ → 引用模式: + │ · 私有目录仍创建(config.json / soul.md / identity.md / tools.md / + │ heartbeat.md / plan.md / todo.md / cron.json / .claude/ / skills/ + │ 照今天原样拷贝 —— 不变) + │ · 不拷贝 agent.md + │ · config.json 新增字段 systemAgentRef = role + │ · 新写 company-context.md(取代原本追加进 agent.md 的那段内容) + │ · INSERT org_graph_nodes(company_id, system_agent_id, role_name) + │ (幂等,见 4.4 的唯一索引) + │ + └─ 未命中(system_agents 里没有 source_kind='template' 的这一行—— + 可能是新模板还没跑 seed,也可能是 §4.6 那 6 个命名冲突 id 之一) + → 回退到今天的 _copyTemplate() 全量拷贝(含 agent.md),行为零变化 + → console.warn 提示"运行 npm run graph:seed 以启用引用模式,或该 id 存在命名空间冲突见 27 号文档 §7" +``` + +### 4.2 `AgentManager.getAgentConfig()` 改动(agent-manager.js:131-191) + +在现有动态兜底分支里,`agentDir` 存在之后,新增一步:读 `agentDir/config.json` 的 `systemAgentRef` 字段。 + +```js +if (fs.existsSync(agentDir)) { + const config = readConfigIfExists(agentDir); // 已有的 _readAgentConfig 等价逻辑 + const ref = config?.systemAgentRef; + const sharedAgentMdPath = ref ? lookupSharedAgentMd(ref) : null; // 见 4.3 + + agentConfig = { + type: 'orchestrator', protocol: 'claude-cli', ..., + prompt: sharedAgentMdPath || path.join(agentDir, 'agent.md'), // 引用模式 vs legacy 二选一 + companyContextPath: ref ? path.join(agentDir, 'company-context.md') : null, // 新增,供 PromptBuilder 用 + workDir: agentDir, // 不变 + ... + }; +} +``` + +`lookupSharedAgentMd(systemAgentId)`: + +```sql +SELECT legacy_path FROM system_agents WHERE id = ? AND source_kind = 'template' +``` + +**`source_kind` 过滤是硬性要求,不是可选优化**——见 §4.6。查到行后拼 `path.join(ROOT, legacy_path, 'agent.md')`;查不到则整体走 legacy 全量拷贝分支。这个查询函数放在**新文件** `server/services/graph-service.js`(P1 自己的模块,不改动 P0 owner 的 `graph-db.js`/`graph-seed-service.js`,避免并行开发冲突),内部调用 `graph-db.js` 导出的 `getDb()`。 + +### 4.3 `PromptBuilder.build()` 改动(prompt-builder.js:130-177) + +`agentConfig.companyContextPath` 非空时,在 `getAgentPrompt()` 读完共享 `agent.md` 之后,再读一份 `company-context.md` 并作为独立 part 拼进去,位置对应今天"agent.md 正文 + 追加的所属公司段"的顺序: + +```js +if (isSkillBased) { + ... +} else { + ... + const agentPrompt = this.getAgentPrompt(agentName); // 现在可能来自共享路径 + if (agentPrompt) parts.push(agentPrompt); + if (agent.companyContextPath && fs.existsSync(agent.companyContextPath)) { + parts.push(this.loadFile(agent.companyContextPath)); // 新增 + } +} +``` + +对 legacy 拷贝模式的 agent(`companyContextPath` 为 `null`),这个分支不生效,行为完全不变。 + +### 4.4 `company-service.hire()` 改动(company-service.js:189-230, 621-648, 667-739) + +- `_copyTemplate()` 新增一个引用模式变体(暂命名 `_provisionReferenceAgent()`):复制 `TEMPLATE_FILES` 时跳过 `agent.md`,其余不变。 +- `_injectCompanyContext()` 引用模式下:第 2 步(追加进 agent.md)改成写一个新文件 `company-context.md`(内容就是原来那段 `## 所属公司` 文本 + `opts.customAgent`),不再碰 `agent.md`(因为它现在压根不在私有目录里)。第 1、3、4 步(config.json / user.md / soul.md 自定义)完全不变。 +- `hire()` 成功后,除了今天已有的 `company.agents.push(roleName)` + `_saveCompany()`(**继续保留,不替换**——很多地方还在读 `company.json.agents[]`),额外调用 `graph-service.upsertOrgGraphNode(companyId, roleName, roleName)`。这一步失败不应该让 `hire()` 整体失败(try/catch + warn),因为 P1 阶段 org_graph_nodes 只是审计记录,不是唯一真相源。 + +### 4.5 schema 微调:`org_graph_nodes` 唯一索引 + +现有 `graph-db.js`(P0 产物)的 `org_graph_nodes` 没有约束同一 `(company_id, role_name)` 不能重复插入。P1 需要在 `hire()` 幂等重跑时不产生重复行。这是**加法**,不改 P0 已有的表结构定义,用独立语句追加: + +```sql +CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role + ON org_graph_nodes(company_id, role_name); +``` + +放在 `graph-service.js` 里首次调用时执行(`CREATE ... IF NOT EXISTS` 幂等),不需要动 `graph-db.js`。加之前建议先跑一次 `SELECT company_id, role_name, COUNT(*) FROM org_graph_nodes GROUP BY 1,2 HAVING COUNT(*) > 1` 确认 `seedCompanyAgents()` 回填的历史数据里没有已存在的重复行(按其去重逻辑推断不应该有,但加唯一索引前验证一次成本很低)。 + +### 4.6 关键风险:`agent-id` 命名空间冲突(真实存在,不是假设) + +27 号文档 §7 记录了一个 P0 实施时才暴露的事实:`agent-creator` / `digester` / `evolver` / `evolver-youtube-crawler` / `hunter` / `innovator` 这 6 个 id,在 `system/agents/` 和 `system/templates/` 下**同时存在**,物理上是两份可能已经不同步的文件。`seedTemplates()` 遇到冲突时让 `global_agent` 行赢(跳过 template 行的写入),保证 `system_agents.id` 唯一性不被破坏——但这只是保护了数据库,没有回答"该读哪份文件"。 + +这不是纸上谈兵:`data/companies/idearun/agents/` 里已经真实雇佣了 `evolver-youtube-crawler`,正是冲突名单里的一个。而 `company-service.hire()` 的模板查找**永远只看 `TEMPLATES_DIR`**(`company-service.js:199`,`path.join(TEMPLATES_DIR, roleName)`),从来不看 `system/agents/`。 + +**如果 P1 的查表逻辑写成 `WHERE id = role`(不加 `source_kind` 过滤)**,对 `evolver-youtube-crawler` 这类 id 会查到 `global_agent` 行,`legacy_path` 指向 `system/agents/evolver-youtube-crawler/`——但 `idearun` 当初实际雇佣时拷贝的是 `system/templates/evolver-youtube-crawler/agent.md`。这两份文件如果内容不同,P1 上线后 `idearun` 的 `evolver-youtube-crawler` 在下一次调用时会突然读到一份**它从未被雇佣时使用过的 agent.md**——对用户来说是一次无法预期的、未经确认的角色定义漂移。 + +**修正**:§4.1/§4.2 已经把查表条件改成 `WHERE id = role AND source_kind = 'template'`。对这 6 个冲突 id,这个条件永远查不到行(因为 `seedTemplates()` 跳过了它们),于是自动落进"未命中 → legacy 全量拷贝"分支——即这 6 个 id 在 P1 阶段**天然被排除在引用模式之外**,不会有虚拟引用行为,直到 27 号文档 §7 提出的"该以哪份文件为准"这个产品问题被人工决定之后,才应该考虑把它们纳入。 + +--- + +## 五、与 P0(已完工)的衔接点 + +P0 已经把 `seedCompanyAgents()` 也做完了:`org_graph_nodes` 现在不是空表,已经为**所有现存公司雇佣关系**(含 `idearun` 的 6 个角色)各建了一行。这对 P1 意味着: + +- P1 的 `hire()` 改动(§4.4)在写入新的 `org_graph_nodes` 行时,不能假设自己是"第一个写入者"——历史数据已经在那,§4.5 的唯一索引就是为了让新写入和历史数据共存时不产生重复行。 +- P1 不需要(也不应该)对 `seedCompanyAgents()` 已经创建的 `source_kind='company_hired'` 行做任何处理——那些是"公司自己培养、没有对应模板"的角色,天然不满足 §4.1 的 `source_kind='template'` 命中条件,会正确地继续走 legacy 路径。 +- 唯一需要在 P1 动工前处理的衔接项就是 §4.6 的命名冲突排除逻辑——这一点已经通过查表条件的过滤自动满足,**不需要额外的迁移或人工介入**。 + +--- + +## 六、验收标准 + +沿用 27 号文档给出的验收标准,细化为可直接验证的检查项: + +1. 对一个已在 `system_agents` 表中的模板角色(如 `qa-judge`)执行 `hire(companyId, 'qa-judge')`: + - 私有目录下**没有** `agent.md` 文件 + - 私有目录下**有** `company-context.md`,内容包含公司 ID/名称/使命 + - `config.json` 含 `systemAgentRef: "qa-judge"` + - `org_graph_nodes` 表新增一行 `(companyId, 'qa-judge', 'qa-judge')` +2. 对该 agent 调用 `callAgent()`:`buildFullPrompt()` 产出的最终 prompt 中,agent.md 正文来自 `system/templates/qa-judge/agent.md`(与共享文件内容一致,改共享文件后无需重新 hire 即可生效),且包含 company-context.md 的所属公司段落。 +3. 对一个**不在** `system_agents` 表中的模板角色执行 `hire()`:退回到今天的行为(完整拷贝含 `agent.md`),并在日志里看到 reseed 提示。 +4. 找一家 P1 上线前已经 `hire()` 过的旧公司(纯目录拷贝,无 `systemAgentRef`):`callAgent()` 行为不变,`getAgentConfig()`/`PromptBuilder.build()` 都不进入新分支。 +5. 重复对同一 `(companyId, role)` 调用 `hire()`:第二次直接命中 `already_exists` 短路(这一步逻辑不变),`org_graph_nodes` 不产生重复行。 +6. **命名冲突回归用例**(对应 §4.6):用一家**新**测试公司(不是 `idearun`——它是 P1 上线前的存量雇佣,天然走 legacy 分支,测不出这个问题)对角色名 `evolver-youtube-crawler` 执行一次全新的 `hire()`。断言:`hire()` 内部的 `system_agents` 查表**查不到**行(因为唯一的 template 行被 seed 时跳过了),因此整个走 legacy 全量拷贝分支(含 `agent.md`),而不是误把 `global_agent` 行的 `legacy_path`(`system/agents/evolver-youtube-crawler/`)当成引用来源。这条用例失败就意味着 §4.6 的 `source_kind='template'` 过滤条件被写错了。 + +--- + +## 七、影响面 / Blast Radius + +P1 改动限定在三个文件 + 一个新文件: + +| 文件 | 改动类型 | +|------|----------| +| `server/src/core/agent-manager.js` | 修改:动态兜底分支的 `prompt` 来源判断 | +| `server/src/core/prompt-builder.js` | 修改:`build()` 增加 `companyContextPath` 拼接分支 | +| `server/services/company-service.js` | 修改:`hire()` / `_copyTemplate()` / `_injectCompanyContext()` 分叉出引用模式路径 | +| `server/services/graph-service.js` | 新增:`lookupSharedAgentMd()` / `upsertOrgGraphNode()` | + +**明确不改**,但依赖同一批私有目录路径的下游——之所以不改是因为引用模式下私有目录的物理位置、`config.json`、`memory/`、`.claude/` 全部保持原样,这些文件仍然照常存在: + +| 文件 | 为什么不受影响 | +|------|----------------| +| `server/services/memory-service.js` | 只读 `data/companies/{id}/agents/{role}/memory/`,P1 不改这个目录的存在 | +| `server/services/heartbeat-service.js` | 同上,读 `config.json`/`heartbeat.md`,两者仍照常私有存在 | +| `server/services/task-service.js` | 同上 | +| `server/routes/agent-chat.js` | 同上,且 `agentName.includes(':')` 的解析方式完全不变 | + +如果后续阶段要把 `soul.md`/`identity.md`/`tools.md` 也去重,才需要重新评估这四个文件。 + +--- + +## 八、测试计划 + +- 单元测试:`graph-service.js` 的 `upsertOrgGraphNode` 幂等性、`lookupSharedAgentMd` 对存在/不存在两种情况的返回。 +- 集成测试(复用 `server/scripts/test-company-e2e.js` 的模式):`hire()` 引用模式全流程 → `callAgent()` → 断言最终 prompt 内容分段正确。 +- 回归测试:跑一遍现有 `server/tests/**` 中涉及 `company-service.js` / `agent-manager.js` 的用例,确认 legacy 拷贝模式路径零回归。 + +--- + +## 九、Open Questions(留给实现阶段核实) + +1. **SKILL.md 模式的 agent**:`PromptBuilder.build()` 里 `isSkillBased`(`promptPath.endsWith('SKILL.md')`)分支目前直接把 SKILL.md 当唯一 prompt 来源,不叠加 protocol。当前梳理的模板样本(`qa-judge` 等)都是 `agent.md` 模式,没有验证 SKILL.md 模式模板是否也存在同样的"追加公司上下文"问题。实现前需要抽查 `system/templates/*` 里是不是有 `prompt` 指向 `.claude/skills/*/SKILL.md` 的模板。 +2. **`opts.customAgent` 的语义**:今天允许调用方在 `hire()` 时传一段自定义文本追加进 `agent.md`。P1 把它挪进 `company-context.md` 后,语义上从"追加进角色定义"变成"追加进公司专属上下文",需要确认现有调用方(如果有)不依赖它出现在 `agent.md` 本身的物理路径里。 +3. **共享 `agent.md` 被 Evolver 重写时的一致性**:P1 只解决"读"的问题(新 hire 读到最新版)。如果 Evolver 正在原地重写 `system/templates/{role}/agent.md` 的同时有 agent 正在被调用读取,会不会读到半写状态?这个问题在 P0 之前就存在(模板文件本来就会被 Evolver 直接改写),P1 没有让它变得更差,但既然现在读路径变得"更热"(被更多公司共享读取),值得在实现时评估要不要加一层读写锁或原子替换(写临时文件 + rename)。 +4. **6 个命名冲突 id 何时纳入引用模式**:§4.6 的过滤条件让 `agent-creator`/`digester`/`evolver`/`evolver-youtube-crawler`/`hunter`/`innovator` 这 6 个 id 在 P1 阶段永远走 legacy 分支。这是刻意的临时状态,不是长期方案——一旦 27 号文档 §7 提出的"哪份文件权威"问题有了产品决策,需要回来把这 6 个 id 的处理方式补上(可能是人工合并文件、也可能是确认两份本来就该是不同东西、重新命名去冲突)。这一项应该在排 P2(Org Graph)之前找时间处理掉,因为 P2 要让 CEO/HR agent 直接在 org graph 上招聘,到时候这 6 个 id 的歧义会更早暴露给用户。 diff --git a/docs/done/system-design/27-p2-org-graph.md b/docs/done/system-design/27-p2-org-graph.md new file mode 100644 index 0000000..5f31120 --- /dev/null +++ b/docs/done/system-design/27-p2-org-graph.md @@ -0,0 +1,307 @@ +# 27-P2. Org Graph — 公司级 Workflow 的生成与落地 + +> **所属**: [27 Graph Agent 架构](../../todo/system-design/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 命名规范](../../todo/system-design/27-naming-conventions.md)。v0.3 的 `org-graph-creator` 在本版本统一改名为 **`orggraph-planning`**。 +> **配套**: [27-E2E 端到端流程](../../todo/system-design/27-e2e-task-execution-flow.md)(本文档在整条链路里的位置)、[27-EXAMPLE 参考场景](../../todo/system-design/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(`org-planner` 等) | system agent 引用 | +| 边的语义 | **执行流**:`condition ∈ {success, fail}` | **执行流**:同左 | +| 执行引擎 | [27-P3/P4](../../todo/system-design/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](../../todo/system-design/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](../../todo/system-design/27-p3-p4-work-graph-console.md) §四.A): + +1. **无环**(仅针对 `edge_kind='forward'`)—— 三色 DFS,复用 DevLoop `doc-store.js:167` 的 `hasCycle()` 实现。 +2. **单一入口**(✅ 已定案,见 [27-REQ](../../todo/system-design/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](../../todo/system-design/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](../../todo/system-design/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": "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()` 之后追加: + +```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](../../todo/system-design/27-p3-p4-work-graph-console.md) 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 骨架: + +```jsonc +// 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](../../todo/system-design/27-naming-conventions.md) §3.1)。 + +`dev.json` 那两条 `fail` 边是 P3 的 `fail_reason` 细分路由([27-P3/P4](../../todo/system-design/27-p3-p4-work-graph-console.md) §四.D.2.2): +带 `failReason` 的边按归因结果精确匹配,不带 `failReason` 的那条是**兜底边**, +任何有 `fail` 出边的节点都必须留一条,否则归因产出图上没有的取值时会落到死路。 +`content.json` / `ops.json` 目前只有兜底边。 + +设计要点: + +- **是起点集合,不是穷举枚举** —— 加一种公司类型只需加一个 JSON 文件,不改任何代码。`orggraph-planning` 读全部模板后自行选型/裁剪/组合,不是代码里的 `switch(type)`。 +- **`nodes` 里的角色名是建议**,`orggraph-planning` 必须对照 `company templates` 核实是否真有可招模板;没有则走 `needs_training` 培养分支。 +- **模板里的角色名必须遵循 [27-NAMING](../../todo/system-design/27-naming-conventions.md) §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](../../todo/system-design/27-requirement.md) 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](../../todo/system-design/27-e2e-task-execution-flow.md) §三 阶段 2,具体跑一遍的样子见 [27-EXAMPLE](../../todo/system-design/27-example-dev-company-walkthrough.md) 阶段 2。 diff --git a/docs/todo/system-design/27-e2e-task-execution-flow.md b/docs/todo/system-design/27-e2e-task-execution-flow.md index 4612c63..5e10b2c 100644 --- a/docs/todo/system-design/27-e2e-task-execution-flow.md +++ b/docs/todo/system-design/27-e2e-task-execution-flow.md @@ -3,7 +3,7 @@ > **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的跨阶段总览文档,串起 P2(Org Graph)、P3/P4(Work Graph 执行)、P5(自我进化)。 > **版本**: v0.1 > **日期**: 2026-08-15 -> **前置**: [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`)。 +> **前置**: [27-P2 Org Graph 落地](../../done/system-design/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`)。 > **需求源头**: [27-REQ 原始需求](./27-requirement.md) —— 本文档是对那些原话的结构化转述,原文以那份为准。 > **配套**: [27-EXAMPLE 参考场景走查](./27-example-dev-company-walkthrough.md) —— 本文档讲机制,那份文档把机制在一家具体公司上跑一遍数。两份对照着读。 @@ -67,7 +67,7 @@ ### 阶段 2 · orggraph-planning 根据描述生成基础 org graph -**状态**:📐 已设计未实现([27-P2](./27-p2-org-graph.md) §4.8/§4.9/§4.10) +**状态**:📐 已设计未实现([27-P2](../../done/system-design/27-p2-org-graph.md) §4.8/§4.9/§4.10) P2 已经设计了:`system/org-graph-templates/{dev,content,ops}.json` 模板库 + `orggraph-planning` agent + 创建时自动招聘并 fire-and-forget 触发首轮对话。 @@ -177,7 +177,7 @@ P2 已经设计了:`system/org-graph-templates/{dev,content,ops}.json` 模板å > | **Org Graph** | orggraph-planning | 建公司时,一次 | org node agent | 执行流(success/fail 条件) | > | **Work Graph** | workgraph-planning agent | 每个 org node 接到任务时 | system agent 引用 | 执行流(success/fail 条件) | > -> **这意味着 [27-P2](./27-p2-org-graph.md) 现在的 org graph 模型需要改**:它把边定义成 `reports_to` / `collaborates_with`(静态汇报结构),而实际需要的是**带 success/fail 条件的执行流边**,与 work graph 的边同构。汇报关系如果还需要,应降级为节点上的元数据属性,而不是 org graph 的主干语义。 +> **这意味着 [27-P2](../../done/system-design/27-p2-org-graph.md) 现在的 org graph 模型需要改**:它把边定义成 `reports_to` / `collaborates_with`(静态汇报结构),而实际需要的是**带 success/fail 条件的执行流边**,与 work graph 的边同构。汇报关系如果还需要,应降级为节点上的元数据属性,而不是 org graph 的主干语义。 > > **好处**:一套 schema、一套执行引擎、一套控制台 UI,在两个层级上原样复用;三层下钻(org graph → work graph → 并行子图)天然递归;"启动/下一步/暂停/停止/重试"五个操作在每一层语义完全一致。 > @@ -356,7 +356,7 @@ DevLoop 就这个问题做过显式决策(`loops-l2.md` 方案 A/B/C),选æ **所有 org graph / work graph 的 node agent,都由一个元 agent 架构继承生成,统一是「全局通用本体 + 公司/角色记忆分区」的结构。** -这条定案取代了 v0.1 §四 里"三个验收 agent 要不要按父节点特化"这个问题——答案是**不特化**:不存在 `goal-benchmark` / `test-planner-benchmark` 各自独立的 agent,只有通用的 benchmark 设计能力,靠记忆分区区分它在不同 org node / 不同公司下的经验积累。agent 池不随 org node 数量膨胀,也与 [27-P1](./27-p1-virtual-reference.md) 虚拟引用寻址的初衷一致(同一份本体,多处引用,各自私有语境)。 +这条定案取代了 v0.1 §四 里"三个验收 agent 要不要按父节点特化"这个问题——答案是**不特化**:不存在 `goal-benchmark` / `test-planner-benchmark` 各自独立的 agent,只有通用的 benchmark 设计能力,靠记忆分区区分它在不同 org node / 不同公司下的经验积累。agent 池不随 org node 数量膨胀,也与 [27-P1](../../done/system-design/27-p1-virtual-reference.md) 虚拟引用寻址的初衷一致(同一份本体,多处引用,各自私有语境)。 ✅ **元 agent 在仓库里已经存在**:`system/templates/base/`("标准基座员工 Base Agent")。它已经强制了一套四段式生命周期: @@ -376,9 +376,9 @@ RESEARCH → PLAN → EXECUTE → VERIFY | # | 文档 | 原错误陈述 | 事实 | 修订结果 | |---|---|---|---|---| -| 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` 已存在,配套测试 `graph-service.test.js` / `agent-manager-p1.test.js` 均在 | **✅ 已改**。27-P2 文件头「前置」已写明「P1 虚拟引用寻址已实现(commit `226489d`)」;27-P3/P4 §九「与 P1」条写明「无直接依赖,已实现(commit `226489d`)」 | +| 1 | [27-P2](../../done/system-design/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` 已存在,配套测试 `graph-service.test.js` / `agent-manager-p1.test.js` 均在 | **✅ 已改**。27-P2 文件头「前置」已写明「P1 虚拟引用寻址已实现(commit `226489d`)」;27-P3/P4 §九「与 P1」条写明「无直接依赖,已实现(commit `226489d`)」 | | 2 | [27-P3/P4](./27-p3-p4-work-graph-console.md) §三.1 | "`runProject` 目前直接 501" | **已完整实现**(`loop-controller.js:576`),含 Kahn 分波、L2 闸门、Triager 四分支、预算熔断 | **✅ 已改**。27-P3/P4 §三.1 现为「`runProject()`(行 576,**已完整实现**…)」,并追加了「波内实际是串行」的补充观察 | -| 3 | [27-P2](./27-p2-org-graph.md) §4.1 | 提议建部分唯一索引 `idx_org_graph_nodes_company_role_active`(带 `WHERE status='active'`),但没说要先 DROP 旧索引 | **`graph-service.js` 已经建了全量唯一索引 `idx_org_graph_nodes_company_role`**(无 WHERE),必须先 DROP | **✅ 已改且已落地代码**。27-P2 §4.1 的 SQL 现以 `DROP INDEX IF EXISTS idx_org_graph_nodes_company_role;` 开头;`graph-db.js:59-60` 已实现这条 DROP + 重建 | +| 3 | [27-P2](../../done/system-design/27-p2-org-graph.md) §4.1 | 提议建部分唯一索引 `idx_org_graph_nodes_company_role_active`(带 `WHERE status='active'`),但没说要先 DROP 旧索引 | **`graph-service.js` 已经建了全量唯一索引 `idx_org_graph_nodes_company_role`**(无 WHERE),必须先 DROP | **✅ 已改且已落地代码**。27-P2 §4.1 的 SQL 现以 `DROP INDEX IF EXISTS idx_org_graph_nodes_company_role;` 开头;`graph-db.js:59-60` 已实现这条 DROP + 重建 | | 4 | [27-P3/P4](./27-p3-p4-work-graph-console.md) §四.B.2、§五 T3 | 把"手写分层布局算法,不引入图形库"当作**新**工作量 | `web/src/components/devloop/UnitsDAG.vue` **已实现** SVG + Kahn 分波布局 | **✅ 已改**。27-P3/P4 §三.5 新增「前端 DAG 已实现,扩展而非重写」一节;§四.B.2 与 §五 T3 均改为「**扩展 `UnitsDAG.vue`**」,并把新增工作量收窄到「subgraph 角标 + backward 曲线路由 + 循环徽标」三块 | ### 此外建议(复核结论) diff --git a/docs/todo/system-design/27-example-dev-company-walkthrough.md b/docs/todo/system-design/27-example-dev-company-walkthrough.md index 49a6c26..c7c743d 100644 --- a/docs/todo/system-design/27-example-dev-company-walkthrough.md +++ b/docs/todo/system-design/27-example-dev-company-walkthrough.md @@ -33,7 +33,7 @@ } ``` -**系统动作**([27-P2](./27-p2-org-graph.md) §4.10): +**系统动作**([27-P2](../../done/system-design/27-p2-org-graph.md) §4.10): 1. `companyService.create()` 建目录 + `company.json`(`agents: []`) 2. 🔴 自动 `hire(id, 'orggraph-planning', {})` @@ -48,7 +48,7 @@ **它读三样东西**: 1. 公司 description(上面那段) -2. `system/org-graph-templates/*.json`(候选骨架,[27-P2](./27-p2-org-graph.md) §4.8) +2. `system/org-graph-templates/*.json`(候选骨架,[27-P2](../../done/system-design/27-p2-org-graph.md) §4.8) 3. `node bin/lemon.js company templates`(现实里能招到的角色,`listTemplates()` 返回 id/name/type/description) **它的判断**:description 里有"做一个工具""跑在本地""CLI 版本"→ 匹配 `dev.json`。 @@ -76,7 +76,7 @@ `ceo` / `hr` 这类不参与任务执行的角色仍然可以被招聘进公司(存在于 `company.json.agents[]`),但**不出现在 org graph 这张 workflow 里**——org graph 只画"任务怎么流过这家公司"。 -**它提交图 + 执行招聘**(两步,[27-P2](./27-p2-org-graph.md) §4.1 第 6-7 步): +**它提交图 + 执行招聘**(两步,[27-P2](../../done/system-design/27-p2-org-graph.md) §4.1 第 6-7 步): ```bash # 1. 整体提交图定义,过四项校验(无环 / 单一入口 / 可达性 / 回退边标注)后事务性落库 @@ -255,10 +255,10 @@ brd-planning → prd-planning → sdd-planning ─┬→ goal-test-data ──â” | 1 | `qa-judge` 没有程序化调用入口(判决无法驱动任何自动动作) | 5, 6, 7 | [27-E2E](./27-e2e-task-execution-flow.md) §11.3 | | 2 | org-validator 节点的验收执行能力(跑 test_script / 喂 test_data)未实现 | 5, 7 | §11.3 | | 3 | 「用户 → 公司」任务入口不存在 | 3 | §三 阶段 3 | -| 4 | **P2 的 org graph 边模型需从 `reports_to` 改为 `success`/`fail` 执行流边** | 2, 4 | §五定案 + [27-P2](./27-p2-org-graph.md) v0.4 修正 | +| 4 | **P2 的 org graph 边模型需从 `reports_to` 改为 `success`/`fail` 执行流边** | 2, 4 | §五定案 + [27-P2](../../done/system-design/27-p2-org-graph.md) v0.4 修正 | | 5 | fan-out 并行撞 CONVENTIONS §12 不变量 + SessionPool key 复用 | 5, 6 | §7.4 | | 6 | 任务级总预算不存在 | 7 | §7.1 | | 7 | L4-general 的 agent dream 流程不存在(跨公司批量蒸馏层缺失,单点接口已齐) | 7 之后(离线) | §8.1 | -| 8 | orggraph-planning / workgraph-planning agent 均未实现 | 2, 5, 6 | [27-P2](./27-p2-org-graph.md) §4.9 | +| 8 | orggraph-planning / workgraph-planning agent 均未实现 | 2, 5, 6 | [27-P2](../../done/system-design/27-p2-org-graph.md) §4.9 | 第 4 项是**架构前提**,必须先改;改完之后做 1-3 项,这个场景就能在"串行、无 L4"的模式下端到端跑通一遍。 diff --git a/docs/todo/system-design/27-graph-agent-architecture.md b/docs/todo/system-design/27-graph-agent-architecture.md index e3afd09..ded6ccc 100644 --- a/docs/todo/system-design/27-graph-agent-architecture.md +++ b/docs/todo/system-design/27-graph-agent-architecture.md @@ -35,7 +35,7 @@ |------|------| | **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](./27-p2-org-graph.md) §一。 | +| **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](../../done/system-design/27-p2-org-graph.md) §一。 | | **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 强制终止。 | @@ -101,7 +101,7 @@ system/agents/{agentId}/ ### 3.3 Org Graph:company 级 workflow -> ⚠️ **v0.3 修正**:本节 v0.2 的内容("公司静态结构、边是汇报/协作关系、招聘时由 CEO 或 HR 维护")已作废。完整设计见 [27-P2 v0.4](./27-p2-org-graph.md)。 +> ⚠️ **v0.3 修正**:本节 v0.2 的内容("公司静态结构、边是汇报/协作关系、招聘时由 CEO 或 HR 维护")已作废。完整设计见 [27-P2 v0.4](../../done/system-design/27-p2-org-graph.md)。 - **节点**:对某个 org node agent 的引用(`org_graph_nodes`)。 - **边**:**执行流**(`org_graph_edges`:`condition ∈ {success, fail}`、`edge_kind ∈ {forward, backward}`),与 `work_graph_edges` 同构。 @@ -225,8 +225,8 @@ CREATE TABLE work_graph_edges ( | 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 虚拟引用寻址](./27-p1-virtual-reference.md) | 新雇佣的 agent 不再产生文件拷贝,调用时能正确拼装公共+私有上下文;旧公司(仍是拷贝模式)继续可跑,不强制迁移存量数据 | -| **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 并渲染(哪怕暂时按列表展示) | +| **P1 虚拟引用** | 停止文件拷贝,改为引用 | 改造 `AgentManager.getAgentConfig()` + `PromptBuilder.build()` 支持 `(agentId, companyId)` 寻址;`company-service.hire()` 改为创建 org_graph 引用节点而非 `_copyTemplate()`。细化需求与设计见 [27-P1 虚拟引用寻址](../../done/system-design/27-p1-virtual-reference.md) | 新雇佣的 agent 不再产生文件拷贝,调用时能正确拼装公共+私有上下文;旧公司(仍是拷贝模式)继续可跑,不强制迁移存量数据 | +| **P2 Org Graph** | `company.json.agents[]` 迁移为图结构 | `org_graph_nodes`/`edges` 落地;HR/CEO 招聘走新表。细化需求与设计见 [27-P2 Org Graph 落地](../../done/system-design/27-p2-org-graph.md) | 现有 Company Dashboard 前端能正确读出 org graph 并渲染(哪怕暂时按列表展示) | | **P3+P4 图执行引擎与执行控制台**(**两个 Phase 已合并**) | **一套引擎同时驱动 org graph 与 work graph**(R-15.4 引擎泛化),带回退与熔断 | 定义层 `work_graphs`/`work_graph_nodes`/`work_graph_edges` + **两层共用的运行时层** `graph_runs`/`graph_run_nodes`/`graph_run_loop_records`(共五张新表);`graph-engine.js` 的 `advanceOne(runId)` 幂等推进原语 + 两种 node executor(`agent_call` / `subgraph`);五个控制操作(启动 / 下一步 / 暂停 / 停止 / 重试)× 手动·自动两种模式;`GraphConsole.vue` 执行控制台页面;`workgraph-planning` agent + 用户→公司任务入口。细化需求与设计见 [27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md) **v0.3**,落地切成**五个可独立验收的阶段**(0 验收契约 / A 引擎地基 / B 回退熔断 / C 控制台 / D 规划闭环) | 见该文档 §六 的 17 条。**核心验收点是第 6 条**:一张 `graph_kind='org'` 的图与一张 work graph 用**同一套端点、同一条代码路径**跑通「手动逐步 / 自动跑完 / 暂停恢复 / 停止」;外加第 8 条「总是失败的节点第 4 次 `force_terminated` 收敛到 `blocked`」、第 17 条「一句自然语言任务两层跑到 `completed`」 | | ~~**P4 回退 + Loop-Validator**~~ | **已并入上一行**,不再单列 | 合并理由见 [27-P3/P4](./27-p3-p4-work-graph-console.md) §二:执行引擎一旦要支持「暂停/单步/停止/重试」这种可被外部随时打断的控制语义,「先做单向、再加回退」会让第一版状态机设计得过窄,P4 加回退时必然回来重改数据模型和主循环。**两处定案变更**:① 熔断改为**引擎无条件做的整数计数**(单边 `loop_count > 3` + run 级 `backward_count > 5` 双层硬顶),不依赖 LLM,`loop_validator` 节点由「强制」降级为**可选增强**;② 结构校验从四项扩到**五项**,新增 `fail-fallback-edge`(有 `fail` 出边的节点必须留一条 `fail_reason IS NULL` 兜底边),org / work 两层都加 | 同上 | | **P4 并行执行与产物索引**(**回收编号**) | fan-out 真并发 + 产物统一索引 | `advanceBatch` 批量拾取(手动模式退化为 `maxConcurrency=1`,行为与 P3 一致);同 agent 多实例隔离(启用 `SessionPool.getKey` 已预留但从未使用的 `scope` 参数);解除「一家公司一个 active root run」;`data/companies/{cid}/tasks/{taskId}/` 产物目录 + `files.json` 检索索引。细化设计见 [27-P4 并行执行与产物索引](./27-p4-parallel-and-artifacts.md) | 3 个并行分支同时 running 且总墙钟明显短于串行;部分失败时 fan-in 停等且可视化「在等谁」;`files.json` 可从节点行 rebuild | @@ -263,7 +263,7 @@ P0 阶段的处理方式是**迁移脚本层面的临时保护**:`system_agent - **[27-E2E 端到端任务执行流程](./27-e2e-task-execution-flow.md)** — 跨阶段总览:从建公司 → 下达任务 → org graph 逐节点执行 → work graph 规划与循环 → 验收收敛。**建议先读这一份**,它指出了本文档体系与已有 DevLoop 实现之间的对应关系、三处语义冲突和若干空白。 - **[27-NAMING 命名规范](./27-naming-conventions.md)** — 跨文档命名约定(`{作用域}-{职能}`)。新增 agent id / 表名 / 字段名前必读,避免与既有的 `benchmark` / `goal` 等命名撞车。 - **[27-EXAMPLE 参考场景走查](./27-example-dev-company-walkthrough.md)** — 把上面那套架构在一家具体公司上跑一遍数(含各阶段真实形态的 JSON / 图 / 成本估算 / 断点清单)。实现时的靶子与端到端验收场景。 -- [27-P1 虚拟引用寻址](./27-p1-virtual-reference.md) — 已实现(commit `226489d`) -- [27-P2 Org Graph 落地](./27-p2-org-graph.md) — 设计稿 +- [27-P1 虚拟引用寻址](../../done/system-design/27-p1-virtual-reference.md) — 已实现(commit `226489d`) +- [27-P2 Org Graph 落地](../../done/system-design/27-p2-org-graph.md) — 设计稿 - [27-P3/P4 Work Graph 执行控制台](./27-p3-p4-work-graph-console.md) — 已实现(commit `c3abf4a`) - [27-P4 并行执行与产物索引](./27-p4-parallel-and-artifacts.md) — 设计稿 diff --git a/docs/todo/system-design/27-p1-virtual-reference.md b/docs/todo/system-design/27-p1-virtual-reference.md deleted file mode 100644 index ba70675..0000000 --- a/docs/todo/system-design/27-p1-virtual-reference.md +++ /dev/null @@ -1,257 +0,0 @@ -# 27-P1. 虚拟引用寻址 — 需求与设计文档 - -> **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 Phase P1("虚拟引用") -> **版本**: v0.2(设计阶段,未实现;因 P0 完工同步更新) -> **日期**: 2026-08-15 -> **前置**: P0 地基已**全部完工**并提交(commit `8421f55`)——`seedGlobalAgents()`/`seedTemplates()`/`seedCompanyAgents()`/`verifyCounts()` 四步全部实现,`system_agents.legacy_path` 可用,`org_graph_nodes` 已经把**现有全部公司雇佣关系**回填完毕(不再是空表)。P0 实施过程中发现一个直接影响 P1 设计的问题:**`system/agents/` 与 `system/templates/` 存在 6 个同名但内容可能不同步的 id**,详见 [27 号文档 §7](./27-graph-agent-architecture.md#七p0-实施发现2026-08-15) 与下文 §4.6——本版本已针对这个问题修正了查表逻辑。 - ---- - -## 一、目标与非目标 - -### 目标(本阶段要交付什么) - -按 27 号文档 P1 行的原始定义: - -> 停止文件拷贝,改为引用。改造 `AgentManager.getAgentConfig()` + `PromptBuilder.build()` 支持 `(agentId, companyId)` 寻址;`company-service.hire()` 改为创建 org_graph 引用节点而非 `_copyTemplate()`。 - -经过对现有代码的走查(见第三节),这句话在真正落地时需要收窄——不是"完全停止拷贝",而是**只让 `agent.md` 这一份文件摆脱物理拷贝**,理由见第四节的关键约束分析。 - -### 非目标(本阶段明确不做) - -- **不做** `soul.md` / `identity.md` / `tools.md` / `.claude/skills/` 的去重——原因是运行时 CLI 进程按 `cwd` 相对路径读取它们(证据见 §3.3),贸然去重会在 runtime 直接读不到文件。这些文件的去重留给后续阶段(等这些模板文件内部的相对引用被统一改写为绝对共享路径之后再做)。 -- **不做** Org Graph 前端展示、`company.json.agents[]` 的废弃——`org_graph_nodes` 在 P1 只是**追加写入**,不替换任何现有读取路径。 -- **不做** `memory-service.js` / `heartbeat-service.js` / `task-service.js` / `agent-chat.js` 里重复的 `id.includes(':')` 寻址逻辑的统一收编——P1 刻意不碰这些文件,把 blast radius 限制在 `agent-manager.js` + `prompt-builder.js` + `company-service.js` 三个文件内(见第八节)。 -- **不做**存量公司数据迁移——旧公司(P1 上线前已 `hire()` 过的 agent)永远走原有的物理拷贝路径,不强制转换。 -- **不做** `system/agents/` 与 `system/templates/` 之间 6 个同名 id 的内容合并决策——这是 27 号文档 §7 抛出的产品问题(哪份文件才是权威版本),P1 只做到"绕开风险、不读错文件",不做"解决冲突"。见 §4.6。 - ---- - -## 二、背景:为什么只能先做 agent.md - -27 号文档 §1 列的四个问题里,P1 直接对应的是第 1 条"状态碎片化": - -> 同一角色在 10 家公司名下就有 10 份互不相干的 `agent.md`/`memory.md`。模板更新后,已雇佣的实例不会同步。 - -但 `memory.md` 是运行时持续写入的**私有**状态(每家公司的 Agent 记忆本来就该不同),不在去重范围内——27 号文档自己在 §3.2 也是这么定义 `scope=public` 的 `memory.md`(跨公司共享的是"能力"记忆,不是任意私有工作记忆)。真正符合"应该共享但目前被拷贝成 N 份"定义的,是**角色定义文件**:`agent.md`/`soul.md`/`identity.md`/`tools.md`。 - -P1 要解决的具体问题:Innovator 把某个角色的 `agent.md` evolve 到 v1.4,但已经雇佣过 v1.1 版本的 5 家公司,实例文件永远停在 v1.1,除非手动逐个覆盖。 - ---- - -## 三、现状代码走查(决定设计边界的关键证据) - -### 3.1 寻址入口:`AgentManager.getAgentConfig()` - -`server/src/core/agent-manager.js:124-202`。对 `"companyId:role"` 格式的地址,走的是"动态兜底"分支(第 131-191 行),不是 `this.config.agents` 静态配置——company-hired 的 agent **从来没有**进入过静态配置,每次都靠这个分支现场拼出 `agentConfig`: - -```js -agentConfig = { - type: 'orchestrator', protocol: 'claude-cli', ..., - prompt: path.join(agentDir, 'agent.md'), // ← P1 要改的就是这一行的来源 - workDir: agentDir, // ← 这一行必须保持不变(见 3.3) - ... -}; -``` - -拼出来的结果会缓存进 `this.config.agents[agentName]`(第 187-190 行),下一步 `PromptBuilder.build()` 直接读这个缓存,**不会**重新解析地址。这意味着 P1 的核心改动点只有一个:**这个分支怎么决定 `prompt` 字段指向哪。** - -### 3.2 `PromptBuilder.build()` 不需要感知寻址 - -`server/src/core/prompt-builder.js:130-177`。它读的是 `this.config.agents[agentName].prompt`,对这个路径来自哪里完全无感。也就是说:**只要 agent-manager.js 把 `prompt` 字段指对地方,PromptBuilder 本身理论上不用改——除非要把"公共 agent.md + 私有 company-context"拼成两段(见 3.4)。** - -### 3.3 关键约束:`workDir` 决定了哪些文件必须留在私有目录 - -`system/templates/qa-judge/agent.md:483-489`:"参考文档"一节用的是相对路径: - -``` -- `soul.md` - 判官人格与思维方式 -- `identity.md` - 身份信息与核心原则 -- `../../workspace/innovator/qa_judge_ideal_model.md` - 理想能力模型 -``` - -Claude CLI 子进程以 `workDir`(= `agentDir`,私有目录)为 `cwd` 启动,之后自己用文件工具按相对路径读取 `soul.md`。**如果这些文件不物理存在于私有目录,agent 运行时直接读不到。** 这就排除了"把整个 agent 目录指向共享位置"这个最简单的方案——那样会导致同一个共享目录被多家公司的 CLI 进程并发写入/读取,且 Claude CLI 自身的项目级状态(存在 `~/.claude/projects/<按路径 hash>/`)也会被多家公司共用同一路径而串数据。**结论:`workDir` 必须继续指向私有目录,不能改。** - -### 3.4 隐藏耦合:`agent.md` 今天不是纯共享文件,会被写脏 - -`server/services/company-service.js:687-704`(`_injectCompanyContext` 第 2 步):`hire()` 复制模板后,**直接把公司归属信息追加进复制出来的 `agent.md`**: - -```js -const companyContext = ['', '---', '', '## 所属公司', `- **公司ID**: ${companyId}`, ...].join('\n'); -fs.writeFileSync(agentMdPath, content + companyContext, 'utf-8'); -``` - -这意味着如果简单粗暴地让 `prompt` 直接指向共享 `legacy_path/agent.md`,会丢失这段"我是哪家公司的员工"的上下文——而这段上下文对 Agent 的行为是必需的(决定它向谁汇报、服务于什么使命)。**P1 必须把这段追加内容从"写死进文件"改成"运行时拼接"**,这正好对应 27 号文档 §3.6 说的"PromptBuilder 装配公共定义 + 私有语境"。 - -`user.md` 不受影响:它是**整份重写**(`company-service.js:707-729`),不是追加共享内容,本来就是纯私有文件,P1 不用动它。 - ---- - -## 四、设计方案 - -### 4.1 数据流总览 - -``` -hire(companyId, role) - │ - ├─ 查 system_agents 表: SELECT * WHERE id = role AND source_kind = 'template' - │ (注意:只匹配 'template',不匹配 'global_agent'——原因见 §4.6) - │ - ├─ 命中 - │ → 引用模式: - │ · 私有目录仍创建(config.json / soul.md / identity.md / tools.md / - │ heartbeat.md / plan.md / todo.md / cron.json / .claude/ / skills/ - │ 照今天原样拷贝 —— 不变) - │ · 不拷贝 agent.md - │ · config.json 新增字段 systemAgentRef = role - │ · 新写 company-context.md(取代原本追加进 agent.md 的那段内容) - │ · INSERT org_graph_nodes(company_id, system_agent_id, role_name) - │ (幂等,见 4.4 的唯一索引) - │ - └─ 未命中(system_agents 里没有 source_kind='template' 的这一行—— - 可能是新模板还没跑 seed,也可能是 §4.6 那 6 个命名冲突 id 之一) - → 回退到今天的 _copyTemplate() 全量拷贝(含 agent.md),行为零变化 - → console.warn 提示"运行 npm run graph:seed 以启用引用模式,或该 id 存在命名空间冲突见 27 号文档 §7" -``` - -### 4.2 `AgentManager.getAgentConfig()` 改动(agent-manager.js:131-191) - -在现有动态兜底分支里,`agentDir` 存在之后,新增一步:读 `agentDir/config.json` 的 `systemAgentRef` 字段。 - -```js -if (fs.existsSync(agentDir)) { - const config = readConfigIfExists(agentDir); // 已有的 _readAgentConfig 等价逻辑 - const ref = config?.systemAgentRef; - const sharedAgentMdPath = ref ? lookupSharedAgentMd(ref) : null; // 见 4.3 - - agentConfig = { - type: 'orchestrator', protocol: 'claude-cli', ..., - prompt: sharedAgentMdPath || path.join(agentDir, 'agent.md'), // 引用模式 vs legacy 二选一 - companyContextPath: ref ? path.join(agentDir, 'company-context.md') : null, // 新增,供 PromptBuilder 用 - workDir: agentDir, // 不变 - ... - }; -} -``` - -`lookupSharedAgentMd(systemAgentId)`: - -```sql -SELECT legacy_path FROM system_agents WHERE id = ? AND source_kind = 'template' -``` - -**`source_kind` 过滤是硬性要求,不是可选优化**——见 §4.6。查到行后拼 `path.join(ROOT, legacy_path, 'agent.md')`;查不到则整体走 legacy 全量拷贝分支。这个查询函数放在**新文件** `server/services/graph-service.js`(P1 自己的模块,不改动 P0 owner 的 `graph-db.js`/`graph-seed-service.js`,避免并行开发冲突),内部调用 `graph-db.js` 导出的 `getDb()`。 - -### 4.3 `PromptBuilder.build()` 改动(prompt-builder.js:130-177) - -`agentConfig.companyContextPath` 非空时,在 `getAgentPrompt()` 读完共享 `agent.md` 之后,再读一份 `company-context.md` 并作为独立 part 拼进去,位置对应今天"agent.md 正文 + 追加的所属公司段"的顺序: - -```js -if (isSkillBased) { - ... -} else { - ... - const agentPrompt = this.getAgentPrompt(agentName); // 现在可能来自共享路径 - if (agentPrompt) parts.push(agentPrompt); - if (agent.companyContextPath && fs.existsSync(agent.companyContextPath)) { - parts.push(this.loadFile(agent.companyContextPath)); // 新增 - } -} -``` - -对 legacy 拷贝模式的 agent(`companyContextPath` 为 `null`),这个分支不生效,行为完全不变。 - -### 4.4 `company-service.hire()` 改动(company-service.js:189-230, 621-648, 667-739) - -- `_copyTemplate()` 新增一个引用模式变体(暂命名 `_provisionReferenceAgent()`):复制 `TEMPLATE_FILES` 时跳过 `agent.md`,其余不变。 -- `_injectCompanyContext()` 引用模式下:第 2 步(追加进 agent.md)改成写一个新文件 `company-context.md`(内容就是原来那段 `## 所属公司` 文本 + `opts.customAgent`),不再碰 `agent.md`(因为它现在压根不在私有目录里)。第 1、3、4 步(config.json / user.md / soul.md 自定义)完全不变。 -- `hire()` 成功后,除了今天已有的 `company.agents.push(roleName)` + `_saveCompany()`(**继续保留,不替换**——很多地方还在读 `company.json.agents[]`),额外调用 `graph-service.upsertOrgGraphNode(companyId, roleName, roleName)`。这一步失败不应该让 `hire()` 整体失败(try/catch + warn),因为 P1 阶段 org_graph_nodes 只是审计记录,不是唯一真相源。 - -### 4.5 schema 微调:`org_graph_nodes` 唯一索引 - -现有 `graph-db.js`(P0 产物)的 `org_graph_nodes` 没有约束同一 `(company_id, role_name)` 不能重复插入。P1 需要在 `hire()` 幂等重跑时不产生重复行。这是**加法**,不改 P0 已有的表结构定义,用独立语句追加: - -```sql -CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_nodes_company_role - ON org_graph_nodes(company_id, role_name); -``` - -放在 `graph-service.js` 里首次调用时执行(`CREATE ... IF NOT EXISTS` 幂等),不需要动 `graph-db.js`。加之前建议先跑一次 `SELECT company_id, role_name, COUNT(*) FROM org_graph_nodes GROUP BY 1,2 HAVING COUNT(*) > 1` 确认 `seedCompanyAgents()` 回填的历史数据里没有已存在的重复行(按其去重逻辑推断不应该有,但加唯一索引前验证一次成本很低)。 - -### 4.6 关键风险:`agent-id` 命名空间冲突(真实存在,不是假设) - -27 号文档 §7 记录了一个 P0 实施时才暴露的事实:`agent-creator` / `digester` / `evolver` / `evolver-youtube-crawler` / `hunter` / `innovator` 这 6 个 id,在 `system/agents/` 和 `system/templates/` 下**同时存在**,物理上是两份可能已经不同步的文件。`seedTemplates()` 遇到冲突时让 `global_agent` 行赢(跳过 template 行的写入),保证 `system_agents.id` 唯一性不被破坏——但这只是保护了数据库,没有回答"该读哪份文件"。 - -这不是纸上谈兵:`data/companies/idearun/agents/` 里已经真实雇佣了 `evolver-youtube-crawler`,正是冲突名单里的一个。而 `company-service.hire()` 的模板查找**永远只看 `TEMPLATES_DIR`**(`company-service.js:199`,`path.join(TEMPLATES_DIR, roleName)`),从来不看 `system/agents/`。 - -**如果 P1 的查表逻辑写成 `WHERE id = role`(不加 `source_kind` 过滤)**,对 `evolver-youtube-crawler` 这类 id 会查到 `global_agent` 行,`legacy_path` 指向 `system/agents/evolver-youtube-crawler/`——但 `idearun` 当初实际雇佣时拷贝的是 `system/templates/evolver-youtube-crawler/agent.md`。这两份文件如果内容不同,P1 上线后 `idearun` 的 `evolver-youtube-crawler` 在下一次调用时会突然读到一份**它从未被雇佣时使用过的 agent.md**——对用户来说是一次无法预期的、未经确认的角色定义漂移。 - -**修正**:§4.1/§4.2 已经把查表条件改成 `WHERE id = role AND source_kind = 'template'`。对这 6 个冲突 id,这个条件永远查不到行(因为 `seedTemplates()` 跳过了它们),于是自动落进"未命中 → legacy 全量拷贝"分支——即这 6 个 id 在 P1 阶段**天然被排除在引用模式之外**,不会有虚拟引用行为,直到 27 号文档 §7 提出的"该以哪份文件为准"这个产品问题被人工决定之后,才应该考虑把它们纳入。 - ---- - -## 五、与 P0(已完工)的衔接点 - -P0 已经把 `seedCompanyAgents()` 也做完了:`org_graph_nodes` 现在不是空表,已经为**所有现存公司雇佣关系**(含 `idearun` 的 6 个角色)各建了一行。这对 P1 意味着: - -- P1 的 `hire()` 改动(§4.4)在写入新的 `org_graph_nodes` 行时,不能假设自己是"第一个写入者"——历史数据已经在那,§4.5 的唯一索引就是为了让新写入和历史数据共存时不产生重复行。 -- P1 不需要(也不应该)对 `seedCompanyAgents()` 已经创建的 `source_kind='company_hired'` 行做任何处理——那些是"公司自己培养、没有对应模板"的角色,天然不满足 §4.1 的 `source_kind='template'` 命中条件,会正确地继续走 legacy 路径。 -- 唯一需要在 P1 动工前处理的衔接项就是 §4.6 的命名冲突排除逻辑——这一点已经通过查表条件的过滤自动满足,**不需要额外的迁移或人工介入**。 - ---- - -## 六、验收标准 - -沿用 27 号文档给出的验收标准,细化为可直接验证的检查项: - -1. 对一个已在 `system_agents` 表中的模板角色(如 `qa-judge`)执行 `hire(companyId, 'qa-judge')`: - - 私有目录下**没有** `agent.md` 文件 - - 私有目录下**有** `company-context.md`,内容包含公司 ID/名称/使命 - - `config.json` 含 `systemAgentRef: "qa-judge"` - - `org_graph_nodes` 表新增一行 `(companyId, 'qa-judge', 'qa-judge')` -2. 对该 agent 调用 `callAgent()`:`buildFullPrompt()` 产出的最终 prompt 中,agent.md 正文来自 `system/templates/qa-judge/agent.md`(与共享文件内容一致,改共享文件后无需重新 hire 即可生效),且包含 company-context.md 的所属公司段落。 -3. 对一个**不在** `system_agents` 表中的模板角色执行 `hire()`:退回到今天的行为(完整拷贝含 `agent.md`),并在日志里看到 reseed 提示。 -4. 找一家 P1 上线前已经 `hire()` 过的旧公司(纯目录拷贝,无 `systemAgentRef`):`callAgent()` 行为不变,`getAgentConfig()`/`PromptBuilder.build()` 都不进入新分支。 -5. 重复对同一 `(companyId, role)` 调用 `hire()`:第二次直接命中 `already_exists` 短路(这一步逻辑不变),`org_graph_nodes` 不产生重复行。 -6. **命名冲突回归用例**(对应 §4.6):用一家**新**测试公司(不是 `idearun`——它是 P1 上线前的存量雇佣,天然走 legacy 分支,测不出这个问题)对角色名 `evolver-youtube-crawler` 执行一次全新的 `hire()`。断言:`hire()` 内部的 `system_agents` 查表**查不到**行(因为唯一的 template 行被 seed 时跳过了),因此整个走 legacy 全量拷贝分支(含 `agent.md`),而不是误把 `global_agent` 行的 `legacy_path`(`system/agents/evolver-youtube-crawler/`)当成引用来源。这条用例失败就意味着 §4.6 的 `source_kind='template'` 过滤条件被写错了。 - ---- - -## 七、影响面 / Blast Radius - -P1 改动限定在三个文件 + 一个新文件: - -| 文件 | 改动类型 | -|------|----------| -| `server/src/core/agent-manager.js` | 修改:动态兜底分支的 `prompt` 来源判断 | -| `server/src/core/prompt-builder.js` | 修改:`build()` 增加 `companyContextPath` 拼接分支 | -| `server/services/company-service.js` | 修改:`hire()` / `_copyTemplate()` / `_injectCompanyContext()` 分叉出引用模式路径 | -| `server/services/graph-service.js` | 新增:`lookupSharedAgentMd()` / `upsertOrgGraphNode()` | - -**明确不改**,但依赖同一批私有目录路径的下游——之所以不改是因为引用模式下私有目录的物理位置、`config.json`、`memory/`、`.claude/` 全部保持原样,这些文件仍然照常存在: - -| 文件 | 为什么不受影响 | -|------|----------------| -| `server/services/memory-service.js` | 只读 `data/companies/{id}/agents/{role}/memory/`,P1 不改这个目录的存在 | -| `server/services/heartbeat-service.js` | 同上,读 `config.json`/`heartbeat.md`,两者仍照常私有存在 | -| `server/services/task-service.js` | 同上 | -| `server/routes/agent-chat.js` | 同上,且 `agentName.includes(':')` 的解析方式完全不变 | - -如果后续阶段要把 `soul.md`/`identity.md`/`tools.md` 也去重,才需要重新评估这四个文件。 - ---- - -## 八、测试计划 - -- 单元测试:`graph-service.js` 的 `upsertOrgGraphNode` 幂等性、`lookupSharedAgentMd` 对存在/不存在两种情况的返回。 -- 集成测试(复用 `server/scripts/test-company-e2e.js` 的模式):`hire()` 引用模式全流程 → `callAgent()` → 断言最终 prompt 内容分段正确。 -- 回归测试:跑一遍现有 `server/tests/**` 中涉及 `company-service.js` / `agent-manager.js` 的用例,确认 legacy 拷贝模式路径零回归。 - ---- - -## 九、Open Questions(留给实现阶段核实) - -1. **SKILL.md 模式的 agent**:`PromptBuilder.build()` 里 `isSkillBased`(`promptPath.endsWith('SKILL.md')`)分支目前直接把 SKILL.md 当唯一 prompt 来源,不叠加 protocol。当前梳理的模板样本(`qa-judge` 等)都是 `agent.md` 模式,没有验证 SKILL.md 模式模板是否也存在同样的"追加公司上下文"问题。实现前需要抽查 `system/templates/*` 里是不是有 `prompt` 指向 `.claude/skills/*/SKILL.md` 的模板。 -2. **`opts.customAgent` 的语义**:今天允许调用方在 `hire()` 时传一段自定义文本追加进 `agent.md`。P1 把它挪进 `company-context.md` 后,语义上从"追加进角色定义"变成"追加进公司专属上下文",需要确认现有调用方(如果有)不依赖它出现在 `agent.md` 本身的物理路径里。 -3. **共享 `agent.md` 被 Evolver 重写时的一致性**:P1 只解决"读"的问题(新 hire 读到最新版)。如果 Evolver 正在原地重写 `system/templates/{role}/agent.md` 的同时有 agent 正在被调用读取,会不会读到半写状态?这个问题在 P0 之前就存在(模板文件本来就会被 Evolver 直接改写),P1 没有让它变得更差,但既然现在读路径变得"更热"(被更多公司共享读取),值得在实现时评估要不要加一层读写锁或原子替换(写临时文件 + rename)。 -4. **6 个命名冲突 id 何时纳入引用模式**:§4.6 的过滤条件让 `agent-creator`/`digester`/`evolver`/`evolver-youtube-crawler`/`hunter`/`innovator` 这 6 个 id 在 P1 阶段永远走 legacy 分支。这是刻意的临时状态,不是长期方案——一旦 27 号文档 §7 提出的"哪份文件权威"问题有了产品决策,需要回来把这 6 个 id 的处理方式补上(可能是人工合并文件、也可能是确认两份本来就该是不同东西、重新命名去冲突)。这一项应该在排 P2(Org Graph)之前找时间处理掉,因为 P2 要让 CEO/HR agent 直接在 org graph 上招聘,到时候这 6 个 id 的歧义会更早暴露给用户。 diff --git a/docs/todo/system-design/27-p2-org-graph.md b/docs/todo/system-design/27-p2-org-graph.md deleted file mode 100644 index 86825c9..0000000 --- a/docs/todo/system-design/27-p2-org-graph.md +++ /dev/null @@ -1,307 +0,0 @@ -# 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(`org-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": "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()` 之后追加: - -```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 用一个纯文本的拓扑序列表占位(`org-planner → org-coder → org-validator`),等 P3 画布就绪再替换。 - -### 4.6 `system/org-graph-templates/` - -每个文件是一种公司类型的 workflow 骨架: - -```jsonc -// 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](./27-naming-conventions.md) §3.1)。 - -`dev.json` 那两条 `fail` 边是 P3 的 `fail_reason` 细分路由([27-P3/P4](./27-p3-p4-work-graph-console.md) §四.D.2.2): -带 `failReason` 的边按归因结果精确匹配,不带 `failReason` 的那条是**兜底边**, -任何有 `fail` 出边的节点都必须留一条,否则归因产出图上没有的取值时会落到死路。 -`content.json` / `ops.json` 目前只有兜底边。 - -设计要点: - -- **是起点集合,不是穷举枚举** —— 加一种公司类型只需加一个 JSON 文件,不改任何代码。`orggraph-planning` 读全部模板后自行选型/裁剪/组合,不是代码里的 `switch(type)`。 -- **`nodes` 里的角色名是建议**,`orggraph-planning` 必须对照 `company templates` 核实是否真有可招模板;没有则走 `needs_training` 培养分支。 -- **模板里的角色名必须遵循 [27-NAMING](./27-naming-conventions.md) §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](./27-requirement.md) 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](./27-e2e-task-execution-flow.md) §三 阶段 2,具体跑一遍的样子见 [27-EXAMPLE](./27-example-dev-company-walkthrough.md) 阶段 2。 -- libgit2 0.21.4