Commit b9495f57b70136ea1e2b22d52ab488fe75f74d77

Authored by yibo
1 parent 514ec3d5

docs(27-P3C): §十六 批 5 实施记录(第 1-2 步)

记两条这一批换来的东西:

1. 加列时把 CREATE INDEX 写进建表块,会让**所有存量库一启动就炸** ——
   建表块先于迁移块执行,存量库上 CREATE TABLE IF NOT EXISTS 是 no-op,
   索引引用的那一列还不存在。而新建库(单测默认路径)完全正常。
   由此得到一条可复用的规矩:凡是加列,必须同时测「存量库重启」那条路 ——
   新建库的路径由每个单测天然覆盖,存量库那条没人走,而生产上只有存量库。

2. 回填沿用原 taskId 而不是新发:conversations/task-{taskId} 是目录名、
   汇报链路按它查找、summary 里互相引用。换 id 等于把引用全断掉,
   而回填的目的恰恰是别让历史消失。

并记下第 3 步动手前的前提:/task/dispatch 的 URL 与请求体印在
tools-base.md 里(所有 agent 的工具说明),一个字不能改;而 agent-chat.js
在 8-19 被另一个会话大改过,预研写在那之前,所以要重新读现状再动手。

顺带记一处过期且已咬过人的:tools-base.md 印的是 localhost:3000,
实际端口是 3010 —— 回填出来的派单正文里有人手写「端口 3010,不是 3000」
在纠正它。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 未开工,预研方案已就绪。开放问题已全部关闭。 5 +> **状态**: **批 1-4 已实施并全绿**(§十三 / §十四 / §十五)。**批 5 进行中**(§十六:第 1-2 步已落地,第 3-6 步未做)。开放问题已全部关闭。
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 的警示块);
@@ -1065,3 +1065,64 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次 @@ -1065,3 +1065,64 @@ manual,授权早就给过了,让它自己接回来是**兑现人刚才那次
1065 2. **机器级闸门仍然不存在**:`SessionPool` 没有 `maxSessions`,`task-queue.js` 另有个 `MAX_CONCURRENT=3` 1065 2. **机器级闸门仍然不存在**:`SessionPool` 没有 `maxSessions`,`task-queue.js` 另有个 `MAX_CONCURRENT=3`
1066 抢同一台机器。批 4 只放开了公司级额度(硬上限 20),机器压不压得住没有任何守卫。 1066 抢同一台机器。批 4 只放开了公司级额度(硬上限 20),机器压不压得住没有任何守卫。
1067 3. 批 5(两套任务体系统一)未开工,预研方案已就绪。 1067 3. 批 5(两套任务体系统一)未开工,预研方案已就绪。
  1068 +
  1069 +---
  1070 +
  1071 +## 十六、批 5 实施记录(2026-08-19/20,进行中)
  1072 +
  1073 +> 体例同 §十三~§十五。**本节随批 5 推进增量补写**,未做的步骤明确标出来。
  1074 +
  1075 +### 16.1 进度
  1076 +
  1077 +| 步 | 内容 | 状态 |
  1078 +|---|---|---|
  1079 +| 1 | `tasks` 表吸收 `executor` / `assignee_agent_id` / `parent_task_id` | ✅ `5cd91c3` |
  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'` | ⏳ 未做 |
  1085 +
  1086 +全量:**131 文件 / 1251 测试全绿**。
  1087 +
  1088 +### 16.2 第 1 步踩到的:一个会让**所有存量库开不了**的顺序问题
  1089 +
  1090 +第一版把 `CREATE INDEX ... ON tasks(assignee_agent_id)` 写进了建表块。**建表块整体先于迁移块执行**,
  1091 +而存量库上 `CREATE TABLE IF NOT EXISTS tasks` 是 no-op —— 表还是批 4 的形态、没有那一列,
  1092 +于是这句索引直接抛 `no such column`,`getDb()` 起不来。
  1093 +
  1094 +**新建库完全正常**(单测默认走的就是这条路),所以只测新建库根本看不见这个洞。
  1095 +是专门去跑「批 4 形态的库重启一次」才撞出来的。索引已挪进迁移块、排在 ALTER 之后,
  1096 +守卫见 `tasks-executor-migration.test.js`。
  1097 +
  1098 +> 由此得到一条可复用的规矩:**凡是加列,必须同时测「存量库重启」这条路**。
  1099 +> 新建库的路径由每个单测天然覆盖,存量库那条没人走 —— 而生产上只有存量库。
  1100 +
  1101 +已知不对称(写在代码注释里):`ALTER TABLE ADD COLUMN` 加不了 CHECK 也加不了外键,
  1102 +所以存量库上 `executor` 的取值域**只由应用层守**,与新建库的 CHECK 不同源。
  1103 +
  1104 +### 16.3 第 2 步的关键判断:沿用原 `taskId`
  1105 +
  1106 +回填时**不新发 id**。理由是引用面:`conversations/task-{taskId}` 是目录名、汇报链路按 taskId
  1107 +找回这条派单、summary 里互相引用。换 id 等于把这些引用全断掉,**而回填的目的恰恰是
  1108 +「别让历史消失」**。
  1109 +
  1110 +`company_id` 落保留 id `_global`:tasklog 的行**没有 company 这一维**(`from`/`to` 都是本体 id),
  1111 +全局 agent 的会话本来就落在这个 id 下(28 §5.1)。刻意不去猜某条派单「其实属于哪家公司」——
  1112 +猜错会让它出现在错误的公司任务列表里,那比「都归 `_global`」更难发现。
  1113 +
  1114 +实跑:4 条派单全部回填成功、零失败;再跑一次全部跳过、零新增。
  1115 +
  1116 +### 16.4 第 3 步(下一步)的前提,动手前必须先核实
  1117 +
  1118 +`POST /api/agent/task/dispatch` 与 `/task/report` 的 **URL 与请求体形态一个字都不能改** ——
  1119 +它们印在 `packages/agent-core/base/common/tools-base.md` 里,是**所有 agent 的工具说明**,
  1120 +改它等于同时改所有 agent 的提示词。
  1121 +
  1122 +而 `server/routes/agent-chat.js` 在 2026-08-19 被另一个会话大改过(会话回收 / `IdleTimer` /
  1123 +`CALL_KINDS` / 进程泄漏修复,见 `310a2b2` / `2092a2e` / `b0b2b62`)。**预研是在那之前写的**,
  1124 +所以第 3 步动手前要重新读一遍现状,而不是照预研的描述改。
  1125 +
  1126 +> 顺带记一处过期:`tools-base.md` 里印的是 `http://localhost:3000`,而实际端口是 **3010**
  1127 +> (`server/config.js`)。已经真咬过人 —— 回填出来的派单正文里有人手写了「端口 3010,不是 3000」
  1128 +> 来纠正它。这一处该在第 3 步一起改(改的是端口不是契约形态)。