39-boss-agent-task-supervision.md 31.7 KB

39. Boss Agent 任务看板监督 + 公司目标代码目录 — 需求评估与设计草案

状态:✅ P1-P5 已实现(2026-08-30,commit a00590a P1/P3/P4 后端、5108d67 P2 前端、d967ac0 P5)。本文档是落地前的设计草案,实现细节以代码为准,本文档不再更新,仅存档决策过程。已知未覆盖的后续工作见文末新增的「六、已知遗留」。 日期:2026-08-30 背景:承接对话讨论——goal/task 两级模型(27-P3C)已落地,但推进它的是纯确定性代码(task-scheduler.js + transitionRun 钩子),没有任何 agent 在持续监督这块看板、处理异常、把执行中发现的新问题回写成新任务。用户提出五阶段方案补上这一层,并追加公司级目标代码目录需求。 读者须知:本文档大量引用现有代码路径核实"已有 vs 缺失",结论以文内给出的具体函数/文件为准;这些引用会随代码演进过期,落地前请重新核实。


一、先说结论

用户的五阶段里,P2(任务看板)本质已完成,P1/P3 的基础设施已有大半,P4 是真正的新增核心,P5 的机制已经实现了 70%,只是没有暴露到公司层面。 真正从零开始的工作量比五个"phase"暗示的要小得多,但顺序建议大改:P4(回写闭环)应该提到最前面,因为它才是这次讨论最初的诉求("自己发现问题、自己重新规划"),P1/P3 的 boss agent 只有在 P4 的工具/数据通路建好之后才有事可做。

用户的 phase 名义工作量 实际现状 建议
P1 雇佣通用 boss agent 新建一个角色 + 训练 心跳基础设施、ceo 本体、Innovator 训练方法论全部已有 复用 ceo,不新建角色;工作量收窄成"写 heartbeat.md + 工具"
P2 任务看板独立页面 一个新系统 goal/task 两级看板已实现(GoalTaskPane.vue),只是嵌在控制台里 降级成 UI 改造,非必须,可排最后
P3 心跳挨个决定下一个 task 新的调度逻辑 挨个执行已经是 task-scheduler.js 在做,且做得更好(事件驱动、零成本、已测试) 不要重建调度;心跳的职责收窄为"异常判断",且应事件触发而非纯轮询
P4 bug/新任务回写看板 一条新闭环 不存在——没有任何 agent 可读写 goal/task 板的工具,四分类失败归因(30 号文档)目前只导向"重试或人工介入",不导向"开新任务" 真正的核心工作,优先做
P5 公司目标代码目录 新能力 DevLoop 的 path-resolver.js 已实现 sandbox / linked-new / linked-existing 三种模式,linked-existing(接管已有目录)已经是完整实现,只是没有公司级默认值,也没打通到 org/work graph(非 DevLoop)路径 补一个公司级默认配置 + 核实非 DevLoop 路径是否需要同样机制

二、逐 phase 现状核实

P1:每个公司雇佣 boss agent,system agent 层训练通用版本

