Commit 2255f09c958c3592f1e4a58f9760f122bbd43d63

Authored by yibo
1 parent 74ea1ff8

feat(company): 新增 company-goal 字段;P2 需求审计的四个定案落入文档

起因:逐条比对 27-REQ 的原话与 P2 v0.4,发现四处偏离,其中两处是设计悄悄
改变了原意。本次修正并落定。

代码(P2 §五 T0):
- company.json 新增 goal 字段(company-goal),create({id,name,goal,description})
- POST /api/company/create 与 update() 同步支持
- 修的是一个实打实的遗漏:R-6 原话是"写入 company 的目标和描述"(两个字段),
  而 create() 一直只有 description。orggraph-planning 规划流水线的主依据本该是
  company-goal,此前根本拿不到。

文档定案(R-13):
- R-13.1 goal 三层递归:company-goal / task-goal / subagent-goal,与"验收逐层
  递归"对应。澄清 R-12 说的"唯一的那个 goal"其实是 task-goal;三个验收 agent
  保持通用不按层级拆分。记录一处待定:subagent-goal 按执行者命名,与另两个的
  作用域命名轴不一致(按轴应为 subtask-goal)。
- R-13.2 公司类型完全由 orggraph-planning 推断 —— 这条**推翻了我自己记的一个
  缺陷**:审计时我把"不加 type 枚举字段"记为擅自偏离原话,实际那正是所需,
  当前设计无需改动。
- R-13.3 新 agent 从 system/templates/base 元 agent 继承骨架(自动带四段式
  RESEARCH→PLAN→EXECUTE→VERIFY)+ agent-creator 填充,而非直接走 needs_training
  全量培养;新建一律 scope='company';innovator 训练是可选后置步骤。
  这是 R-9.2"所有 node agent 由元 agent 继承生成"在 P2 的落地点(新增 T5b)。
- R-13.4 org graph 强制单入口,原 open question 关闭。

顺带修掉 P2 的一处内部不一致:§六 承诺"提供手动触发 orggraph-planning 的端点"
给存量公司补图,但 §四.3 API 表和 §五 任务表都没有。已补 POST …/org-graph/replan。

另更正 R-11 中"代码层未执行"的过期表述——OKR 正名已于 74ea1ff 实施完毕。

回归:npm run test:unit 652 例全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/todo/system-design/27-naming-conventions.md
... ... @@ -23,8 +23,10 @@
23 23 | `orggraph` | 公司级 workflow |
24 24 | `workgraph` | org node 级 workflow |
25 25 | `{domain}` | 业务领域(`code` / `content` / `ops` …),即 org node 所属领域 |
26   -| `goal` | **一个人类需求描述**(唯一,验收边界)|
27   -| `task` | 图中的执行单元(一个 goal 分解出多个,可并行)|
  26 +| `company-goal` | **公司的目标**(建公司时写入,OKR 树的输入)|
  27 +| `task-goal` | **一次用户请求的目标**(唯一,验收边界;R-12 说的就是这一层)|
  28 +| `subagent-goal` | **子任务的目标**(一个 task-goal 分解出多个)|
  29 +| `task` | 图中的执行单元(一个 task-goal 分解出多个,可并行)|
28 30 | `agent` | 单个 system agent |
29 31 | `skill` | 单个 skill |
30 32  
... ... @@ -81,6 +83,22 @@ work graph 内动态选入的验收产出节点(见 [27-E2E](./27-e2e-task-exe
81 83  
82 84 验收标准回答的是"**人类想要的东西做到了没有**"——这是 goal 级的判定。task 只是达成它的执行分支,一个 goal 下有很多个;把验收挂在 task 上会导致"到底以哪个 task 的验收为准"根本无法回答。
83 85  
  86 +### goal 是三层递归的(R-13)
  87 +
  88 +「goal」本身在三个层级上各有一个,与 [27-E2E](./27-e2e-task-execution-flow.md) §11.3「验收逐层递归」严格对应——每一层的 goal 就是那一层的验收边界:
  89 +
  90 +| goal | 归属 | 谁验收它 | 落在哪 |
  91 +|---|---|---|---|
  92 +| **`company-goal`** | 公司 | OKR 树(长周期度量) | `company.json.goal`,建公司时写入 |
  93 +| **`task-goal`** | 一次用户请求 | org graph 末端的 `{domain}-validator` | 任务入口带入 |
  94 +| **`subagent-goal`** | 子任务 / 并行分支 | 该 work graph 内的 validator 节点 | 由 `workgraph-planning` 拆分时产生 |
  95 +
  96 +**`company-goal` 与 OKR 的关系**:`company-goal` 是建公司时的一句话**定性**目标,是 **OKR 树的输入**——OKR 把它拆成可度量的 Objective → Key Result。两者不是替代关系,不要用 OKR 节点去存 `company-goal`。
  97 +
  98 +**三个验收 agent(`goal-benchmark` / `goal-test-data` / `goal-test-script`)是通用的,不按层级拆分**:它们在哪一层被调用,就为那一层的 goal 产出验收标准。这与 §八bis 的元 agent 继承(全局通用本体 + 记忆分区)一致——不需要 `task-goal-benchmark` / `subagent-goal-benchmark` 这种按层级复制的 agent。
  99 +
  100 +> **一处命名轴不一致(待定,不阻塞)**:`company-goal` / `task-goal` 是按**作用域**命名,`subagent-goal` 是按**执行者**命名。按作用域应为 `subtask-goal`。当前按提出时的原词 `subagent-goal` 记录,若要统一改为 `subtask-goal`,全局换一个词即可。
  101 +
84 102 > **决策沿革**(完整过程见 [27-REQ](./27-requirement.md) R-10 / R-12):最初选 `task-`,理由是"`goal` 被 OKR 占用";§六 把 OKR 正名后该理由失效,改用"验收跟着一次 task 执行走"继续保留 `task-`;最终因上面的基数论证推翻,定为 `goal-`。
85 103  
86 104 ---
... ...
docs/todo/system-design/27-p2-org-graph.md
... ... @@ -98,7 +98,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_edges_dedupe_active
98 98 与 work graph 的执行前校验同一套规则([27-P3/P4](./27-p3-p4-work-graph-console.md) §四.A):
99 99  
100 100 1. **无环**(仅针对 `edge_kind='forward'`)—— 三色 DFS,复用 DevLoop `doc-store.js:167` 的 `hasCycle()` 实现。
101   -2. **单一入口** —— 必须恰好有一个入度为 0 的 forward 节点(任务从这里进入)。多入口会让"任务从哪开始"无法确定。
  101 +2. **单一入口**(✅ 已定案,见 [27-REQ](./27-requirement.md) R-13)—— 必须恰好有一个入度为 0 的 forward 节点,任务从这里进入。多入口会让"任务从哪开始"无法确定,且需要任务入口多带一个参数。
102 102 3. **可达性** —— 每个节点都必须从入口可达,孤立节点直接拒绝。
103 103 4. **回退边必须显式标注** —— 任何指向"拓扑序更早节点"的边,`edge_kind` 必须是 `backward`;引擎独立复核,不信任标注(27 号文档 §3.4 第 5 点的同款兜底)。
104 104  
... ... @@ -110,13 +110,18 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_org_graph_edges_dedupe_active
110 110  
111 111 新增 system agent 模板 `system/templates/orggraph-planning/`(`config.json.type` 取 `meta-bootstrap`,与业务角色区分)。工作流:
112 112  
113   -1. **读输入**:公司 `id` / `name` / `description`(由 §4.4 的首轮消息带入)。
  113 +1. **读输入**:公司 `id` / `name` / **`goal`(company-goal)** / `description`(由 §4.4 的首轮消息带入)。
  114 + `company-goal` 是「这家公司要达成什么」,`description` 是「这家公司是干什么的」——前者是规划流水线的主依据,后者是背景。见 [27-NAMING](./27-naming-conventions.md) §四。
114 115 2. **读候选骨架**:`ls system/org-graph-templates/`,逐个读取,理解各自的 `whenToUse`。
115 116 3. **读可招角色**:`node bin/lemon.js company templates`(复用现成 CLI,`listTemplates()` 返回 id/name/type/description)。
116 117 4. **规划**:综合三者,产出一张 workflow —— 节点列表 + 执行流边。允许裁剪、替换、组合多个模板,也允许判断需要一个模板库里没有的新角色。
117 118 5. **落盘规划记录**:`workspace/orggraph-plan.md`(图 + 为什么这么设计的理由),供事后审计。
118 119 6. **提交图**:调用 §4.3 的整体写入 API,图通过 §3.3 校验后落库。
119   -7. **执行招聘**:按拓扑序对每个节点调 `hire()`。模板不存在的角色走既有 `needs_training` 分支(自动触发 agent-creator + innovator,`company.js:917-940` 已实现)。
  120 +7. **执行招聘**:按拓扑序对每个节点调 `hire()`。
  121 + - **模板已存在** → 走 P1 虚拟引用寻址,不拷贝 `agent.md`。
  122 + - **模板不存在** → **从 `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` 全量培养分支。
  123 + - **新建 agent 的 `scope`**:一律默认 `scope='company'`、`owner_company_id` 为本公司(27 号文档 §3.2)。升级为 `public` 走既有的 benchmark ≥95 门槛,不在 P2 范围内。
  124 + - **训练是可选的后置步骤**:建成后可手动触发 `innovator` 培养,也可按公司需求配置为自动触发。P2 只保证「角色能被建出来且带四段式骨架」,不强制等训练完成。
120 125 8. **收尾**:关闭自己的心跳转入休眠。后续增量调整归 HR。
121 126  
122 127 `orggraph-planning` 自己**不出现在它规划的 org graph 里** —— 它是基础设施角色,类比 `agent-creator` / `innovator`。
... ... @@ -134,6 +139,7 @@ v0.2/v0.3 设计的 `hire(companyId, roleName, {reportsTo})` **不再需要**。
134 139 | `POST /api/company/:companyId/org-graph` | **整体写入**:接收 `{nodes:[], edges:[]}`,过 §3.3 四项校验,事务性落库。校验失败返回 400 + 具体哪条规则不满足(供 `orggraph-planning` 读错误后自行修正重试) |
135 140 | `PATCH /api/company/:companyId/org-graph` | **增量修改**:加/删节点、加/删边(后续 HR 调整用)。同样过校验——改完之后整张图仍须合法 |
136 141 | `GET /api/company/:companyId/org-graph` | **只读**:返回 `{nodes, edges}`,只含 `status='active'` 的行 |
  142 +| `POST /api/company/:companyId/org-graph/replan` | **重跑规划**:对已有公司手动再触发一次 `orggraph-planning`(§六 存量公司补图的入口)。异步,立即返回;agent 跑完后通过上面的 POST 写入新图 |
137 143  
138 144 `GET` 返回形状:
139 145  
... ... @@ -164,7 +170,7 @@ if (bootstrap.status === &#39;hired_from_template&#39;) {
164 170 fetch(`http://127.0.0.1:${PORT}/api/agent/orggraph-planning/chat`, {
165 171 method: 'POST', headers: { 'Content-Type': 'application/json' },
166 172 body: JSON.stringify({
167   - message: `公司 ${id}(${name})已创建。描述:${description || '(未填写)'}\n请按 orggraph-planning 工作流规划并建立这家公司的 workflow。`,
  173 + message: `公司 ${id}(${name})已创建。\n公司目标(company-goal):${goal || '(未填写)'}\n公司描述:${description || '(未填写)'}\n请按 orggraph-planning 工作流规划并建立这家公司的 workflow。`,
168 174 conversationId: `orggraph-bootstrap-${id}-${Date.now().toString(36)}`,
169 175 }),
170 176 }).catch(e => console.error('[Create] orggraph-planning 触发失败:', e.message));
... ... @@ -215,17 +221,19 @@ if (bootstrap.status === &#39;hired_from_template&#39;) {
215 221  
216 222 | # | 任务 | 文件 | 依赖 |
217 223 |---|---|---|---|
  224 +| **T0** | `company.json` 新增 `goal`(company-goal)字段;`create({id,name,goal,description})`;`POST /create` 与前端建公司表单同步 | `server/services/company-service.js`、`server/routes/company.js`、前端建公司表单 | 无 |
218 225 | **T1** | schema:节点加 `status` / `reports_to_node_id`;DROP 全量唯一索引改建部分索引;**DROP 重建 `org_graph_edges` 为执行流边** | `server/services/graph-db.js` | 无 |
219 226 | **T2** | `org-graph-service.js`:图的整体写入(含 §3.3 四项校验)、增量改、只读查询、节点软删除级联失效边 | `server/services/org-graph-service.js`(新增) | T1 |
220   -| **T3** | 三个 API 端点(POST / PATCH / GET) | `server/routes/company.js` | T2 |
  227 +| **T3** | 四个 API 端点(POST / PATCH / GET / POST …/replan) | `server/routes/company.js` | T2 |
221 228 | **T4** | `system/org-graph-templates/{dev,content,ops}.json` | 新增 | 无,可并行 |
222 229 | **T5** | `system/templates/orggraph-planning/` agent 本体(含 §4.1 八步工作流) | 新增 | T3, T4 |
  230 +| **T5b** | 「从 base 元 agent 继承 + agent-creator 填充」的招聘分支(§4.1 第 7 步),含新建 agent 的 `scope='company'` 落库 | `server/services/company-service.js`、`server/services/graph-service.js` | T1 |
223 231 | **T6** | `/create` 路由接入自动招聘 + 异步触发 | `server/routes/company.js` | T5 |
224 232 | **T7** | `/fire` 路由接入节点软删除(改路由 handler 本体,**不是** `CompanyService.fire()`——后者只有测试脚本在用,是死代码路径) | `server/routes/company.js` | T2 |
225 233 | **T8** | 前端:复用 P3 画布组件渲染 org graph(或先上纯文本拓扑序占位) | `web/src/views/CompanyDashboard.vue` | T3(+ P3 的 T3 若要画布) |
226 234 | **T9** | 测试:`org-graph-service.test.js`(四项校验各一条负例、幂等写入、软删除级联)+ e2e 扩展 | `server/tests/unit/org-graph-service.test.js` | T2, T3 |
227 235  
228   -**顺序**:T1→T2→T3 是主干。T4 可最早并行。T5 依赖 T3/T4。T7 独立于 T5/T6,可并行。T8 视是否等 P3 画布决定。
  236 +**顺序**:T0 独立可先做(它是 `orggraph-planning` 的输入前提)。T1→T2→T3 是主干。T4 可最早并行。T5 依赖 T3/T4。T7 独立于 T5/T6,可并行。T8 视是否等 P3 画布决定。
229 237  
230 238 ---
231 239  
... ...
docs/todo/system-design/27-requirement.md
... ... @@ -148,7 +148,7 @@
148 148  
149 149 **落点**:[27-NAMING](./27-naming-conventions.md) §六
150 150  
151   -**要点**:体系整体从 `goal` 改为 `okr`;且当前第 1 层节点也叫 "Goal" 是一处自我遮蔽,建议正名为 `Objective`(`O-001`),成为标准的 Objective → Key Result → Sub-Task 三层。代码层是真实数据迁移(9 条 API 路径 + `data/companies/{cid}/goals/` 磁盘目录 + `G-` ID 前缀写在已有 JSON 里),已给影响面清单,未执行。
  151 +**要点**:体系整体从 `goal` 改为 `okr`;且当前第 1 层节点也叫 "Goal" 是一处自我遮蔽,建议正名为 `Objective`(`O-001`),成为标准的 Objective → Key Result → Sub-Task 三层。代码层已于 commit `74ea1ff` **实施完毕**(`okr-service.js`/`routes/okr.js`、`/api/okr` + 旧路径别名、`scripts/migrate-goals-to-okr.js` 迁移脚本、7 例单测)。
152 152  
153 153 ---
154 154  
... ... @@ -170,6 +170,44 @@
170 170  
171 171 ---
172 172  
  173 +## R-13 · P2 需求审计中的四个定案
  174 +
  175 +针对「P2 是否兑现原话」的逐条比对,四个分叉的定案回答,逐条照搬。
  176 +
  177 +### R-13.1 · goal 分三层
  178 +
  179 +> 公司的目标是公司的 goal 叫做 company-goal ,任务的目标叫做 task-goal ,还有每一个子任务的目标叫做 subagent-goal,应该这么叫吧?
  180 +
  181 +**落点**:[27-NAMING](./27-naming-conventions.md) §四「goal 是三层递归的」、[27-P2](./27-p2-org-graph.md) §4.1 第 1 步
  182 +
  183 +**澄清了什么**:R-12 说的「一个人类需求描述只有一个 goal」,指的其实是三层里的 **`task-goal`**。三个验收 agent(`goal-benchmark` / `goal-test-data` / `goal-test-script`)保持通用,不按层级拆分——在哪一层调用就为那一层的 goal 产出标准,与 R-9.2 元 agent 继承一致。
  184 +
  185 +**待定(不阻塞)**:`subagent-goal` 是按执行者命名,另两个按作用域命名,轴不一致;按作用域应为 `subtask-goal`。当前按原词记录。
  186 +
  187 +### R-13.2 · 公司类型由 agent 推断
  188 +
  189 +> 完全由 orggraph-planning 推断
  190 +
  191 +**落点**:[27-P2](./27-p2-org-graph.md) §4.6(模板是起点集合,不是代码里的 `switch(type)`)
  192 +
  193 +**推翻了什么**:审计时我把「不给 company.json 加枚举 type 字段」记为「我擅自偏离了 R-3 原话(原话把类型列为输入)」。本条确认 LLM 推断正是所需,**当前设计无需修改**,那条自我记过的缺陷不成立。
  194 +
  195 +### R-13.3 · 新 agent 从元 agent 继承
  196 +
  197 +> 从 base 元 agent 继承 + agent-creator 填内容,建成后可以手动或者根据 company 的需求设置自动innovator 培养训练
  198 +
  199 +**落点**:[27-P2](./27-p2-org-graph.md) §4.1 第 7 步、§五 T5b
  200 +
  201 +**这是 R-9.2 在 P2 的落地点**:模板不存在的角色,先从 `system/templates/base/` 继承骨架(自动带上 `RESEARCH → PLAN → EXECUTE → VERIFY` 四段式),再由 `agent-creator` 填充角色定义——**不是**直接走现有的 `needs_training` 全量培养分支。训练是可选后置步骤,可手动触发或按公司需求配置自动。新建 agent 一律 `scope='company'`。
  202 +
  203 +### R-13.4 · org graph 强制单入口
  204 +
  205 +> 强制单入口(推荐)
  206 +
  207 +**落点**:[27-P2](./27-p2-org-graph.md) §3.3 第 2 条(原为 open question,现已定案)
  208 +
  209 +---
  210 +
173 211 ## 附 · 需求 → 设计落点 速查
174 212  
175 213 | 需求 | 一句话 | 主要落点 |
... ... @@ -186,3 +224,4 @@
186 224 | R-10 | 统一命名前缀 | NAMING 全文 |
187 225 | R-11 | OKR 正名,不叫 goal | NAMING §六 |
188 226 | R-12 | **验收产物定名 `goal-`**;goal 唯一、task 多个 | NAMING §四 |
  227 +| R-13 | goal 三层(company/task/subagent);类型由 agent 推断;新 agent 从 base 继承;强制单入口 | NAMING §四 / P2 §3.3 §4.1 §五 |
... ...
server/routes/company.js
... ... @@ -21,7 +21,7 @@ const scheduleService = new ScheduleService();
21 21 // POST /api/company/create
22 22 router.post('/create', async (req, res) => {
23 23 try {
24   - const { id, name, description } = req.body;
  24 + const { id, name, goal, description } = req.body;
25 25 if (!id || !name) return res.status(400).json({ error: 'id 和 name 为必填项' });
26 26  
27 27 // 校验 id 格式 (小写字母、数字、-)
... ... @@ -29,7 +29,7 @@ router.post(&#39;/create&#39;, async (req, res) =&gt; {
29 29 return res.status(400).json({ error: 'id 格式无效 (只允许小写字母、数字、-)' });
30 30 }
31 31  
32   - const company = companyService.create({ id, name, description });
  32 + const company = companyService.create({ id, name, goal, description });
33 33  
34 34 res.json({ success: true, company, message: '公司已创建,当前为没有员工的空壳公司。请通知 MasterBot 从 Template 库中雇佣 Agent 到你的团队中。' });
35 35 } catch (err) {
... ...
server/services/company-service.js
... ... @@ -33,9 +33,12 @@ class CompanyService {
33 33  
34 34 /**
35 35 * 创建一家新公司
36   - * @param {object} opts - { id, name, description }
  36 + * @param {object} opts - { id, name, goal, description }
  37 + * goal company-goal:这家公司要达成什么(定性,OKR 树的输入,orggraph-planning 规划流水线的主依据)
  38 + * description 这家公司是干什么的(背景)
  39 + * 两者的区分见 docs/todo/system-design/27-naming-conventions.md §四
37 40 */
38   - create({ id, name, description = '' }) {
  41 + create({ id, name, goal = '', description = '' }) {
39 42 const companyDir = path.join(DATA_DIR, id);
40 43 if (fs.existsSync(companyDir)) {
41 44 throw new Error(`公司 ${id} 已存在`);
... ... @@ -51,6 +54,7 @@ class CompanyService {
51 54 const company = {
52 55 id,
53 56 name,
  57 + goal,
54 58 description,
55 59 status: 'creating', // creating → running → paused → shutdown
56 60 createdAt: now,
... ... @@ -120,6 +124,7 @@ class CompanyService {
120 124 if (!company) return null;
121 125  
122 126 if (updates.name !== undefined) company.name = updates.name;
  127 + if (updates.goal !== undefined) company.goal = updates.goal;
123 128 if (updates.description !== undefined) company.description = updates.description;
124 129 if (updates.status !== undefined) {
125 130 company.status = updates.status;
... ...