Commit 3353b17492604fc8c67ee9f4aaf5eed3af60b9a1
1 parent
71cf45e1
docs(27-P3C): §十六 批 5 收尾 —— 五批全部实施完毕
记三件这一批换来的东西: §16.5 第 3 步怎么做到不碰契约:dispatch 的九步副作用里只有第 ⑧ 步是存储, 把 saveTaskMeta/loadTaskMeta 变成表的适配器、其余八步一行不动,URL 与请求体 形态天然不可能变。字段落点的三个判断也记了(objectiveId 可推导所以不存、 sourceConvId 零读取方所以删、没选 JSON blob 因为 R-19.2 否过那个形态)。 §16.6 删掉的是「猜」不是「记录」:122 行的三策略启发式换成 parent_task_id 显式记录;getTaskTimeline 保留,树从表来、正文仍读 agent 的一手会话记录。 §16.7 两次「测试全绿 ≠ 没坏」:routes/tasks.js 漏改(没有测试走那条路)、 控制台栏宽(构建/单测/探针都不会报)。与 §16.2 那个「存量库一启动就炸」 (测试能覆盖但没人写)合起来是一条经验 —— 新建库的路径、纯逻辑的路径由单测 天然覆盖;「存量数据」「真实接口」「肉眼可见」这三类必须另外安排人走一遍。 §16.8 留了三条:tasklog 目录改名待做、tools-base.md 的端口仍是 3000(实际 3010,已真咬过人)、TaskTimeline.vue 的两处字段语义变了。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
1 changed file
with
79 additions
and
6 deletions
docs/todo/system-design/27-p3c-goal-task-model.md
| @@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
| 2 | 2 | ||
| 3 | > **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 **P3 收尾**(P3C),直接续 [27-P3/P4 执行控制台](./27-p3-p4-work-graph-console.md) §四.B 与 [27-P3B 真跑跑道](./27-p3b-real-run-runway.md)。 | 3 | > **所属**: [27 Graph Agent 架构](./27-graph-agent-architecture.md) 的 **P3 收尾**(P3C),直接续 [27-P3/P4 执行控制台](./27-p3-p4-work-graph-console.md) §四.B 与 [27-P3B 真跑跑道](./27-p3b-real-run-runway.md)。 |
| 4 | > **日期**: 2026-08-18 | 4 | > **日期**: 2026-08-18 |
| 5 | -> **状态**: **批 1-4 已实施并全绿**(§十三 / §十四 / §十五)。**批 5 进行中**(§十六:第 1-2 步已落地,第 3-6 步未做)。开放问题已全部关闭。 | 5 | +> **状态**: **五批全部实施完毕并全绿**(§十三 ~ §十六)。132 文件 / 1257 测试,`web:build` 通过。开放问题已全部关闭;后续项见 §15.5 与 §16.8。 |
| 6 | > **⚠️ 批次与 phase 的对应不是一一对齐的**,判断「这份能不能搬进 done」时按批看,别按文档名看: | 6 | > **⚠️ 批次与 phase 的对应不是一一对齐的**,判断「这份能不能搬进 done」时按批看,别按文档名看: |
| 7 | > · **批 1-3 = 真正的 P3 收尾**(goal/task 两级模型 + `task-planning` + 排队),本文档的主体; | 7 | > · **批 1-3 = 真正的 P3 收尾**(goal/task 两级模型 + `task-planning` + 排队),本文档的主体; |
| 8 | > · **批 4(并行)归 [27-P4](./27-p4-parallel-and-artifacts.md) §一.3**,那边是设计权威,本文档只服从(见 §五.2 的警示块); | 8 | > · **批 4(并行)归 [27-P4](./27-p4-parallel-and-artifacts.md) §一.3**,那边是设计权威,本文档只服从(见 §五.2 的警示块); |
| @@ -1078,12 +1078,12 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次 | @@ -1078,12 +1078,12 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次 | ||
| 1078 | |---|---|---| | 1078 | |---|---|---| |
| 1079 | | 1 | `tasks` 表吸收 `executor` / `assignee_agent_id` / `parent_task_id` | ✅ `5cd91c3` | | 1079 | | 1 | `tasks` 表吸收 `executor` / `assignee_agent_id` / `parent_task_id` | ✅ `5cd91c3` | |
| 1080 | | 2 | `system/tasklog/*.json` 回填进 `tasks` 表 | ✅ `514ec3d` | | 1080 | | 2 | `system/tasklog/*.json` 回填进 `tasks` 表 | ✅ `514ec3d` | |
| 1081 | -| 3 | `/task/dispatch` 改写 `tasks` 表(**URL 与请求体一个字不动**) | ⏳ 未做 | | ||
| 1082 | -| 4 | 30 分钟去重从「扫目录」改成一条 SQL | ⏳ 未做 | | ||
| 1083 | -| 5 | `GET /api/tasks` 改读表;删 `TaskService.getAggregatedTasks()` 的会话反推 | ⏳ 未做 | | ||
| 1084 | -| 6 | 调度器只管 `executor='graph'` | ⏳ 未做 | | 1081 | +| 3 | `/task/dispatch` 改写 `tasks` 表(**URL 与请求体一个字不动**) | ✅ `12880b8` | |
| 1082 | +| 4 | 30 分钟去重从「扫目录」改成一条 SQL | ✅ 同上 | | ||
| 1083 | +| 5 | `GET /api/tasks` 改读表;删 `TaskService.getAggregatedTasks()` 的会话反推 | ✅ `71cf45e` | | ||
| 1084 | +| 6 | 调度器只管 `executor='graph'` | ✅ 同上 | | ||
| 1085 | 1085 | ||
| 1086 | -全量:**131 文件 / 1251 测试全绿**。 | 1086 | +**批 5 完成,P3C 五批全部实施完毕。** 全量:**132 文件 / 1257 测试全绿**。 |
| 1087 | 1087 | ||
| 1088 | ### 16.2 第 1 步踩到的:一个会让**所有存量库开不了**的顺序问题 | 1088 | ### 16.2 第 1 步踩到的:一个会让**所有存量库开不了**的顺序问题 |
| 1089 | 1089 | ||
| @@ -1126,3 +1126,76 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次 | @@ -1126,3 +1126,76 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次 | ||
| 1126 | > 顺带记一处过期:`tools-base.md` 里印的是 `http://localhost:3000`,而实际端口是 **3010** | 1126 | > 顺带记一处过期:`tools-base.md` 里印的是 `http://localhost:3000`,而实际端口是 **3010** |
| 1127 | > (`server/config.js`)。已经真咬过人 —— 回填出来的派单正文里有人手写了「端口 3010,不是 3000」 | 1127 | > (`server/config.js`)。已经真咬过人 —— 回填出来的派单正文里有人手写了「端口 3010,不是 3000」 |
| 1128 | > 来纠正它。这一处该在第 3 步一起改(改的是端口不是契约形态)。 | 1128 | > 来纠正它。这一处该在第 3 步一起改(改的是端口不是契约形态)。 |
| 1129 | + | ||
| 1130 | +### 16.5 第 3 步(最危险的一步)怎么做到不碰契约 | ||
| 1131 | + | ||
| 1132 | +`/task/dispatch` 的副作用有九步,**只有第 ⑧ 步是「存储」**: | ||
| 1133 | + | ||
| 1134 | +``` | ||
| 1135 | +① 校验 from/to/task + 两个本体目录存在 | ||
| 1136 | +② 去重(30 分钟内同接收方 + 正文互相包含) | ||
| 1137 | +③ 推断 parentTaskId | ||
| 1138 | +④ 正文净化(\n → <br>,| → |) | ||
| 1139 | +⑤ 写目标 agent 的 todo.md | ||
| 1140 | +⑥ 写发送者的 plan.md | ||
| 1141 | +⑦ 更新 OKR 节点 → in_progress | ||
| 1142 | +⑧ saveTaskMeta(taskId, {...}) ← 只换这一步的内脏 | ||
| 1143 | +⑨ autoExecute → POST /api/agent/{to}/chat | ||
| 1144 | +``` | ||
| 1145 | + | ||
| 1146 | +**把 `saveTaskMeta` / `loadTaskMeta` 变成 tasks 表的适配器**,其余八步一行不动 —— | ||
| 1147 | +URL 与请求体形态天然不可能变。②③ 顺带从「扫目录」改成带索引的 SQL,**判据一个字没改**, | ||
| 1148 | +换的只是从哪儿把候选捞出来。 | ||
| 1149 | + | ||
| 1150 | +字段落点的三个判断: | ||
| 1151 | +- **`objectiveId` 不单独存**:它恒等于 okr node id 的首段(`OkrService.parseNodeId` 与 | ||
| 1152 | + `reportRunOutcome` 同一口径),且两处读取方都是 `if (objId && meta.nodeId)` —— 从不单独用。 | ||
| 1153 | +- **`sourceConvId` 删掉**:全仓零读取方。正是本文档 §九.4quater 立过案的「实现在、零调用方」, | ||
| 1154 | + 不往新表里再搬一份。 | ||
| 1155 | +- **`summary` / `execution_conv_id` 加列**(用户定案 A):前者当前只写不读但语义清晰、界面迟早要它; | ||
| 1156 | + 后者有真读取方。**没有选「加一个 legacy meta 的 JSON blob 列」** —— §十二 引 R-19.2 明确否过 | ||
| 1157 | + 「建表而不是 JSON」,那会把一个已被否掉的形态重新引进来。 | ||
| 1158 | + | ||
| 1159 | +验证方式是**真起服务打一遍**,不是只看测试绿:照 `tools-base.md` 印的请求体原样 POST → 200 + | ||
| 1160 | +老形态响应;再打一次 → 409 且 `existingTaskId` 正确;`/task/report` 之后 `status→completed`、 | ||
| 1161 | +`summary` 与 `finished_at` 都在;`loadTaskMeta` 往返出来的形状与老 JSON 逐字一致。 | ||
| 1162 | + | ||
| 1163 | +### 16.6 第 5 步:删掉的是「猜」,不是「记录」 | ||
| 1164 | + | ||
| 1165 | +原 `getAggregatedTasks` 是 122 行的启发式:扫全部 agent 的 `conversations/`,按会话 id 里的 | ||
| 1166 | +`T-xxx` 归组,再用三条策略(meta 的 `parentTaskId` / from agent 的活跃会话时间包含 / | ||
| 1167 | +master conv 的标题相似)**猜**父子关系。猜出来的树没法验证对错,而且每次请求都要遍历所有 | ||
| 1168 | +agent 的会话目录 —— 路由那 10 秒缓存就是为它加的。 | ||
| 1169 | + | ||
| 1170 | +现在父子关系是 `tasks.parent_task_id`,派单时就显式写下来了。`task-service.js` **391 行 → 236 行**。 | ||
| 1171 | + | ||
| 1172 | +**`getTaskTimeline` 保留**,但来源拆清楚: | ||
| 1173 | +- 任务树(有哪些子任务)→ `tasks` 表,显式、可验证; | ||
| 1174 | +- 时间线正文 → agent 的 `conversations/`,那是**一手记录,没有替代品**。 | ||
| 1175 | + | ||
| 1176 | +两处口径变化(已写进代码注释):`agents` 从「会话里出现过的 agent 集合」变成「谁派的 + 派给谁」; | ||
| 1177 | +`conversationCount` 从「扫出来的文件数」变成「有没有起过执行会话」(0/1)—— 为一个列表字段 | ||
| 1178 | +遍历所有 agent 的会话目录,正是这次要去掉的那笔开销。 | ||
| 1179 | + | ||
| 1180 | +### 16.7 两次「测试全绿 ≠ 没坏」 | ||
| 1181 | + | ||
| 1182 | +这一批里有两处只有**真跑**才会暴露的问题,测试一条都没红: | ||
| 1183 | + | ||
| 1184 | +1. **`routes/tasks.js` 漏改**:它自己还在算 `t.conversations.length`,而 `conversations` 是 | ||
| 1185 | + 会话反推那套的产物、已随之删除 → `GET /api/tasks` 直接 500。没有任何测试走那条路。 | ||
| 1186 | +2. **控制台 org graph 栏宽**(§十五 之外,`81629b7`):320px 减去 padding 只剩 292px, | ||
| 1187 | + 而两个节点加一条边要 380px+ —— 节点名被从中间切断。构建、单测、探针全都不会报。 | ||
| 1188 | + | ||
| 1189 | +> 与之对照,§十六.2 那个「存量库一启动就炸」是**测试能覆盖但没人写**的类型。 | ||
| 1190 | +> 三者合起来是一条经验:**新建库的路径、纯逻辑的路径由单测天然覆盖; | ||
| 1191 | +> 「存量数据」「真实接口」「肉眼可见」这三类必须另外安排人去走一遍。** | ||
| 1192 | + | ||
| 1193 | +### 16.8 留给下一轮 | ||
| 1194 | + | ||
| 1195 | +1. `system/tasklog/` 目录**还在**(4 个历史文件,已回填进表)。按 §十二.5 应改名为 | ||
| 1196 | + `system/tasklog.bak-unify/` 而不是直接删,跑顺一个版本再清。本轮未做。 | ||
| 1197 | +2. `tools-base.md` 里印的端口仍是 `localhost:3000`,实际是 **3010**。已经真咬过人 —— | ||
| 1198 | + 回填出来的派单正文里有人手写「端口 3010,不是 3000」在纠正它。**改端口不改契约形态**, | ||
| 1199 | + 但它是所有 agent 的工具说明,值得单独一次改动 + 通告。 | ||
| 1200 | +3. `TaskTimeline.vue` 未动。它按字段名取值,本轮保住了全部 12 个字段,但 `agents` / | ||
| 1201 | + `conversationCount` 的语义变了(见 §16.6),界面上的措辞可能要跟着调。 |