已有的基础设施(全部实地核实过):

  1. 通用心跳机制:server/services/heartbeat-service.js。状态落在 system/agents/{aid}/{cid}/heartbeat.json(公司态目录,不是共享本体——这条边界是踩过坑之后加的,见文件头注释),本体 config.json.heartbeat 只是只读默认建议间隔。_triggerHeartbeat() 按 setInterval 定时给 agent 发一条 [SYSTEM HEARTBEAT] 系统消息(走正常的 /api/agent/{agentId}/chat),要求 agent "根据你的身份和 heartbeat.md 规则……如果一切正常,无事发生,请直接回复『心跳正常』",带 30 分钟会话互斥防重叠。这套机制现成可用,不用重建。
    • ✅ 修正一处之前的误判:原以为 prompt 里"扫描全局 OKR 目标状态和节点"那句硬编码需要改代码才能适配新 goal/task 模型。查了 system/agents/ 下已有 35 个本体带 heartbeat.md(如 org-innovator/heartbeat.md),内容五花八门、完全跟 OKR 无关,说明触发消息里那句"扫描 OKR"从来就只是默认示例文案,各 agent 早就按自己 heartbeat.md 里写的规则各干各的。结论:heartbeat-service.js 大概率零改动,只需要新建 system/agents/ceo/heartbeat.md,写清楚"去查 goal/task 板、按 §四.9 的规则判断",agent 读到自己的 heartbeat.md 就会照着做,不需要动服务端代码。落地时找一个现有 heartbeat.md 生效的真实案例复核一次这个推断,再动手写 ceo 的那份。
  2. 本体已存在:system/agents/ceo/(type: "cxo","Chief Executive Officer",heartbeat.enabled: false 默认关)。同时存在 system/agents/master/(type: "master",全局唯一,/api/master/chat,负责建公司/招聘等跨公司操作)。两者定位不同:master 是平台层面的单例助手,ceo 才是"每个公司一个"的角色。
  3. 训练方法论已验证:36-prd-agent-methodology-training-feasibility.md 已经用 Innovator(Hunt→Digest→Evolve)训练出过一个方法论型 agent(prd-methodology-trial),2 轮收敛、96/97 分,方法论对这类"判断型"角色确实管用。可以照搬这条路径训练 ceo 的监督方法论,而不是手写一份 prompt 拍脑袋。

结论:不建议新建"boss"角色。复用 system/agents/ceo/——它的岗位定义(CEO)本来就该管"任务有没有卡住、要不要重新分解",另起一个语义重叠的角色只会重犯这个仓库反复踩过的"名字歧义"坑(27-naming-conventions.md 通篇在防这个)。P1 的实际工作量是:① 给 ceo 补一份 heartbeat.md(监督规则);② 给它挂上 P4 需要的工具(见下);③ 默认 heartbeat.enabled: true(但见 P3 的成本讨论,建议先不无脑轮询);④ 可选:用 Innovator 训练它的监督方法论。

缺口 A:目前没有任何 agent 工具能读写 goals/tasks 表或操作 graph run(搜了 server/src/tools/ 全目录,零命中)。ceo 就算被唤醒也无事可做——这是 P1 能不能生效的真正前提,见 P4。

缺口 B(新发现,之前漏了):ceo 现在没有被雇进任何公司,hire() 也没有任何自动触发点会调它。查了 hire(companyId, roleName)(company-service.js:422)——它只查本体目录 + company_agents 表,不查 org graph,机制上雇一个不在图里的角色完全没问题(数据模型层面,"雇了"和"进图"是两件独立的事)。但目前没有任何代码路径会主动调 hire(companyId, 'ceo')——它不在三份 org-graph-templates 里,也没有类似 orggraph-planning 那样的自动招聘钩子替它兜底。37-org-graph-department-scoping.md 里记录过一模一样的处境:hr/ 本体"已经真实存在……只是从没被装进任何图……今天完全是编外人员"——ceo 现在是同款处境,不是有先例可抄,是同一个坑还没人填。P1 必须显式加一步:① 公司创建流程里(不管选哪个 org-graph-template)固定 hire(companyId, 'ceo') 一次;② 存量公司需要一次性回填脚本,遍历现有公司补雇。这条不做,前面说的"复用 ceo"就是一句空话——压根没有 ceo 实例在跑。


P2:任务看板独立页面

已实现(27-P3C,✅ 2026-08-20):web/src/components/graph-console/GoalTaskPane.vue + goal-task-model.js,挂在 /:companyId/graph-console 左栏。goal/task 两级状态、优先级排队位次、颜色语义全部已有,REST 是 server/routes/company.js 的 GET/POST /api/company/:cid/goals 系列。

用户要的"独立页面" 目前是控制台的一个侧栏,不是路由级独立页面。这是纯 UI 改造,不涉及数据模型或后端,随时可做,风险最低。

