• 记三件这一批换来的东西:
    
    §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>
    yibo authored
     
    Browse File »
  • 记两条这一批换来的东西:
    
    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>
    yibo authored
     
    Browse File »

  • ## 批 4:解除「一家公司同时只有一个 active root run」
    - DROP idx_graph_runs_one_active_root(建表块删掉 + 迁移块显式 DROP,
      CREATE ... IF NOT EXISTS 对存量库不生效)
    - max_parallel_tasks 从 company.json 调用时读(不是模块加载时快照,
      否则改配置要重启才生效且不报错);缺省 1、非法回落 1 并留声、硬上限 20
    - 准入判据「从活跃集外进入活跃集」按额度计数
    
    **生死线用 8 个真进程并发抢坑实测**(不是纸面论证):max=1/2/3/5 四组,
    每组恰好抢到 max 个、库里活跃数 = max,落选进程各吃 40 次 RunSlotBusyError,
    零 other 错误。唯一索引没了之后,准入事务是真守卫。
    
    ## 批 3 对抗复核的四条遗留
    - N1 🔴 cancelQueuedRun 的前提被批 3 自己的 requeueBlockedRootRun 改没了:
      queued 的 run 现在可以是跑过一半的。探针实测取消后子 run 残留
      paused/stop_requested=0 —— 那条 running 的子 run 继续调 agent 烧 token。
      改为无条件走 engine.stopRun
    - L1 ⚠️ 恢复路径对 manual 只兑现一半:注释写着「T1 永远回不来这个后果因此
      消失」,但调度器故意不碰 manual → 退回队列后没人接。定案:「manual 不自动
      出队」约束的是首次起跑的授权,已被人亲手起过的不算新授权。
      判据用 attempt>0 而不是 started_at —— 后者会被 requeue 清成 NULL,
      恰好在需要它的那一刻是空的(实测踩过)
    - N2 🟠「退回队列」对用户完全不可见:200 + ok:true + 按钮全灰 + 图消失 +
      一句「节点状态一律 pending」的假提示。加 notice 明说
    - N3 🟠 isSlotRaceError 把 InvalidTransitionError 当竞态吞掉,而探针证伪了
      那条注释:被推走时条件更新返回 0 不抛,真正抛它的只有「调用方声明了非法
      转换」。删掉该分支,让它吵起来
    
    四条里三条是同一个模式:注释承诺的前提被本轮自己的改动改没了,注释还留着。
    文档 §十三.3 / §十四.4 已就这个模式立过两次案,这是第三、四、五次。
    
    ## 文档
    §十五 实施记录,含 §15.4 一次流程事故:87b776c 由第三方会话在对抗复核完成
    前误提,提交说明的「1201 用例全绿」压在一份尚未通过复核的代码上。
    
    全量:128 文件 / 1240 测试全绿,web:build 通过。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »

  • goal 从「人写一句话」变成「agent 拆成一张 task DAG、人确认后下达」,
    收敛时由 org-validator 在 goal 层再验一次。
    
    ## 交付
    - task-planning 本体(毕业判据实测通过:agent.md + soul.md 一次写完、
      不带 _base 哨兵、路径按 cwd 深度 3 实算核对)
    - 拆分链路:3 轮 errors 原样回喂 → 不过就降级成 1 条同描述 task,
      goal 绝不卡在 planning;token 记 goals.planning_tokens
    - goal 级 org-validator 验收:≥2 条 task 全 completed 才调,单 task 跳过,
      有 failed 直接 failed 不调(省一次真花钱的调用)
    - 控制台四栏 + GoalTaskPane 两级树 + 两步表单(拆分中给逃生口)
    - 中断恢复:启动时补跑验收与汇报
    
    ## 三条跨进程竞态,全部用真探针实跑复现后才判定修好
    1. runGoalSplit 无条件把 goal 写回 draft,抹掉逃生口刚下达的 active ——
       而触发它的正是表单自己推荐的那个逃生口。后果是一个正在烧钱的 goal
       显示成「草稿」,且从此永不收敛(OKR 不写、上级不唤醒、无日志)
    2. CAS 把最终态提前写进库,验收窗口内重启即丢,且 verdict=NULL 与
       「单 task 合法跳过」形态相同,查不出来 → 占位判词 + 启动重推
    3. 认领语句 SET verdict=RESUMING WHERE verdict IN (PENDING, RESUMING)
       —— 写进去的值就在 WHERE 里,changes 恒为 1。而函数注释白纸黑字
       写着「认领写的是另一个占位值」。注释是对的,代码没兑现。
       实测:org-validator 调 2 次 / OKR 写 2 条 / token 双记
       → claim 带唯一 token + 时间戳租约,回写条件更新决胜,主路径同闸门
    
    ## 与设计正文的偏离(已记进文档 §十四)
    拆分后 goal 落 draft 而非 active(active 的语义已被 markGoalDispatched
    钉死为「已下达」);mode 三级继承对显式 tasks[] 同样生效;占位判词不出
    API;新增 planning_error / reported_at 两列,后者迁移带回填(否则升级后
    第一次启动会把历史终态 goal 全部重报一遍)。
    
    ## 过程
    四轮 26 个 agent,复核报出 22 条缺陷,其中 6 条是修复自己引入的。
    第四轮修完挂了 3 条测试,全是测试在钉住缺陷本身 —— 其中一条的注释
    写着「模拟进程死后重启」,构造的却是新鲜占位值:注释描述 A 场景、
    代码构造 B 场景、断言按 A 场景写。
    
    全量:118 文件 / 1112 测试全绿,web:build 通过。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 新增 §十三,体例照 27-P3/P4 §十:设计正文不改写,只在这里与实现对账,
    冲突处以实施记录为准(正文是设计时的判断,本节是跑出来的事实)。
    
    记了与设计的四处偏离,其中两处是设计正文写错了:
    
    ① blocked 在 task/goal 两层都不是终态。正文 §四.3 把它与
       completed/failed 并列画成终态,实现时实跑复现出后果:预算耗尽后
       加预算恢复、跑完,两次条件更新静默命中 0 行,最终 run=completed
       而 task=blocked/goal=failed/OKR=failed 永久对不上。RUN_TRANSITIONS
       里 blocked: ['running','stopped'] —— 对 run 它是可恢复态,
       reviveParentFromChild 还会自动拉回。
    
    ② goal 级是唯一汇报出口,reportRunOutcome 已删除(零其它调用方)。
       1 goal:1 task 形态下 run 级与 goal 级会把上级就同一件事唤醒两次。
       from 的口径同时修正为实际交付者(terminalAgentId 搬进
       org-graph-service 当唯一持有者),不再用 created_by ——
       那是「谁下的单」,默认字面量 'human',下游会静默落空。
    
    ③ dispatch 挑第一条可跑的 draft,连带补上 PATCH 改 seq —— 原先 409
       的 hint 写着「改依赖或 seq」,而那个端点从不读 body.seq。
    
    正文里三处提到 reportRunOutcome 的地方一并标注:§四.7 加了「本节写于
    实施之前」的前置说明,§十二(批 5)的前提改成「函数换了、依赖关系
    没变」—— A 的回调链路仍长在 B 身上,只是出口从 company.js 的 run 级
    换成了 task-settle 的 goal 级。
    
    过程也记了:三轮编排 21 个 agent,对抗复核报 19 条,其中 4 条是
    第二轮修复自己引入的。结论:对抗复核不能只做一轮 —— 修复和实现
    一样会出错,而且更隐蔽,它长得像在解决问题。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 并行会话指出 graph-engine.js 的 indexNodeOutputs 注释里早就写着
    「goal-contract 零调用、setPlanWorkGraph 零调用、三值透传只接了读
    那一半」。顺着查下去,这个模式在仓库里被记录过至少 7 处,而且证据
    本身就是结论:
    
    - graph-engine.js 跨层判据处:「(『实现在、零调用方』的第五次)」
    - graph-run-route-io.test.js 文件头:「这一路踩了十几次的
      『实现在、零调用方』换个方向再来一次」
    - graph-run.js:243:「等于又一次『实现在、零调用方』」
    - 27-goal-contract-format.md:101:「T0 交付后有四轮时间里,全仓库
      唯一提到 goal-contract.js 的是一句注释 —— 实现完整、零调用方、
      零单测,验收 1 从未被执行过」
    
    有人数到第五次,有人写「十几次」,然后它继续复发。这些记录证明的
    不是「有这个问题」,而是「发现了也没人回来收」。
    
    本文档的三处要求是同一个惯犯的三个实例:砍掉 goals.budget_json
    (§四.1)、report_to 必须落库(§四.7 禁令二)、处理 goal-contract
    那个声明了却零使用的 RUNS_ROOT(验收 12c)。写明它们是硬要求,
    不是可以留到下一轮的整洁度问题。
    
    批 1 一并加不变式测试「凡是声明了 GRAPH_RUNS_DIR 覆盖的模块,该
    覆盖必须真的被用到」,形状照抄 agents-root-single-holder.test.js。
    「样本非空」前置断言别省 —— 扫不到文件时会静默全绿,那正是这类
    测试自己变成零调用方的方式。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 并行会话指出我上一版归因错了,逐条复验后确认它是对的:
    
    【纠正】runRootDir 与 companyRootDir 不是同一事实的两种拼法,是两个
    不同的事实:
      runRootDir     → run 目录,覆盖态 {RUNS_ROOT}/{rootRunId},
                       扁平即可(rootRunId 全局唯一)
      companyRootDir → 公司根,覆盖态 {RUNS_ROOT}/_companies/{cid},
                       那层 _companies 是必须的(公司 id 不是全局唯一坐标,
                       扁平放会和 run 目录撞名)
    company-paths.js 的注释本身写着「理由与 runRootDir 那处 override
    一致」,作者是有意区分的。我上一版写成「三种拼法争权威、实施前先定
    哪个权威」,会让批 1 实施者去做一个根本不存在的设计决策。真正错的
    只有一处:writeTaskGoalArtifact 自己拼路径。修法一行级。
    
    【新增缺陷 B】goal-contract.js:75 声明了 RUNS_ROOT、注释还写着「与
    graph-engine.RUNS_ROOT 是同一个变量、测试隔离在用」——然后全文件
    一次都没用过,同文件的 resolveRunDir 照旧自己拼。而且 resolveRunDir
    全仓库零调用方(只有定义、exports 和一行文档)。
    
    它今天不会出错,危害是注释在撒谎:声明了意图、代码不兑现的常量比
    没有这个常量更容易骗人,第一个接上去的人直接踩。批 1 一并处理 ——
    要么真的用上,要么连函数带常量删掉(零调用方,删更省)。
    
    【验收】12b 补上必须「先设 env 再 require」的姿势要求,12c 新增。
    第六个实例登记:graph-engine:84 / goal-contract:75 / graph-run-sse:34
    三处都在模块加载时快照 GRAPH_RUNS_DIR,而 company-paths:14 的注释
    恰恰记着相反的教训,既有测试已在为此做变通。教训在一个模块里学会
    了没传出去 —— 不是事实被复述,是「事实该怎么读取」的约定只传一半。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 并行会话把 system/agents 那一族收敛到 locator.AGENTS_ROOT 之后
    (e120645,13 处跨 5 文件,我复验 3/3 通过),顺着同一个模式查
    company.js 里的 data/companies 那一族,撞见一个真的分叉:
    
      写 company.js:writeTaskGoalArtifact
         path.resolve(__dirname,'../..','data','companies',...)   不认 GRAPH_RUNS_DIR
      读 graph-engine.js:runRootDir(describeTask / goal-contract 都走它)
         RUNS_ROOT ? path.join(RUNS_ROOT, root_run_id) : ...      认
      三 company-paths.js:companyRootDir
         runsRoot ? path.join(runsRoot,'_companies',cid) : ...    认,但形状又不同
    
    同一个事实三种拼法。GRAPH_RUNS_DIR 一旦设上(目前只有测试在用),
    写进 A 处、从 B 处读,describeTask 拿到 null 后静默退回兜底文案
    「执行图节点任务」—— agent 从此拿不到真实任务描述,且没有任何报错。
    goal-contract.test.js 的注释佐证读取方那套形状才是约定。
    
    生产环境两边恰好都退到同一个默认分支,所以今天不发作。但批 1 会把
    artifact 落盘点从「下达时」前移到「入队时」,正好要动这个函数 ——
    修的时机就是那时。新增验收 12b 专门验它。
    
    方法论也记一条:我第一次只 grep 了 TEMPLATES_DIR 这个名字,因此漏
    掉同一文件里语义相同但叫 DATA_DIR 的 3 处。同义异名的副本按名字
    grep 永远找不到,要按值 grep。
    
    这是本文档攒到的第五个同类实例。同一个事实只能有一处持有者 ——
    路径也是事实。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 并行会话提醒 company-service.js 的 TEMPLATES_DIR 名字过期(实指
    system/agents)。查调用点时发现更值得记的是 company.js:同一个
    路径常量在这个文件里被重复定义了 7 次,6 处硬编码
    path.resolve(__dirname,'../../system/agents'),只有 1 处用了权威
    访问器 locator.AGENTS_ROOT。
    
    这个路径 2026-08-18 刚被 agent 目录重构搬过一次 —— 下次再搬,
    改 6 处漏 1 处就是静默 bug。批 1 本来就要大改 company.js(全部新
    端点挂这里),顺手收敛成模块级一份,不是为改而改。
    
    这与本文档反复点名的是同一个病:canDispatch 的取值域被前端复述过
    一份、ACTIVE_ROOT_RUN_STATUS 在两个文件里逐字重复、
    isUngraduatedStatus() 的取值域不许在别处复述。同一个事实只能有
    一处持有者,路径也是事实。
    
    同时登记一条刻意不做的:company-service.js 那个 TEMPLATES_DIR
    改名要动 50 处、跨 6 个文件、零功能收益,不值得。留记录是为了
    说明它是已知的,不是没人看见。
    
    顺带独立复验:template-refs.test.js 4/4 通过,27-P3/P4 的
    blocker 21(CI 因骨架悬空引用而红)确实已关闭。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 刚提醒过并行会话「27-graph-agent-architecture 的状态行落后两个阶段,
    会连累每一次迁移判断」,回头看自己这份也有同类问题:文档名叫 p3c、
    自述「P3 收尾」,但内容已经溢出 P3 的边界。
    
    补清楚三件事:
    - 批 1-3 才是真正的 P3 收尾,是本文档主体
    - 批 4(并行)归 27-P4 §一.3,那边是设计权威(§五.2 已有警示块)
    - 批 5(两套任务体系统一)不属于任何既有 phase,是跨阶段技术债清理
    
    以及最要紧的一行:五批全部未开工,0 行代码。判断能否搬 done 时
    按批看,别按文档名看。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 27-P4 §一.3 的目标逐字写着「解除『一家公司同时只有一个 active
    root run』约束 —— P3 v1 用一个唯一索引强制它,并行能力具备后应
    放开」。本文档的批 4 在做同一件事。
    
    划清边界:放开约束的设计归 27-P4,本文档只负责在它之上定义
    「谁来排队、按什么序出队」。两边各写一版会产生两套并发守卫 ——
    而这次要删的正是最后一道 DB 兜底,守卫分裂的代价特别高。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 并行的 agent 重构那条线同步来的,已核实三个函数都在:
    - agent-locator.js  missingBodyDefinition()   判什么算没填完
    - graph-service.js  promoteDraftIfComplete()  转正动作
    - graph-service.js  isUngraduatedStatus()     hire() 挡人的判据
    
    关键是最后一个只挡 draft / training 两个状态,不是「≠ active 就挡」。
    库里有 36 个本体的 status 是 enabled/idle/ready/trained,按「不等于
    active」判会被全部误判成没填完 —— 这个坑已经被踩过一次。
    
    取值域的唯一持有者是 graph-service,不要在别处复述 —— 与本文档
    §六.3 记的 canDispatch 教训(前端复述了一份取值域还多写一个
    blocked,导致公司失败一次后再也下不了任务)是同一个病。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 另一条工作线正在做 agent 目录结构重构(system/templates 并入
    system/agents),约 900 个变更尚未提交。逐条核实交集:
    
    好消息 —— 本文档引用的全部函数/常量(settleRun / markRunReady /
    stopRun / defaultPlanWorkGraph / PLAN_MAX_ROUNDS /
    writeTaskGoalArtifact / reportRunOutcome / ACTIVE_ROOT_RUN_STATUS /
    saveTaskMeta / getAggregatedTasks)在 650 行改动后全部存活,五条
    行为性结论逐条复验仍成立,work-graph-db.js 完全没被碰(第二轮
    复核最关键的那批发现不受影响)。27-P3/P4 那条「只写函数名不写
    行号」的体例在这里直接兑现了价值。
    
    坏消息 —— §四.9 有一条写反了,已纠正并留档:
    - system/templates/ 彻底取消,本体落 system/agents/task-planning/
    - _registry.json 不再是数据源头(seedAgents 直接 readdirSync 扫
      目录当真相,registry 只按 id 补 benchmark_score),所以「不登记
      会把 CI 跑红」不成立 —— 建目录才是关键动作,登记是可选的
    - 真正的闸门是新增的「毕业」判据:agent.md + soul.md 非空且不带
      _base 骨架哨兵,否则 status 停在 draft、hire() 返回
      body_not_ready。「先建骨架后填」的中间态雇不进公司
    - 本体正文路径三条硬规矩(cwd 深度 3):自身文件 ../../、仓库根
      ../../../../../、交付物必须用 $W 绝对路径。写错全都不报错、
      只静默降级 —— 静默故障家族的最新成员
    
    排期影响:批 1 照常(work-graph-db.js 无人触碰);批 2 的
    task-planning 本体等重构落地后再建,否则要按旧形态做一遍再改;
    批 5 开工前重新核 agent-chat.js(+455 行)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • companyService.get 已经在读这个文件,零新增读路径;建表要多一张表、
    一套读写、一次迁移,而目前公司级配置项只有这一个。涨到 3 个以上时
    再考虑建表。
    
    这是 P3C 最后一个开放问题,§十一 至此全部关闭。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • R-20.6:
    1. 退役 /api/goals 与 /api/company/:cid/goals 两个 OKR 别名,确认执行
    2. goal 级验收仍用 org-validator,在 goal 层再调一次同一个本体。
       不复用 task 级结论 —— 两次验的是基数不同的东西:task 级问
       「这一个 task 的 task-goal 达成没有」,goal 级问「N 个 task
       合起来人要的东西做出来没有」。单 task 的 goal 跳过这次调用。
       goal 状态机加 failed,判词落 goals.verdict
    3. 预算先不考虑,不做 goal 级闸门;但 planning_tokens /
       validation_tokens 两个记账字段保留 —— 它们不是闸门,是让
       goal 层那两次 agent 调用的花费可见
    4. queued 开放改 depends_on。当初担心的「改完位次怎么算」不成立:
       queuePosition 只按 priority→seq→created_at 排,depends_on 决定
       的是够不够格出队,两者正交
    5. 两套任务体系统一到 tasks 表,新增 §十二(批 5)
    
    定案 5 核实出一个反直觉的前提:两套不是并列的,新体系的回调链路
    长在旧体系身上 —— reportRunOutcome 收敛后调的正是
    /api/agent/task/report。所以「删掉另一套」= 删存储与会话反推
    (system/tasklog + TaskService.getAggregatedTasks),不能删入口
    与回调:/task/dispatch 的 URL 与请求体写在 tools-base.md 里,
    改它等于同时改所有 agent 的提示词。
    
    tasks 表为此吸收三个字段(executor / assignee_agent_id /
    parent_task_id):agent 派单与 graph 执行的载体确实不同,
    硬说成一回事是骗自己;调度器只管 executor='graph'。
    顺带修好:派单去重从扫目录变一条 SQL,两套 T- id 合一。
    
    开放问题 2/4/6/7 全部关闭,只剩 5(max_parallel 放哪)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 上游失败时下游自动转 blocked 已定案,开放问题里还留着同一个问题
    的疑问句 —— 这正是「代码改了、文档停在上一轮」那类漂移。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • R-20.5 四条:
    1. task-planning agent 提进 v1(原计划 v2 人手拆)。产出一律 draft
       清单,人确认编辑后才下达 —— 那一步是唯一一道成本闸门,
       拆出 5 条 task 就是 5 份 DEFAULT_TASK_BUDGET
    2. 并行仍留批 4,v1 只排队(max_parallel_tasks=1,唯一索引不动)
    3. manual 任务不参与自动出队:startRun(manual) 只点亮入口就停下等
       /next,而 run 已经是 running 占着坑,自动拉起会堵死队头
    4. 上游失败 → 下游自动推导为 blocked 并标红,给「重跑上游」和
       「强制放行」两个出路。blocked 每次 tick 重算而不落死,
       上游重跑成功后下游自己回 queued
    
    定案 1 牵出一处新缺陷:task-planning 跑在 goal 层、任何 run 存在
    之前,它烧的 token 没有预算池可扣 —— 正是 defaultPlanWorkGraph
    注释里点名的「隐形的一半花费」在 goal 层的新实例。上一轮刚以
    「零消费方」为由砍掉的 goal 级预算字段,以记账字段 planning_tokens
    的形式回来(闸门仍不做)。
    
    链路照抄 defaultPlanWorkGraph 不另造:调 agent → 校验 → errors
    原样回喂,最多 3 轮;3 轮不过降级成 1 条同描述的 task,绝不能把
    goal 卡在 planning。新本体必须与 _registry.json 登记同 commit,
    否则 template-refs.test.js 当场把 CI 跑红(§八.21 有前科)。
    
    验收 19-24 新增:拆分正常 / 失败降级 / 记账 / 不该花钱的路径不花钱
    / 上游失败下游标红且可恢复。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 逐条对着代码核初稿,最要命的一条是基础事实搞错了:
    
    - createRun 在建行的事务里就按 readDefGraph 把 graph_run_nodes 全插好了。
      初稿三处基于「queued run 没有节点行、进去就是空指针」的论证全部作废。
      真实情况是 queued run 与 ready run 在数据上只差 run_status 一个字段,
      所以「靠没有节点行自然报错」那道想象中的防线根本不存在,
      唯一的闸门是 advanceOne 里 run_status !== 'running' 那一句,必须加断言钉死。
    
    由此派生出一个既存、但被排队从毫秒窗口放大成必然事件的缺陷:
    节点行在 createRun 时快照,而 getRunState 每次读都取当前定义。
    排队期间点「重新规划组织」→ 新节点没有 run 行 → 出队即卡死。
    补 syncRunNodesToDef,出队前对齐。
    
    其余六处:
    - 出队原语写错:markRunReady 把 from 写死成 planning,复用不了;
      正解是 updateRunStatus(runId,'queued','ready') 这一条条件更新
    - 唯一索引冲突是 throw 不是 changes=0,初稿的错误处理只覆盖一半
    - 触发点枚举本身是漏的:stopRun 与预算耗尽→blocked 两条最常见的
      释放路径都不走 settleRun。改为挂在唯一漏斗 transitionRun 上,
      一个钩子覆盖全部路径(枚举法在这里靠不住,而漏的症状是静默的)
    - report_to 只活在请求闭包里,排队后回调链路静默断掉 → 必须落库
    - okr_node_id 逐 task 下发会反复覆写同一个 OKR 节点,
      done 会被后来的 failed 翻掉 → task 的 run 一律不带 okr_node_id
    - manual 任务占着 running 等人点「下一步」,会永久堵住队头
      → 不参与自动出队
    
    补漏:存量回填脚本(不做的话批 1 反而让历史任务更看不见)、
    任务级 rerun 与节点级 retry 的命名撞车、queuePosition 排序口径
    必须与调度器同源、砍掉没有消费方的 goals.budget_json。
    
    错误结论留档不删(§三 ⚠️ 块 / §五.1 / §九.1 开头)——
    它们是最容易再犯一次的那类。验收从 12 条涨到 19 条。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • 控制台走查暴露的「一个 company 只能有一个任务」,根因不是并发约束,
    是「人类提的需求」和「机器执行的单元」被压在同一条 root graph_run 上。
    
    三个实地核实的前提,让这次不是发明新概念:
    - 27-NAMING §四 早就定义了 goal 1 : task N 的基数
    - graph_runs.goal_id / task_id 两列早就在,company.js 注释也写着
      「三者基数不同不可混用」—— 但全仓库没有任何写入方,goal_id 是悬空外键
    - G- 前缀已被 OKR 正名释放;/api/goals 与 /api/company/:cid/goals 这两个
      OKR 向后兼容别名全仓库零调用方,可以退役后把路径要回来
    
    设计要点:
    - goals / tasks 两张新表 + graph_runs 新增 run_status='queued'
    - queued 的唯一理由是把出队的并发临界区交给既有唯一索引兜底:
      idx_graph_runs_one_active_root 覆盖 ready/running/paused 而不覆盖 queued,
      超发会被索引直接拒绝,不需要应用层的「先查后插」
    - 排队推翻了 P3「拒绝而不排队」的定案,但那条定案的理由(没有出队 worker =
      静默丢任务)今天仍成立,所以调度器的四个触发点是功能主体而非收尾
    - 已下达的描述冻结:入队即写 task-goal.json 并被 describeTask 消费,
      改库不改 artifact 会让两份对不上,两份都改等于篡改审计输入
    - 现有 T-${Date.now().toString(36)} 在「一个 goal 批量建 N 个 task」下
      同毫秒必撞,且 tasks.id 是主键 —— 改用仓库既有约定的 4hex 后缀
    
    R-20 四条定案回记至 27-requirement.md。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »