diff --git a/docs/todo/system-design/27-p3c-goal-task-model.md b/docs/todo/system-design/27-p3c-goal-task-model.md index 01d0bb1..2169f48 100644 --- a/docs/todo/system-design/27-p3c-goal-task-model.md +++ b/docs/todo/system-design/27-p3c-goal-task-model.md @@ -15,6 +15,9 @@ > ⑥ `report_to` 只活在请求闭包里,排队后**回调链路静默断掉**;`okr_node_id` 逐 task 下发会**反复覆写同一个 OKR 节点**(§四.7 两条禁令); > ⑦ **manual 任务会永久堵住队头**(占着 `running` 等人点「下一步」),改为不参与自动出队(§五.2bis)。 > 补漏:存量回填脚本(§四.8,不做的话批 1 反而让历史任务更看不见)、任务级 `rerun` 与节点级 `retry` 的命名撞车(§六.3)、`queuePosition` 排序口径必须与调度器同源、砍掉没有消费方的 `goals.budget_json`(§十一.6)。 +> +> **第三轮(2026-08-18,四条口径由用户定案 → 27-REQ R-20.5)**:① **`task-planning` agent 提进 v1**(原计划 v2),新增 §四.9,批 2 范围与工作量随之变化;② 并行仍按批 4 单独排期,v1 只做排队(`max_parallel_tasks = 1`,唯一索引不动);③ manual 任务不参与自动出队(§五.2bis);④ 上游失败 → 下游自动推导为 `blocked` 并标红(§五.1.2)。 +> 定案 ① 连带牵出一处新缺陷:**`task-planning` 跑在 goal 层、任何 run 存在之前,它烧的 token 没有预算池可扣** —— 正是 `defaultPlanWorkGraph` 注释里点名的「隐形的一半花费」的新实例。上一轮刚以「零消费方」为由砍掉的 goal 级预算字段,因此以记账字段 `planning_tokens` 的形式回来(§四.1)。 --- @@ -90,8 +93,8 @@ goals (G-xxx) 人类下达。一条 = 一个需求 = 一条验收边 | 层 | 回答的问题 | 谁改它 | 状态机 | |---|---|---|---| -| goal | 人想要什么?做到了没有? | 人 | `draft → active → done / cancelled / archived` | -| task | 这个需求拆成哪几件事?谁先谁后? | 人(v1)/ `task-planning` agent(v2,§十一) | `draft → queued → running → completed / failed / blocked / cancelled` | +| goal | 人想要什么?做到了没有? | 人 | `draft → planning → active → done / cancelled / archived` | +| task | 这个需求拆成哪几件事?谁先谁后? | **`task-planning` agent 拆、人可改**(§四.9)| `draft → queued → running → completed / failed / blocked / cancelled` | | run | 这一次跑成什么样? | 引擎 | 既有 `RUN_TRANSITIONS` + 新增 `queued`(§四.3) | **为什么 task 与 run 必须分开**(「不能直接把 task 当 root run」的三条硬理由): @@ -117,11 +120,13 @@ CREATE TABLE IF NOT EXISTS goals ( title TEXT NOT NULL, -- 短标题,列表显示用;可改 description TEXT NOT NULL, -- 人类需求原文 = 验收边界;下达后不可改 status TEXT NOT NULL DEFAULT 'draft', - -- draft | active | done | cancelled | archived + -- draft | planning | active | done | cancelled | archived + -- planning = task-planning 正在拆分(§四.9),是个真会持续几十秒的状态 priority TEXT NOT NULL DEFAULT 'P1', okr_node_id TEXT, -- 可选,挂到 OKR 度量树的哪个节点(O-001.1.1) created_by TEXT, -- 'human' | agent id(Master/CEO 代下单) report_to TEXT, -- 收敛后要唤醒的上级 agent,见 §四.7 + planning_tokens INTEGER NOT NULL DEFAULT 0, -- task-planning 这一次拆分烧掉的 token,见 §四.9 created_at TEXT NOT NULL, updated_at TEXT NOT NULL, dispatched_at TEXT, -- 首次下达时间;非空 = 描述已冻结 @@ -130,7 +135,9 @@ CREATE TABLE IF NOT EXISTS goals ( CREATE INDEX IF NOT EXISTS idx_goals_company ON goals(company_id, status); ``` -**没有 `budget_json`**:预算闸门是按 root run 算的(`DEFAULT_TASK_BUDGET` 只挂 root run,子 run 一律上溯 `root_run_id` 扣同一个池),goal 级预算**没有任何执行机制**。加一个没人消费的字段,就是这个仓库反复被咬的「实现在、零调用方」。goal 级总额控制留到 §十一.6 单独定口径。 +**没有 `budget_json`,但有 `planning_tokens`**:预算**闸门**是按 root run 算的(`DEFAULT_TASK_BUDGET` 只挂 root run,子 run 一律上溯 `root_run_id` 扣同一个池),goal 级预算没有任何执行机制,加一个没人消费的额度字段就是这个仓库反复被咬的「实现在、零调用方」。 + +但 `task-planning` 定案进 v1 之后,**goal 层出现了一笔真实、且没有池子可扣的花费**:它跑在任何 run 存在之前。`defaultPlanWorkGraph` 的注释已经把这个病点过名 ——「规划调用也是真花钱的一次 agent 调用,token 必须计入同一个 root run 预算池,否则『一层规划 + 一层执行』里有一半的花费是隐形的」。goal 级拆分正是那个「隐形的一半」的新实例,所以**必须记账**(`planning_tokens`),哪怕暂时不设闸门。goal 级总额**控制**仍留到 §十一.6。 `okr_node_id` 放在 goal 上而不是 task 上:OKR 度量的是「人的需求达成了没有」,那正是 goal 的粒度 —— 具体的实现禁令见 §四.7,那不是一句设计倾向,是照现有代码顺手写就会踩的坑。 @@ -152,6 +159,8 @@ CREATE TABLE IF NOT EXISTS tasks ( current_run_id TEXT, -- 最近一次 root run(逻辑外键,见 §四.6) attempt INTEGER NOT NULL DEFAULT 0, -- 起过几次 run(任务级重跑,≠ 节点级 retry) report_to TEXT, -- 从 goal 继承或调用方指定;见 §四.7 + created_by TEXT, -- 'task-planning' | 'human':这一条是拆出来的还是人加的 + blocked_reason TEXT, -- status='blocked' 时的原因,如 upstream_failed:T-xxx priority TEXT NOT NULL DEFAULT 'P1', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, @@ -274,6 +283,34 @@ const genId = (p) => `${p}-${Date.now().toString(36)}-${crypto.randomBytes(2).to > 实测当前 `data/graph.sqlite` 里只有 1 条 root run(且卡在 `running`),所以回填本身成本极低 —— 但**开发机与线上库的数据量不一样**,脚本必须写,不能靠「反正没几条」跳过。 +### 4.9 `task-planning` —— 把 goal 拆成 task DAG(v1 就做,用户定案) + +命名依 27-NAMING §二「`{产出物}-planning`」:产出物是 task 列表,故 **`task-planning`**。它是继 `orggraph-planning` / `workgraph-planning` 之后的**第三个规划类 agent**,形态与后者对齐。 + +#### 链路照抄 `defaultPlanWorkGraph`,不另造 + +`graph-engine.defaultPlanWorkGraph` 已经把「调规划 agent → 校验产物 → 不过就把 `errors` 原样回给它再来一轮」这条链路跑通了(`PLAN_MAX_ROUNDS = 3`)。`task-planning` 用**同一个形状**: + +``` +AgentManager 调 task-planning(输入:goal.description + 公司 org graph 摘要 + 可用角色清单) + → 产出 JSON: [{ title, description, depends_on: [], seq }] + → 校验:字段齐全 / depends_on 只引用本次产出的 seq / 无环 / 条数 ≤ 上限 + → 不过 → errors 原样回喂,最多 3 轮 + → 过 → 落 tasks 表,全部 status='draft' +``` + +**产出的 task 一律是 `draft`,不自动入队。** 拆完在界面上是一份可编辑的清单(改描述、删、加、调依赖),人点「全部下达」才转 `queued`。理由:拆分是一次 LLM 猜测,直接把 5 条任务塞进队列跑起来,等于让一次猜错的拆分直接烧掉 5 份预算;而这一步的编辑成本几乎为零。 + +#### 三条必须写进实现的约束 + +1. **token 记账**:复用 `defaultPlanWorkGraph` 那个 `onAcpEvent` 累加 `usage` 的写法,结果写进 `goals.planning_tokens`(§四.1)。这是 goal 层唯一一笔花费,不记就是隐形的。 +2. **失败必须降级,不能把 goal 卡在 `planning`**:3 轮都没产出合法 DAG → **退化成 1 条同描述的 task**(`created_by='human'`、`seq=1`、`depends_on=[]`),goal 转 `active`,并在界面标注「自动拆分失败,已按单任务下达」。绝不能留一个永远转圈的 `planning` goal —— 那正是这次要消灭的那类静默状态。 +3. **`_registry.json` 必须同时登记**:新建本体目录而漏登记,`server/tests/unit/template-refs.test.js` 会直接把 CI 跑红 —— 27-P3/P4 §八.21 记着这个前科(六个本体建了目录没登记,718 passed / 1 failed)。本体文件按 `workgraph-planning` 的形态给齐:`agent.md` / `config.json` / `identity.md` / `soul.md` / `tools.md`。 + +#### 跳过拆分 + +`POST /goals` 带 `split: false` 时不调 agent,直接建 1 条同描述的 task。存量调用方(`POST /:cid/tasks`)走的就是这条路 —— **它们不该因为这次改动突然多花一次 LLM 调用**。 + --- ## 五、排队与并行 @@ -337,6 +374,24 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { > ponytail: `tick` 目前按公司算配额。将来要跨公司全局配额时,改成「按配额池」——钩子位置不用动。 +### 5.1.2 上游失败 → 下游自动转 `blocked`(用户定案) + +`depends_on` 只认 `completed`,所以上游一旦 `failed` / `cancelled`,依赖它的下游**永远满足不了条件**。放着不管,那条 task 会静默躺在队里,界面上和「正在正常排队」长得一模一样 —— 又是这次要消灭的那类症状。 + +`tick` 开头加一步(在选候选之前): + +```js +// 依赖已经不可能满足的 queued task,就地转 blocked,别让它假装在排队 +for (const t of queuedTasks) { + const dead = t.depends_on.find((id) => ['failed','cancelled'].includes(statusOf(id))); + if (dead) setTaskBlocked(t.id, `upstream_failed:${dead}`); +} +``` + +界面上 `blocked` 的行标红并显示 `blocked_reason`,`⋯` 菜单给两个出路:**重跑上游**(上游 `rerun` 成功后,下游自动回 `queued`)或 **强制放行**(清掉那条依赖,人为承担风险)。 + +> **级联要一次算到底**:A 失败 → B blocked → 依赖 B 的 C 也应该 blocked(`blocked` 同样不是 `completed`)。上面那段按「依赖里有任意一个处于非 completed 终态」判定即可自然级联,但要注意**别把 `blocked` 本身当成终态去反向传播**,否则 A 重跑成功后 C 解不开。解法:`blocked` 只由这一步**推导**得出,不落成不可逆状态 —— 每次 `tick` 重算一遍,上游恢复了就自己回 `queued`。 + ### 5.2bis manual 模式会永久堵住队头 `startRun(manual)` 只点亮入口节点就停下等 `/next`,而这时 run 已经是 `running` —— **占着 active 坑**。`max_parallel_tasks = 1` 时,一个没人点「下一步」的 manual 任务会把它后面所有任务**无限期堵死**,而队列界面上看不出任何异常。 @@ -373,7 +428,7 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { | 方法 | 路径 | 说明 | |---|---|---| | `GET` | `/api/company/:cid/goals` | 列表。query: `status` / `includeArchived` / `withTasks=1`(带出每个 goal 的 task 摘要,列表页一次拉完不 N+1) | -| `POST` | `/api/company/:cid/goals` | 新建。body `{ title, description, priority?, okr_node_id?, budget?, tasks?: [...], dispatch?: bool }`。**不传 `tasks` 时自动生成 1 条同描述的 task**(`seq=1`,`depends_on=[]`)—— 保证「只想下达一句话」的最短路径仍是一次调用。`dispatch: true` 则连带把这些 task 入队 | +| `POST` | `/api/company/:cid/goals` | 新建。body `{ title, description, priority?, okr_node_id?, report_to?, split?: bool, tasks?: [...] }`。**默认 `split: true` → 异步调 `task-planning`**:立即 `202 { goalId, status: 'planning' }` 返回,拆分产物几十秒后才出现(前端轮询或走 SSE)。`split: false` 或显式传 `tasks` 则同步建行、不调 agent。**产出一律是 `draft`,不自动入队** —— 入队走下面的 `dispatch` | | `GET` | `/api/company/:cid/goals/:goalId` | 详情:goal + 全部 task + 每个 task 的最近一次 run 摘要 | | `PATCH` | `/api/company/:cid/goals/:goalId` | 改 `title` / `priority` / `okr_node_id`;`dispatched_at` 非空时带 `description` 一律 `409`(§四.4) | | `POST` | `/api/company/:cid/goals/:goalId/dispatch` | 把该 goal 下所有 `draft` task 入队,写 `dispatched_at` | @@ -430,7 +485,13 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { - **task 行**:状态点 + 标题(2 行截断)+ 第二排 `queued#2 · auto · 3/7 · 12min`。`depends_on` 非空时标题前加 `⇢` 并 hover 出「等 T1 完成」。 - 状态点配色**沿用画布节点的既有色板**,不新造:running 蓝 / completed 绿 / failed 红 / blocked 橙 / queued 灰蓝 / draft 空心圈 / cancelled 灰。 -**「+ 新目标」表单**(承接现有 `DispatchTaskButton` 的内容,搬进这一栏内联展开):标题 + 描述 + 执行模式 + auto 模式的成本预警(原样保留)+ **「拆成多个任务」折叠区**(v1 就是一个可增删的行列表,每行 `描述 + 依赖前一条 ☑`)。主按钮「下达」,次按钮「存草稿」。 +**「+ 新目标」表单是两步的**(承接现有 `DispatchTaskButton` 的内容,搬进这一栏内联展开): + +**第一步 · 写需求**:标题 + 描述 + 执行模式 + auto 模式的成本预警(原样保留)+ 一个「自动拆分任务」开关(默认开 = `split: true`)。主按钮「下一步」。 + +**第二步 · 确认拆分**:开关关掉就跳过这一步直接下达。开着则显示 `task-planning` 的产出——一份**可编辑**的任务清单(改描述、删行、加行、调依赖前置),带一行 `本次拆分消耗 ~N tokens`。主按钮「全部下达」,次按钮「存草稿」。 + +拆分进行中(goal `status='planning'`)显示骨架屏 + 「正在拆分…」,**并给一个「跳过,按单任务下达」的逃生口** —— 几十秒的 LLM 等待必须能被绕过,否则一次 agent 卡死就等于这个表单不能用了。拆分失败时这一步直接展示降级结果(1 条同描述的 task)+ 一句「自动拆分失败,已按单任务下达」,**不弹错误对话框**。 `canDispatch === false` 时**主按钮不再禁用** —— 排队之后它永远可点,按钮下方改为提示「当前 1 个任务在跑,新任务将排在第 2 位」。这是 B 类问题在界面上的最终消解。 @@ -466,7 +527,12 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { 16. **manual 不堵队**:下一个 `manual` 任务 + 一个 `auto` 任务 → manual 不被自动拉起,auto 正常起跑;在控制台上对 manual 点「开始」才占坑。 17. **`report_to` 穿过队列**:带 `report_to` 下达一个会排队的任务,等它出队跑完 → 上级 agent 确实收到 `/api/agent/task/report`(验证 §四.7 禁令二)。 18. **OKR 只写一次**:一个挂了 `okr_node_id` 的 goal 下放 3 个 task,其中一个 failed → OKR 节点**只被写一次**,且不是被最后那个 failed 覆盖的(验证禁令一)。 -19. 全量 `npx vitest run` 不掉,重点是 §九.1 那批回归。 +19. **拆分正常**:下达一个「做个官网」这类 goal → `task-planning` 产出 ≥2 条带依赖的 task,**全部是 `draft`**,界面上可改可删可加;点「全部下达」后按依赖顺序依次跑。 +20. **拆分失败必须降级**:让 `task-planning` 连续 3 轮产出非法 JSON → goal 转 `active` + 1 条同描述 task + 界面标注「自动拆分失败」,**绝不能卡在 `planning`**。 +21. **拆分记账**:拆分后 `goals.planning_tokens > 0`,且界面上看得到这一笔(验证 goal 层花费不再隐形)。 +22. **不该花钱的路径不花钱**:`split: false` 与存量 `POST /:cid/tasks` 都**不触发任何 LLM 调用**(mock 层计数验)。 +23. **上游失败 → 下游标红**:A 失败后依赖它的 B 转 `blocked` 且带 `upstream_failed:T-A`;**A 重跑成功后 B 自动回 `queued`**(验证 blocked 是每次 tick 推导出来的,不是落死的状态)。 +24. 全量 `npx vitest run` 不掉,重点是 §九.1 那批回归 + 新本体的 `_registry.json` 登记(`template-refs.test.js`)。 --- @@ -513,6 +579,12 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { **实现时的通用检查法**:凡是「从 HTTP 请求体里取出、直接交给 driver / 闭包」的东西,在排队模型下都要问一句「几小时后它还在吗」。 +### 4ter. `task-planning` 引入的三个新风险 + +1. **多一次会失败的 LLM 调用挡在「下达任务」前面**。今天下达是一次同步 DB 写,现在中间插了一次几十秒、可能失败的 agent 调用。降级路径(§四.9 约束 2)和界面上的逃生口(§七.2)**不是可选项** —— 少了任何一个,一次 agent 卡死就等于整个下达入口不可用。 +2. **拆分质量决定预算消耗**。拆出 5 条 task 就是 5 份 `DEFAULT_TASK_BUDGET`(各 100 次调用 / 500 万 token)。这是「产出一律 `draft`、必须人确认」的根本理由 —— 那一步是唯一一道成本闸门。界面上要显式提示「N 个任务 ≈ N × 单任务成本」。 +3. **新本体不登记 `_registry.json` 会直接把 CI 跑红**。27-P3/P4 §八.21 有前科(六个本体建了目录没登记,`template-refs.test.js` 当场红)。建目录与登记必须在同一个 commit 里。 + ### 5. 两套 `T-` id `agent-chat.js` 的 tasklog 也发 `T-` 开头的 id,本次不动它。两者从此**长得像但不同源**,读日志时别混;本文档的 task id 一律带 `-{4hex}` 后缀,肉眼可分。 @@ -528,8 +600,8 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { | 批 | 内容 | 验收 | |---|---|---| | **1 · 两级模型落库** | `goals` / `tasks` 建表 + **存量回填脚本(§四.8)** + ID 生成器修正 + `POST /:cid/tasks` 兼容改造(自动建 goal、`report_to` 落库)+ `GET` 列表端点 + 退役 `/goals` 别名 | 4 / 10 / 11 / **12** | -| **2 · 控制台左栏** | `GoalTaskPane` 两级树 + URL 选中态 + `loadRuns` 修正 + 新建表单(含拆分任务)| 1 / 19 | -| **3 · 排队** | `run_status='queued'` + `RUN_TRANSITIONS` + `syncRunNodesToDef` + `task-scheduler`(漏斗钩子 + 三个辅助触发 + 重入保护)+ `depends_on` 环检测 + 入队/取消/rerun 端点 + §四.7 两条禁令 + §九.1 全部回归点 | 2 / 3 / 5 / 6 / 7 / 8 / 9 / **13-18** | +| **2 · 控制台左栏 + 拆分** | `GoalTaskPane` 两级树 + URL 选中态 + `loadRuns` 修正 + **`task-planning` 本体(含 `_registry.json` 登记)+ 拆分链路 + 失败降级**+ 两步表单 | 1 / 19 / 20 / 21 / 22 | +| **3 · 排队** | `run_status='queued'` + `RUN_TRANSITIONS` + `syncRunNodesToDef` + `task-scheduler`(漏斗钩子 + 三个辅助触发 + 重入保护 + 上游失败推导 blocked)+ `depends_on` 环检测 + 入队/取消/rerun 端点 + §四.7 两条禁令 + §九.1 全部回归点 | 2 / 3 / 5 / 6 / 7 / 8 / 9 / 13-18 / 23 | | **4 · 并行**(单独排期)| `max_parallel_tasks` + `DROP` 唯一索引 + 事务内 admit | 另立验收 | > 批 3 的验收从 6 条涨到 12 条,是本次复核的直接结果 —— 13/14/15 三条各自对应一个「初稿会实现出来的静默故障」。 @@ -540,7 +612,7 @@ if (run.parent_run_id === null && !ACTIVE_ROOT_RUN_STATUS.has(to)) { ## 十一、开放问题 -1. **谁把 goal 拆成 task?** v1 是人手拆(表单里加行)。v2 建议加一个 **`task-planning`** agent(命名依 27-NAMING §二「`{产出物}-planning`」,产出物是 task 列表)—— 它读 goal 描述 + 公司 org graph,产出带 `depends_on` 的 task DAG。**本次不做,但表结构已经为它留好**(`depends_on` / `seq` / `created_by`)。 +1. ~~谁把 goal 拆成 task?~~ **已定案(2026-08-18):`task-planning` agent 进 v1**,产出 `draft` 任务清单由人确认后下达,详见 §四.9。 2. **goal 级的验收谁做?** 现在 `org-validator` 验的是 task 级(`task-goal`)。一个 goal 的 5 个 task 全 `completed`,不等于人的需求达成了。要不要在 goal 收敛时再跑一次 goal 级验收(`goal-benchmark` 天然就是干这个的)?倾向做,但需要先定「goal 级验收的输入是什么」—— 是 N 个 task 的产物合集吗?留到 P5。 3. **失败的 task 挡不挡住依赖它的下游?** 当前设计:`depends_on` 只认 `completed`,所以上游 failed 会让下游**永远排队**。需要一个显式的「跳过依赖强制入队」操作,还是把这类 task 自动转 `blocked`?倾向后者(自动转 `blocked` 并在界面标红),但要防止级联误判。 4. **跨公司任务总览**。`/api/tasks` + `TaskTimeline` 是从会话反推的另一套任务树,与本文档的 `tasks` 表同名不同源。将来是把它改成读这张表,还是并存?本次不碰,但这是个明确待收的口子。 diff --git a/docs/todo/system-design/27-requirement.md b/docs/todo/system-design/27-requirement.md index dfb4fb9..98edb8d 100644 --- a/docs/todo/system-design/27-requirement.md +++ b/docs/todo/system-design/27-requirement.md @@ -528,6 +528,19 @@ git diff,最后 W13 只能把计数挪去另一个被 ignore 的文件。`file 顺带三个收益:入队即有稳定 `run_id`(可分享的控制台链接)、`task-goal.json` 的落盘路径在入队时就确定(描述冻结点与 artifact 落盘点重合,正是 R-20.3 要的)、timeline 上记得下「何时入队」这条审计事实。 +### R-20.5 · 四条实现口径定案(2026-08-18,文档复核后确认) + +初稿复核完成后就四个「会实质改变要建的东西」的选择征询,用户逐条定案: + +| # | 问题 | 定案 | 影响 | +|---|---|---|---| +| 1 | goal 拆成多个 task 由谁来拆? | **`task-planning` agent 进 v1**(原设计留到 v2 人手拆)。产出一律是 `draft` 清单,人确认/编辑后才下达 | 批 2 多一个 agent 本体 + 拆分链路 + 两步表单,工作量约翻倍 | +| 2 | 并行是这次就要还是先只排队? | **先只排队,`max_parallel_tasks = 1`**。唯一索引 `idx_graph_runs_one_active_root` 不动,DB 层兜底保留 | 并行仍是批 4,单独排期单独验收 | +| 3 | manual 任务在队列里怎么处理? | **不参与自动出队**,人在控制台点「开始」才占坑 | `startRun(manual)` 只点亮入口节点就停下等 `/next`,而 run 已经是 `running` 占着坑;自动拉起会让一个没人管的 manual 任务无限期堵死队头 | +| 4 | 上游 task 失败时下游怎么办? | **自动转 `blocked` 并在界面标红**,给「重跑上游」与「强制放行」两个出路 | `depends_on` 只认 `completed`,不处理的话下游会静默躺在队里,与「正在正常排队」无从区分 —— 正是本次要消灭的那类症状 | + +**定案 1 连带牵出一处新缺陷**:`task-planning` 跑在 **goal 层、任何 run 存在之前**,它烧的 token **没有预算池可扣**。这正是 `graph-engine.defaultPlanWorkGraph` 注释里点过名的那个病——「规划调用也是真花钱的一次 agent 调用,token 必须计入同一个 root run 预算池,否则『一层规划 + 一层执行』里有一半的花费是隐形的」——在 goal 层的新实例。复核第二轮刚以「零消费方」为由砍掉的 goal 级预算字段,因此以**记账**字段 `planning_tokens` 的形式回来(闸门仍不做,见 P3C §十一.6)。 + **落点**:[27-P3C goal / task 两级模型](./27-p3c-goal-task-model.md)(本条派生出的工作项文档)。 --- @@ -570,3 +583,4 @@ git diff,最后 W13 只能把计数挪去另一个被 ignore 的文件。`file | R-20.2 | 两级结构之上做**排队与并行**;推翻 P3「拒绝而不排队」,必须同时交付调度器(四个触发点)| P3C §五 | | R-20.3 | goal / task 描述**一经下达不可改**(artifact 已被 agent 消费,改库等于篡改审计输入)| P3C §四.4 | | R-20.4 | 新增 run status **`queued`**,出队原子性靠既有唯一索引兜底 | P3C §四.3 | +| R-20.5 | 四条口径:**`task-planning` 进 v1** / 并行留批 4 / manual 不自动出队 / 上游失败下游自动 blocked | P3C §四.9 / §五.1.2 / §五.2bis |