"subtask" 层:当前没有独立的 subtask 表,task 就是看板能管理的最细颗粒度。task 再往下是 graph_runs(work graph 节点,可能含嵌套子图),这层在中间的执行画布(WorkGraphCanvas)里看,不在左栏。

⚠️ 不建议新增一张 subtask 表去对应 work graph 节点。这个仓库已经因为"两套任务体系不统一"吃过至少两次苦头(P3C §十二"批 5"整章在处理这个债,27-p3c-batch3-handover.md 也记过一次事故)。如果要在看板上展示"这个 task 内部具体在跑哪几步",应该是只读地把该 task 当前 current_run_id 对应的 graph_run_nodes 渲染成一份"子步骤列表",复用现有表和现有执行画布的数据源,不新建写路径。

P2 决定:与 OKR 页面的关系(已拍板,2026-08-30)

排查发现现状比预期乱:OKR 相关 UI 现在有两份几乎逐行相同的重复实现——web/src/views/OkrDashboard.vue(路由 /okr,requireAdmin,零导航入口的孤儿页面)和 CompanyDashboard.vue 里挂着 "goals" 名字、实际渲染 "Goals (Full OKR Tree)" 的 tab——两者都是旧 OKR 树(O-xxx,owner + progress%),是"OKR 正名"(27-naming-conventions.md §六)前端没跟着改完的遗留物,前端两处都调 /api/okr/*,和 P3C 的新 goal/task 模型完全不是一回事。

已拍板:不合并,OKR 这套前端 UI*直接废弃,新任务看板另起一个独立页面*。具体动作:

  1. 新增路由:仿照 GraphConsole 已经立好的先例(/:id/graph-console 是独立路由,不挤在 CompanyDetail 的 :tab(...) 白名单里),新增 /:id/task-board(命名待定)→ 新建 web/src/views/TaskBoard.vue。不要塞进 :tab(...) 正则,理由与 graph-console 当年的注释一致:任务看板信息密度高,值得一个整页。
  2. 复用现有逻辑,不重写数据层:GoalTaskPane.vue + goal-task-model.js 现在已经封装了 goal/task 的状态映射、排序、颜色语义(且有专门的单测 goal-task-model.test.js 兜底)。新页面应该是把这个组件从 GraphConsole 的侧栏布局里"拔出来"放大铺满整页,不是重新实现一遍。GraphConsole 侧栏可以保留一份精简版(点某个 task 仍需跳转去看它的执行图),也可以改成跳转链接指向新页面——两种都行,实现时再定。
  3. 废弃 OKR 前端:删 /okr 路由 + OkrDashboard.vue;删 CompanyDashboard.vue 里那个挂着 "goals" 名字实为 OKR 树的 tab;路由 :tab(...) 白名单里的 goals/okr 两个值一并摘掉,避免留死链接。
  4. 导航入口:新页面必须挂真实导航入口(AppNav 或公司详情页的显眼位置),不能重蹈 /okr 那种"孤儿页面"的覆辙。
  5. 鉴权:okr.js 当年挂 requireAdmin 是因为它是孤儿页面、没做归属校验的权宜之计(38-auth-multi-tenant-isolation.md),不是"任务看板天生该管理员专属"。新路由要按 company.js 等其他公司级路由的标准做法,走 canAccessCompany 归属校验,不能照抄 requireAdmin。

范围之外、本次不动(留作独立的后续决定,不在这次 P2 改造里顺手做):/api/okr/* 后端路由、okr 相关 SQLite 表、goals.okr_node_id 这个可选外键字段——前端 UI 废弃不等于后端子系统也要一起拆,那是一次单独的、影响面更大的退役决定(牵涉数据迁移/存量数据处理),需要单独评估,这里先只处理前端。


P3:心跳挨个决定下一个 task

这条按用户原话实现是在重建一个已经存在、已经测试、且更优的系统。 task-scheduler.js 的队列排序口径是 priority → seq → created_at(ORDER BY priority, seq, created_at),出队靠 transitionRun 这个唯一漏斗事件触发,不是轮询;v1 约束 max_parallel_tasks(company.json,缺省 1)决定"挨个"还是"并发几个",server/tests/unit/task-slots-parallel.test.js 覆盖了 >1 的情况。这套机制零 LLM 成本、毫秒级触发、已经过多轮真实回归验证(31-work-graph-fail-fallback-coverage.md 的真实 LLM 端到端案例)。

成本账:heartbeat-service.js 现在的实现是每一次 tick 都真的发一次 LLM 调用,只是 prompt 要求"没事就回复 [心跳正常]"——省不了那次调用的 token/延迟。如果 N 家公司、每家每 5 分钟一次心跳,24 小时就是 N×288 次 LLM 调用,绝大多数只会换回一句"心跳正常"。

修订建议:boss agent 心跳只负责调度器处理不了的判断题,而且触发方式改成"事件优先、轮询兜底",不是纯定时轮询:

  1. 判断题清单(调度器管不了、需要 LLM 判断的):
    • task 长时间 blocked 或 failed(预算耗尽/依赖死锁)——要不要重新分解、要不要升级给人类;
    • 同一 goal 下多个 task 出现优先级冲突(业务变化导致原定顺序不再合理)——要不要调整 priority;
    • task-planning 3 轮降级成单任务(P3C §四.9 的降级路径)——要不要人工介入重新审视这条 goal 的描述。
  2. 优先事件驱动:在 transitionRun/settleGoal 这类既有钩子里,遇到上述判断题触发的状态变化(比如 task 转 blocked)时,主动唤醒 ceo 处理这一条,而不是让它等下一次心跳才发现。心跳退化成低频兜底(比如 30 分钟一次,只扫"有没有钩子漏掉的异常"),不是主决策路径——这与现有 task-scheduler.js 头部注释里"兜底扫描周期"的设计哲学完全一致,照抄这个模式即可。

P4:org/work graph 产生的新 bug / 新 task 回写看板,形成闭环

这是五个 phase 里唯一真正从零开始、也是这次讨论最初诉求("自己发现问题、自己重新规划")真正要落地的地方。

现有的失败归因机制(30-task-delegation-reliability-gaps.md):BugTriager/validator-triager 类节点已经在做四分类——contract_mismatch / impl_bug / spec_ambiguous / infra_flake——但四条路径的终点目前都是"原地重试"或"预算耗尽后 blocked 等人工",没有一条走向"生成一条新 task/goal"。

需要新增的两个通路:

  1. 确定性通路(不需要 agent 判断):把四分类里"显然该开新任务"的情形直接在 routeFailure(graph-engine.js)里接一条规则,而不是等 boss agent 心跳才处理——比如 spec_ambiguous 在 goal 级预算内重试超过阈值,直接生成一条 status='draft'、created_by='system'、挂在同一 goal_id 下的新 task,附上失败节点的诊断产物作为新 task 的 description 素材,人工/ceo 再审。这条不需要 LLM 判断"要不要开新任务",只需要判断"开一条什么样的"——后者才是可选的 agent 介入点。
  2. 主观通路(需要 agent 判断,这是 P1 的 ceo/工具的用武之地):agent 在执行任务过程中发现的、不在当前 task 描述范围内的问题(比如 coder 顺手发现一个无关的历史 bug),需要一个工具让它"提一条新任务"而不是把不相关的修复硬塞进当前 task 的产物里。这是当前完全没有的能力。

必须先建的地基——查完代码,发现比"包一层工具"更底层的东西还没有,得先排清楚三件事,顺序不能反:

地基 0(阻塞项):ceo 现在物理上调不通这套 REST API

server/src/tools/tool-registry.js 开头写得明明白白——这是"Master Agent 工具注册中心",[TOOL_CALL: xxx] 这套 JSON 文本协议是 Master 专属的自定义解析层,server/src/tools/devloop/ 下另有一套 DevLoop 专属的 tool-registry。ceo 这类普通 org node agent(走标准 Claude CLI 子进程)两套都不属于——它能用的只有 Claude CLI 原生的 Bash/curl,具体调用哪个接口靠 tools.md 里的大白话 + curl 示例(task-planning/tools.md 就是这个写法的范本)。所以"task-board-tools.js" 这个提法本身不成立——不需要新写一个 JS 工具模块,需要写的是一份 system/agents/ceo/tools.md(现在是空文件)。

但更麻烦的是:server/routes/company.js 顶部 router.use(requireAuth)(14 行),goals/tasks 全系列端点只认人类 JWT,没有走 agent-chat.js 那样的 requireAuthOrInternal。也就是说,就算 ceo 的 tools.md 写得再clear,它发出的每一条 curl 现在都会静默 401——这正是最近几个 commit(d48425a selfChat 自环漏带内部令牌、9e58656 bin/lemon.js task report/dispatch 401)反复修的那一类坑,company.js 这次还没打这个补丁。这是全部 P4 工作的前提阻塞项,必须先给 goals/tasks 这组路由加上 requireAuthOrInternal(或者更收紧一点,只给这几条挂内部令牌白名单),agent 才有物理上调得通的可能。

地基 1:POST /:companyId/tasks 没有"草稿"模式,agent 提的任务会立刻烧预算

现有 POST /:companyId/tasks(company.js:2077)建完 task 行无条件紧接着调 enqueueTaskRun + startNextIfSlotFree——语义是"下达即执行",服务的是"人类/外部 agent 派工"这个存量场景(代码注释原话:"存量调用方的心智是下达即执行")。这条路径不适合 ceo 自主提的新任务:task-planning 拆出来的 task 尚且要先落 draft、人点"全部下达"才转 queued(P3C §四.9 的既定哲学——"一次 LLM 猜测直接烧 5 份预算"是要避免的事),ceo 一次判断失误直接把新任务怼进队列烧钱,风险比 task-planning 更高(task-planning 只在 goal 刚建时跑一次,ceo 是持续在线判断)。

需要的改动很小:给 POST /:companyId/tasks 加一个 draft: true(或 status: 'draft')入参,为真时只调 wdb.createTask() 落库,跳过 enqueueTaskRun/markGoalDispatched/startNextIfSlotFree 三步,返回 status: 'draft'。已有的 POST /:companyId/tasks/:taskId/enqueue(company.js:2275,draft → queued)原样复用,不用新建。默认行为(不传这个参数)完全不变,纯加法,不影响存量调用方。

地基 2:ceo 的 tools.md 写什么

在地基 0/1 落地后,照抄 task-planning/tools.md 的写法(先说"大概率不需要工具"、给出何时才用、给出 curl 示例、专辟一节"你不该用的工具"防止越权)给 ceo 写一份,覆盖:

  • 读:GET /:cid/goals?status=active(扫哪些 goal 在跑)、GET /:cid/tasks?status=blocked / ?status=failed(找卡住的)、GET /:cid/goals/:goalId(看某 goal 下全部 task + run 摘要)
  • 写:PATCH /:cid/tasks/:taskId(改 priority)、POST /:cid/tasks(带 draft:true,提新任务)、POST /:cid/tasks/:taskId/enqueue(把自己或别人提的草稿正式下达——这一步要不要也留给人类点,是个开放问题,见 §四)
  • 明确不该用:org-graph/replan、招聘类端点(这些是 HR/orggraph-planning 的事,ceo 判断"缺角色"之后应该是发通知/提 task,不是自己动手改组织结构)

确定性通路(routeFailure 里那条规则)不走这套 REST

30 号文档四分类里"显然该开新任务"的情形(比如 spec_ambiguous 重试超阈值),是 graph-engine.js 服务端内部代码,直接调 wdb.createTask()(同进程函数调用),不必经过 HTTP、不必带内部令牌,落库同样是 draft。这条通路和 ceo 的 agent 通路是并行的两条腿,共享同一个"落 draft、不自动下达"的规则,但触发方式不同(一个是引擎钩子里的确定性规则,一个是 agent 主观判断)。

先做地基 0(鉴权)→ 地基 1(draft 模式)→ 地基 2(tools.md),P1/P3 的 boss agent 才有事可干——三步不做完,ceo 被心跳唤醒了也只能干瞪眼,或者干瞪眼都算好的,更可能是每次尝试都吃一个静默 401。


P5:公司级目标代码目录("接管已有工作目录")

核实结论:这个能力在 DevLoop 层面已经完整实现,只是没有公司级默认值,也没有确认是否覆盖非 DevLoop 的执行路径。

server/services/devloop/path-resolver.js 的 allocateProjectRoot() 已支持三种模式(server/routes/devloop.js 建项目时传 mode/target_path):

mode 语义 现状
sandbox 自动分配到 data/companies/{cid}/projects/{pid}/(lemonboss 内部) ✅ 已实现,默认值
linked-new 用户指定一个空/新建的绝对路径作为项目根,登记进 ~/.devloop/linked-projects.json ✅ 已实现
linked-existing 用户指定一个已存在的目录,接管为项目根 ✅ 已实现,不是文件头注释说的"预留接口拒收"——代码实测有完整校验:路径必须存在且是目录、不能已经是别的 DevLoop 项目(project.json 冲突检测)、若有 .git 必须 working tree 干净(防止 agent 的 git add -A 把用户脏改动卷进自动 commit)。文件头那句注释是过期文档,建议顺手修正。

这正是用户要的"像 Claude Code 选工作目录一样接管一个已有目录",核心机制已经有,且安全校验(黑名单敏感路径、git 干净树)已经比较完善。

org/work graph(非 DevLoop)路径已核实,结论比预期更好也更麻烦,分开说:

好消息:不是两套系统。graph-engine.js 有自己的 ensureProjectRoot(ctx)(3476 行),文件头注释写明"位置的分配复用 DevLoop 的 path-resolver.allocateProjectRoot(sandbox 模式),不新造一套"——allocateRunProjectRoot()(3458 行)内部真的就是调 require('./devloop/path-resolver').allocateProjectRoot({ company_id, mode: 'sandbox' })。org-coder 类节点的交付物落点、graph_files 索引、34号文档 提到的"项目根",指的都是这同一套机制,不是 locator.taskDir(那是 agent 自己的运行 cwd,_applyRuntimeScope 分配的,两者不是一回事——agent 在 locator.taskDir 里跑,但写产物是写去 ctx.projectRoot,这个区分在 graph-engine.js 3736 行的"越界写入"检测里能看到:写到项目根之外会被判越界)。结论:P5 只需要改一处入口(allocateRunProjectRoot),不需要在 org graph 侧另建一套。

坏消息:现在的分配粒度是"每个 root run 一份全新 sandbox",不是"每个公司一份持久目录"。allocateRunProjectRoot() 硬编码 mode: 'sandbox',且没有任何"复用已有项目根"的入参——ensureProjectRoot() 的幂等只在同一个 root run 内成立(先读 project-root.json 再决定要不要新分配),跨 run(也就是跨 task)永远分配一个新 pid,哪怕是同一个公司、同一个 goal 下的两个 task,也会落进两个互不相干的空目录。这比"没法接管外部目录"更根本——现在同一家公司自己的两个任务都不共享代码,遑论接管一个外部已有仓库。

P5 真正要做的三件事(比原先设想的"补个配置项"要深一层):

  1. 公司级配置:company.json 加 target_dir(绝对路径,可空)+ target_mode(linked-new / linked-existing,配了 target_dir 才有意义)。
  2. 改 allocateRunProjectRoot(run):读 run.company_id 对应的 company.json.target_dir。配了就不再"每 run 分配新 pid",而是所有该公司的 run 一律解析到同一个 root_path(跳过 path-resolver 的 pid/registry 那一整套,直接返回固定路径,首次访问时按 target_mode 做一次 linked-existing/linked-new 的校验——现成的校验逻辑,path-resolver.js 261-310 行原样复用,只是只做一次,不是每个 run 各做一次);没配 target_dir 就走现在的 sandbox 老路径,行为不变。
  3. ⚠️ 并发风险,必须在设计里显式处理:一旦多个 task 共享同一个 target_dir,就与前面讨论过的"跨 task 并发"(max_parallel_tasks > 1)正面相撞——两个 coder agent 同时写同一个 git 仓库会冲突,linked-existing 现有的"进入时 working tree 必须干净"只是一次性校验,不是运行期互斥锁。建议 v1 做法:company.json.target_dir 非空时,该公司的 max_parallel_tasks 强制钉死为 1(在 maxActiveRootRuns() 里加一条:读到 target_dir 就忽略/覆盖用户配的 max_parallel_tasks),从根上排除并发写冲突,比引入 git worktree/分支合并那一整套复杂度便宜得多。ponytail: 这是刻意的简化,天花板是"一家公司同一时间只能有一个任务真正动代码";真要多任务并发写同一个仓库,需要 git worktree + 完成时合并这一层,属于独立工作量,不在这次范围内。

建议:P5 独立于 P1-P4 推进(数据面完全不相关),优先级可以更高——但工作量比最初评估要大一档,不是"公司级配置 + 一次核实"就完,是"公司级配置 + 改分配粒度 + 并发约束联动"三件套。


三、修订后的建议顺序

用户原始顺序是 P1→P2→P3→P4,本文档建议倒过来做地基:

第一步:P4 地基 —— 建 task-board-tools(读写 goal/task 的 agent 工具)
         + routeFailure 里接确定性的「四分类 → 自动开新 task」规则
第二步:P1 收窄版 —— 给 ceo 配 heartbeat.md(监督规则,认新 goal/task 模型不是旧 OKR)
         + 挂上第一步的工具
第三步:P3 收窄版 —— 心跳只做异常判断,事件触发优先、轮询兜底、绝不重做调度
第四步:P4 完整版 —— 把「主观判断要不要开新 task」的部分交给已经装好工具的 ceo
第五步:P2(UI) —— 独立任务看板页面,随时可插入,风险最低
并行轨:P5 —— 公司级默认 target dir 配置 + 核实 org graph 交付物落点是否需要同样机制

理由:P1/P3 的 boss agent 在 P4 地基建好之前没有可操作的对象;P2 是纯 UI,不卡任何人;P5 数据面独立,可以完全并行推进。


四、开放问题(已拍板,2026-08-30)

  1. ceo 会不会和已有流水线角色冲突 已核实排除:system/org-graph-templates/{dev,content,ops}.json 三份模板全文搜索 ceo,零命中——ceo 现在没有出现在任何一张图里,是完全没被使用的孤儿本体。给它加监督职责没有冲突。
  2. P4 确定性通路新开 task 的 report_to:继承原 goal 的 report_to。okr_node_id 不用单独决定——它按 P3C §四.7 既定规则只挂在 goals 表,新 task 挂在同一个 goal_id 下自动沿用该 goal 的 OKR 归属,不存在"新开一份"的问题。
  3. 心跳成本策略:事件触发为主 + 低频兜底轮询(不加"异常持续够久才唤醒"的抖动过滤,先简单落地)。主路径挂在 transitionRun 等既有钩子上,异常状态一出现就唤醒;另加一个量级在 30 分钟左右的低频轮询兜底,防钩子覆盖不到的漏网情况——直接照抄 task-scheduler.js 头部注释里"兜底扫描周期"那个既有设计哲学,不重新发明。
  4. ceo 可以全权自主 enqueue 自己提的 draft task,不需要等人类点——这是五个选项里最激进的一档,明确高于 task-planning(§四.9 定案:产出永远先落 draft,人点"全部下达")的自主权限。这个选择意味着安全网必须落在别处,不能靠"人工审核"这一层:落地时要确保 max_parallel_tasks(防止一次性并发太多)、DEFAULT_TASK_BUDGET(防止单任务无限烧钱)、goal 级 org-validator 收敛验收(防止"完成"了但没真正达成)这几道既有闸门都乘上了 ceo 自主创建的 task,不能因为创建路径变了就绕开——这几道闸门本来就是数据层/调度器层强制的(不挂在"谁创建的"这个字段上),照理天然覆盖,但落地时要专门写一条回归测试钉死这一点。
  5. P5 org graph 交付物落点调研:已做完,结论见 §二 P5 小节——不是两套系统(复用同一个 path-resolver),但分配粒度是"每个 task 一份全新 sandbox",同公司任务之间都不共享代码,比预想的工作量更大,且与"跨 task 并发"直接联动,需要一条并发约束兜底。
  6. P4 确定性通路的实际挂载点:已实现并确认(2026-08-30)。文档原先设想挂在 DevLoop 的四分类(spec_ambiguous/contract_mismatch)上——但那是 DevLoop 专属词汇,graph-engine.js 的 routeFailure 用的是另一套(impl_bug/input_underspecified/skill_defect/infra_flake),根本没有这两个取值。实现时改挂在 handleBackwardEdge 的双层硬顶熔断(单边 loop_count>3 或 run 级 backward_count>5 → force_terminated)上——任何原因导致的反复回退不收敛都触发,不按 fail_reason 精筛。已确认维持这个实现,不额外收窄。副作用是良性的:infra_flake 走的是完全独立的原地重跑代码路径(不经过 handleBackwardEdge),结构上天然不会触发这条自动开新任务的规则,不需要专门加排除逻辑。落点函数:graph-engine.js 的 maybeProposeReplanTask(),挂在 handleBackwardEdge 熔断分支里(约 2131-2133 行)。

六、已知遗留(实现后核实,2026-08-30)

代码落地后回查,发现两个当初设计里提过、但没有专门验证/没做完的点,记在这里而不是假装文档已经全部兑现:

  1. §四.4 定案的"回归测试钉死闸门"没有单独落地。ceo 自主 draft + enqueue 走的是和人类调用完全相同的 POST /:cid/tasks → POST /:cid/tasks/:taskId/enqueue 两个端点(max_parallel_tasks/DEFAULT_TASK_BUDGET/goal 级 org-validator 这几道闸门都挂在数据层/调度器层,不认"谁创建的",理论上自动覆盖),但没有一条测试专门断言"ceo 创建的 task 一样会被这几道闸门拦下"——现有测试(task-entry-draft-mode.test.js、task-blocked-ceo-wake.test.js)验的是 draft 模式本身和唤醒机制,不是闸门穿透性。理论上没有绕过的口子,但没有测试钉死就不能说"已确认"。
  2. company.json.target_dir/target_mode 没有任何 API/UI 入口去设置——现在只能手工编辑公司态目录下的 company.json 文件才能配置。P5 落地的是"读取并生效"这一半,"人怎么把这两个字段填进去"这一半还没做(哪怕只是一个 PATCH /api/company/:cid 端点)。
  3. server/routes/graph-run.js 漏了同一处鉴权修复,已在真实跑动中撞出来并修复(2026-08-30)。地基 0 当时只改了 company.js(goals/tasks 端点),graph-run.js(run 的 start/stop/retry/budget 等控制端点)还是纯 requireAuth。真实端到端测试里 ceo 被唤醒后能读写 goal/task 板,但调 /api/graph-runs/* 处理卡住的 run 时精确撞上 401,自己在对话记录里诊断出这一点并写出了人工需要跑的 curl 命令。已修:router/companyRouter 两个挂载点都改成 requireAuthOrInternal,两处 :companyId/:id 的 param 中间件加了与 company.js 一致的 req.user?.internal 旁路,route-auth-gate.test.js 同步更新断言,全量测试零回归(1469/1473,剩余 4 个是核实过的 pre-existing 失败)。这次的教训:地基 0 的"这一步做完了"判据不能只看 company.js 一个文件——ceo 需要触达的控制面横跨两个路由文件,遗漏了没有靠代码审查发现,是靠真实跑一遍才暴露的。