39. Boss Agent 任务看板监督 + 公司目标代码目录 — 需求评估与设计草案
状态:✅ P1-P5 已实现(2026-08-30,commit
a00590aP1/P3/P4 后端、5108d67P2 前端、d967ac0P5)。本文档是落地前的设计草案,实现细节以代码为准,本文档不再更新,仅存档决策过程。已知未覆盖的后续工作见文末新增的「六、已知遗留」。 日期: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 层训练通用版本
已有的基础设施(全部实地核实过):
- 通用心跳机制:
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的那份。
- ✅ 修正一处之前的误判:原以为 prompt 里"扫描全局 OKR 目标状态和节点"那句硬编码需要改代码才能适配新 goal/task 模型。查了
- 本体已存在:
system/agents/ceo/(type: "cxo","Chief Executive Officer",heartbeat.enabled: false默认关)。同时存在system/agents/master/(type: "master",全局唯一,/api/master/chat,负责建公司/招聘等跨公司操作)。两者定位不同:master是平台层面的单例助手,ceo才是"每个公司一个"的角色。 - 训练方法论已验证:
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*直接废弃,新任务看板另起一个独立页面*。具体动作:
- 新增路由:仿照
GraphConsole已经立好的先例(/:id/graph-console是独立路由,不挤在CompanyDetail的:tab(...)白名单里),新增/:id/task-board(命名待定)→ 新建web/src/views/TaskBoard.vue。不要塞进:tab(...)正则,理由与graph-console当年的注释一致:任务看板信息密度高,值得一个整页。 - 复用现有逻辑,不重写数据层:
GoalTaskPane.vue+goal-task-model.js现在已经封装了 goal/task 的状态映射、排序、颜色语义(且有专门的单测goal-task-model.test.js兜底)。新页面应该是把这个组件从GraphConsole的侧栏布局里"拔出来"放大铺满整页,不是重新实现一遍。GraphConsole侧栏可以保留一份精简版(点某个 task 仍需跳转去看它的执行图),也可以改成跳转链接指向新页面——两种都行,实现时再定。 - 废弃 OKR 前端:删
/okr路由 +OkrDashboard.vue;删CompanyDashboard.vue里那个挂着 "goals" 名字实为 OKR 树的 tab;路由:tab(...)白名单里的goals/okr两个值一并摘掉,避免留死链接。 - 导航入口:新页面必须挂真实导航入口(
AppNav或公司详情页的显眼位置),不能重蹈/okr那种"孤儿页面"的覆辙。 - 鉴权:
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 心跳只负责调度器处理不了的判断题,而且触发方式改成"事件优先、轮询兜底",不是纯定时轮询:
- 判断题清单(调度器管不了、需要 LLM 判断的):
- task 长时间
blocked或failed(预算耗尽/依赖死锁)——要不要重新分解、要不要升级给人类; - 同一 goal 下多个 task 出现优先级冲突(业务变化导致原定顺序不再合理)——要不要调整
priority; task-planning3 轮降级成单任务(P3C §四.9 的降级路径)——要不要人工介入重新审视这条 goal 的描述。
- task 长时间
- 优先事件驱动:在
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"。
需要新增的两个通路:
- 确定性通路(不需要 agent 判断):把四分类里"显然该开新任务"的情形直接在
routeFailure(graph-engine.js)里接一条规则,而不是等 boss agent 心跳才处理——比如spec_ambiguous在 goal 级预算内重试超过阈值,直接生成一条status='draft'、created_by='system'、挂在同一goal_id下的新 task,附上失败节点的诊断产物作为新 task 的description素材,人工/ceo再审。这条不需要 LLM 判断"要不要开新任务",只需要判断"开一条什么样的"——后者才是可选的 agent 介入点。 - 主观通路(需要 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 真正要做的三件事(比原先设想的"补个配置项"要深一层):
- 公司级配置:
company.json加target_dir(绝对路径,可空)+target_mode(linked-new/linked-existing,配了target_dir才有意义)。 - 改
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.js261-310 行原样复用,只是只做一次,不是每个 run 各做一次);没配target_dir就走现在的sandbox老路径,行为不变。 - ⚠️ 并发风险,必须在设计里显式处理:一旦多个 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)
已核实排除:ceo会不会和已有流水线角色冲突system/org-graph-templates/{dev,content,ops}.json三份模板全文搜索ceo,零命中——ceo现在没有出现在任何一张图里,是完全没被使用的孤儿本体。给它加监督职责没有冲突。- P4 确定性通路新开 task 的
report_to:继承原 goal 的report_to。okr_node_id不用单独决定——它按 P3C §四.7 既定规则只挂在goals表,新 task 挂在同一个goal_id下自动沿用该 goal 的 OKR 归属,不存在"新开一份"的问题。 - 心跳成本策略:事件触发为主 + 低频兜底轮询(不加"异常持续够久才唤醒"的抖动过滤,先简单落地)。主路径挂在
transitionRun等既有钩子上,异常状态一出现就唤醒;另加一个量级在 30 分钟左右的低频轮询兜底,防钩子覆盖不到的漏网情况——直接照抄task-scheduler.js头部注释里"兜底扫描周期"那个既有设计哲学,不重新发明。 ceo可以全权自主enqueue自己提的 draft task,不需要等人类点——这是五个选项里最激进的一档,明确高于task-planning(§四.9 定案:产出永远先落 draft,人点"全部下达")的自主权限。这个选择意味着安全网必须落在别处,不能靠"人工审核"这一层:落地时要确保max_parallel_tasks(防止一次性并发太多)、DEFAULT_TASK_BUDGET(防止单任务无限烧钱)、goal 级org-validator收敛验收(防止"完成"了但没真正达成)这几道既有闸门都乘上了ceo自主创建的 task,不能因为创建路径变了就绕开——这几道闸门本来就是数据层/调度器层强制的(不挂在"谁创建的"这个字段上),照理天然覆盖,但落地时要专门写一条回归测试钉死这一点。- P5 org graph 交付物落点调研:已做完,结论见 §二 P5 小节——不是两套系统(复用同一个
path-resolver),但分配粒度是"每个 task 一份全新 sandbox",同公司任务之间都不共享代码,比预想的工作量更大,且与"跨 task 并发"直接联动,需要一条并发约束兜底。 - 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)
代码落地后回查,发现两个当初设计里提过、但没有专门验证/没做完的点,记在这里而不是假装文档已经全部兑现:
- §四.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 模式本身和唤醒机制,不是闸门穿透性。理论上没有绕过的口子,但没有测试钉死就不能说"已确认"。 company.json.target_dir/target_mode没有任何 API/UI 入口去设置——现在只能手工编辑公司态目录下的company.json文件才能配置。P5 落地的是"读取并生效"这一半,"人怎么把这两个字段填进去"这一半还没做(哪怕只是一个PATCH /api/company/:cid端点)。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需要触达的控制面横跨两个路由文件,遗漏了没有靠代码审查发现,是靠真实跑一遍才暴露的。