• resumePendingGoalValidations 只补「收敛钩子跑过、卡在验收/汇报半路」的情形;
    若钩子压根没被触发过(进程重启/异常吞掉/事件丢失),task 早到终态但 goal
    永远停在 active,只能靠人工 OKR 巡检发现(e2e-demo 公司 G-mt2r62ia-73af 卡
    了 48 小时)。新增 sweepStuckActiveGoals 开机全量扫描一次,并启动 5 分钟
    周期性 startZombieGoalSweeper 兜底,把发现窗口从「天」压到「分钟」。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • 两个独立的小修复,都是这次 e2e 回归 + 用户讨论"workgraph-planning 该怎么训练"时发现的:
    
    1. workgraph-plan-<taskId>.md 撞名:一个任务里 org-planner/org-coder/org-validator
       各自调 workgraph-planning 一次、各自的规划记录却共用同一个文件名,落在同一个项目根下,
       后写的原样覆盖先写的——真实撞见过(org-planner 当时的规划记录被 org-validator 覆盖,
       事后一个字都找不回来)。改成 workgraph-plan-<taskId>-<宿主orgNode节点名>.md。
    
    2. subgraph 节点结算 → workgraph-planning 自己的 memory.md 桥接:阶段一的
       recordNodeOutcomeMemory 明确排除了 subgraph 节点(那是给 agent_call 记的),导致
       workgraph-planning 每次规划出的图最终跑得好不好,完全没有可积累的历史——这正是它
       "允许裁剪却没有具体判据"这个问题一直无法通过真实反馈自我改进的原因之一。新增
       recordWorkgraphPlanningMemory,在 subgraph 节点结算时把结果记进 workgraph-planning
       (全局唯一规划本体,不是宿主 org node 自己的身份)的 memory.md,只在真实规划
       (planWorkGraph===defaultPlanWorkGraph)时记,避免测试夹具污染真实目录。
    
    新增 3 条回归测试,graph-engine 相关测试文件全绿。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • run_b962ea87f58f 卡在 wallclock_ms 耗尽的锚点未结清问题上,需要人工追加预算
    才能继续。extendBudget 只增不减、只加到已配额的维度上,追加时顺带结清卡死的
    墙钟锚点(否则 startRunSync 一启动就把挂起时长记进新预算里)。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 30 号文档记录的真实事故:org-coder-test 被上游 missing_required_input 打回后,
    产出的是「建议重跑 org-coder-validator」的诊断文件而不是正常测试结果,引擎把诊断文件里
    的 infra_flake 标记当成了这个节点自己的执行结果——infra_flake 原地重跑故意不烧
    attempt/backward_count("这不是节点的错,不该烧它的重试额度"),代价是它是唯一没有
    熔断线的失败路径,真撞上了一次每轮产出雷同、永不停止的死循环,眼看着把任务预算耗光。
    
    新增 graph_run_nodes.infra_flake_count 列,只在 infra_flake 原地重跑时累加,真正发起
    新一轮真实派发(bumpNodeAttempt)时清零;连续判 infra_flake 超过硬顶(3,与
    LOOP_HARD_CAP_EDGE 同量级)就不再原地重跑,改判 blocked 交给人工——不猜该走哪条
    fail 边(infra_flake 这个取值域通常没人给它连边)。新增回归测试锁死"永不恢复的
    infra_flake 在硬顶后停止重跑"这条行为,graph-engine 相关 10 个测试文件 94/94 通过。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • POST /:cid/tasks -> enqueueTaskRun 入队时从未写过 assignee_agent_id,
    COO 心跳巡检靠 tasks.assignee_agent_id(the "to" field) 判断任务有没有
    人接手,读到永远的 NULL 就把正常跑图的任务误报成孤儿任务
    (本周已发生两次:T-mt25asz4、T-mt5d3mi9-57cf)。
    
    - enqueueTaskRun 入队时补写占位哨兵 'org-graph'(GRAPH_TAKEOVER_ASSIGNEE)
    - settleTaskFromRun 收敛时用 COALESCE 兜底,只在仍为空时补,不覆盖
      派单式任务(agent-chat dispatch 流程)已经写好的真实 assignee
    - TASK_PATCH_COLUMNS 补上 assigneeAgentId,否则 updateTask 写不进这列
    
    T-mt5dkvjm
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 新建独立试验节点 prd-methodology-trial,走真实 Hunt→Digest→Evolve 训练 PRD 撰写方法论:
    2 轮 Small Loop 提前收敛(96/97 分,真实基线 50 分),用三个真实历史项目反推验证方法论
    有效,Deploy 范围限定在该独立节点,未触碰生产 prd-planning。结论:FDE 对方法论类角色
    确实管用,但收敛速度/验收严谨度依赖任务书里显式写明的自纠正要求,非免人工黑盒。
    
    过程中连带修复同一个 2026-08-16 改名批次(innovator→org-innovator 等)遗留的 5 处死链:
    company.js 两处硬编码调用、CompanyDashboard.vue「重新训练」按钮,以及四个本体各自
    config.json 内部 id 字段从未随目录改名同步更新,导致 /api/agent/list 展示层用假 id
    显示成死卡片。顺手修了 AgentScaffold 默认 tools.md 模板一处裸相对路径缺陷(对应
    agent-md-refs.test.js 里已登记多时的已知缺陷)。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • system/templates/ 已在 28 号文档的合并里并入 system/agents/,「模板」作为独立
    数据概念已经消失;确认后手工 CRUD 页面(创建/改名/删除骨架、触发生成与训练、
    传 skill 文件、provision)也已经没人用,一并下线:
    
    - 前端:删 AppNav「Templates」菜单项、/templates 路由、TemplateAgents.vue
    - 后端:company.js 删掉只服务于该页面的写接口(PATCH/DELETE/POST /templates、
      generate/train/skills-upload/provision/files 系列),保留 GET /templates
      (CompanyDashboard 招聘弹窗和 CLI `company templates` 命令仍依赖它列出可招角色)
    - CLI:bin/lemon.js 删 `template create`/`template train`(调用的是被删接口)
    - 修正两处过时注释(company-service.js / graph-service.js):不再声称
      buildAgentDefinitionBrief 有两个调用方,/hire 是唯一派单点
    - work-layer-body-paths.test.js 同步更新断言(2 处调用方 → 1 处)
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 此前只有人类对话路径 agent-chat.js 会写 memory.md,图执行的验收反馈从不进
    memory.md,Dream 天天扫全是空转。runClaimedNode 结算后新增 recordNodeOutcomeMemory
    桥接点,只在真派发(isRealDispatch)且 systemAgentId 对应本体已雇佣(bodyDir 存在)
    时才记一行 success/fail 摘要,避免测试夹具/假 executor 用例把假地址写进真实
    system/agents/ 目录。memory-service.appendToMemoryMd 补 mkdirSync,因为这条新调用方
    不像既有的 saveSessionSummary 那样先建过 memory/ 目录。
    
    从原本混在墙钟看门狗改动里的工作区 diff 中拆出,两个特性互不相关。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • manual 模式没人点「下一步」、auto 驱动循环随进程重启丢失时,run_status='running'
    的 run 再也不会触发 checkBudget,wallclock_ms 超时无人发现(实例:T-msx6toej 卡
    135 小时才被人工揪出)。新增 sweepWallclockBudgets + 60s 兜底定时器
    startWallclockWatchdog,复用既有 checkBudget/suspendRunTree 路径,接入点在
    index.js DISABLE_BACKGROUND_AGENTS 早退之前。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • 35 号文档剩下的三个提案,彼此直接相关(C 产出 D 消费的证据,B 展示 D 算出的结果),
    一次做完。
    
    提案 D(goal-contract.js 引擎级证据闸门):
      `runBenchmark` 新增 evidence 检测——`evidence_hint` 命中"浏览器/视口/开发者工具/
      console/network/截图"类关键词的维度,必须在 evidence.<dim> 有真实字符串支撑,
      否则该维度判不过(哪怕分数达标)。证据来源两处都认:`--target` 文件自带的
      `evidence.<dim>`,或同目录姊妹判词文件 `attempt-<n>.json` 的 `dimensions[].evidence`
      ——后者是 benchmark-validator/agent.md 早就在教的既有写法,不用 agent 多写一份。
      新增 `dimensions[].evidence_required` 字段随判决落盘。6 条新单测。
    
    提案 C(可复用视觉验收工具):
      新增 scripts/visual-acceptance-check.mjs,四个子命令(overflow/style/console/
      contrast),封装最常见的几类真实浏览器检查(Playwright),agent 通过 bash 直接
      调用,不用每次自己临时想办法(真实撞见过两个极端:一次真写了 Playwright 认真测,
      一次只写了正则解析文本)。配一份不进 vitest 快速循环的独立冒烟脚本(起真
      Chromium,几秒量级)。benchmark-validator/agent.md Step 2 加了引用说明。
    
    提案 B(控制台证据可见性):
      GET /api/graph-runs/:id 新增 benchmarkVerdicts 字段(只读 goal-contract.js 已落盘
      的判决,不重新计算)。NodeDetailPanel 概览 tab 新增"验收证据"小节:逐维度显示
      分数 + 是否有证据支撑,evidence_required 且缺证据的标 ⚠️。纯展示,不改判定逻辑。
      2 条新单测。
    
    全量单测 1012/1012 通过;控制台功能用 Chrome 实测过(截图确认"验收证据"小节正确
    渲染)。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • runClaimedNode 派发真实调用之前,先查一次 agentLocator.missingBodyDefinition
    (systemAgentId)——此前这个判据只在招聘/建公司路径上被查过,图执行的核心路径
    从没查过:本体因任何原因(装配没跑完、手工改坏、迁移遗留)仍是空骨架时,引擎
    会原样把它当正常本体派发一次真实调用,静默产出一份没有角色指导支撑的结果
    (真实样本见 34 号文档 §五:unit-tester 顶着 _base 骨架跑完了一整条真实任务)。
    
    本体不完整时:新 fail_reason='body_not_ready'(与招聘路径的同名状态同义),
    不扣预算(没有调用发生,"一次调用一定发生了"这条记账理由不成立)、不起边界
    守卫、走正常的选边/兜底/熔断路径(不发明第二套重试语义)。
    
    只在 agentExecutor === defaultAgentExecutor 时生效(与 boundaryGuardApplicable
    同一判据)——第一版忘了这条,把全仓库几百个用假 systemAgentId + 注入假 executor
    的用例全部判成 body_not_ready,跑单测直接炸穿 75 个用例;加上这条判据后收窄回来,
    只在真会起 CLI 的路径上生效。
    
    只查 agent_call 节点:subgraph 节点的 systemAgentId 不是即将被直接调用的本体,
    它派发的是 workgraph-planning 去规划子图。
    
    新增 graph-engine-body-not-ready.test.js(4 例)+ 更新
    graph-engine-fail-reason.test.js 的取值域断言(五个→六个)。全量单测
    1004/1004 通过。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 用户反馈:控制台原来只能逐个节点点「输入输出」tab 看产物,缺一个全局入口。
    新增 TaskFilesPanel.vue,挂在页头,跨节点/跨 run 一次性列出这个任务的全部产物。
    
    做的时候发现分两类、且第二类之前完全没覆盖:
      · 节点产出索引(graph_files,按 company+task 检索)——规划笔记/日志/契约快照
      · 项目根(W2 ensureProjectRoot,data/companies/{cid}/projects/{pid}/)——
        真实交付物代码写的地方,跨节点共享,indexNodeOutputs 从不扫它
    
    后端补 readProjectRootRecord 导出 + GET /api/graph-runs/:id 挂 projectRoot 字段;
    项目根的文件列表复用已有的 GET /:companyId/files(此前零调用方),不新开列目录端点。
    新增 graph-run-route-io.test.js 两例(分配前 null、分配后原样透传 + 幂等)。
    
    顺带订正 34 号文档:当时断言"index.html 从未被写出来"是错的——真实网站文件
    其实在项目根里,只是没查那个目录。34 号文档 §零 记录了订正过程,问题收窄成
    "validator 有没有真的做过浏览器级检查"(仍未解决,留待下次)。
    
    全量单测 1000/1000 通过。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • settleTaskFromRun 只写 status/finished_at,不清 blocked_reason —— 任务卡住时
    noteTaskBlockedReason 写过的那句「节点 xxx 卡住(...)」会一直挂在 tasks 行上,
    哪怕后来 run 真的恢复跑完转 completed。界面上因此出现「已完成」绿色状态旁边
    还留着一句刺眼的红字,容易被读成还在报错。
    
    真实复现:T-mt2r6fz0-125b 手动推过熔断点后 org run 收敛到 completed,
    任务列表却仍显示 org-coder 卡住的旧归因文本。手动清了这条任务的残留数据,
    代码层面在 settleTaskFromRun 的 UPDATE 里一并置 NULL——task 进终态时
    「为什么卡着」这句话本来就该失效,不管进的是 completed/failed/cancelled 哪一种。
    
    扩展了 goal-task-acceptance.test.js 里"预算耗尽→恢复→跑完"那条既有用例,
    断言 blocked_reason 在完成后确实被清空。全量单测 998/998 通过。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • org-coder-validator 在真实回归任务里被 missing_required_input 反复误判 blocked:
    executor.json/executor-fanout.json 的 test/integration→validator 边声明
    produces:["log"](required 默认 true),但 test 类节点从不写出能被 indexNodeOutputs
    认作 kind=log 的文件(coder.log 实际属于 impl,test 自己的 _plan.md/_research.md
    认不出 kind 也没有一对一无歧义可猜),这个 kind 因此永远不会被索引到,
    requiredInputsMissing 每次都判缺失——不是时序问题,是结构性、每次重试都复现的硬伤。
    
    改成 { kind: 'log', required: false },对齐 validator.json 已有的写法(它的
    needs:artifact 早就标了 required:false)。真正必需的 needs:artifact 不变,
    仍受硬闸门保护。
    
    中途走了一段弯路:最初以为该改 inboundContracts() 按 role 过滤只留 needs,
    改完单测炸了 9 个用例——大多数边只声明 produces(无配对 needs),这条链路
    正是 T-mt25ecbd 产出闸门依赖的数据源,按 role 过滤会静默削弱那次修复。
    已撤回(work-graph-db.js 这次只加注释,没有逻辑改动),细节记在 33 号文档。
    
    新增回归用例 graph-files-index.test.js ⑪:produces required:false 且该 kind
    从未被索引到时不拦下游成功,同边 needs:artifact(required:true) 依旧受保护。
    全量单测 998/998 通过。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • company.js 的 hire 路由原来用一发 fetch(...).catch(...) 回环自己去触发 agent-creator
    填新角色的定义文件——fire-and-forget,.catch 只接网络层错误,agent-creator 撞车时
    (另一次调用正占着它)/api/agent/:agentId/chat 返回 429,被原样当成"成功"吞掉。
    新角色因此永久停在 base 空壳 + status=training,唯一痕迹是一行 console.error(且这行
    在 429 场景下根本不会触发)。
    
    新增两个模块:
    - agent-session-lock.js:从 agent-chat.js 抽出的共享互斥锁存储(纯数据结构,Map +
      超时常量),agent-chat.js 的路由与 hire 路由现在用同一把锁。不能各起一把——
      SessionPool.getOrCreate() 不检查上一次调用有没有跑完,两条路径分开维护锁的话,
      各自都以为自己没有并发,会把第二轮 prompt 发进一个还在处理中的 CLI 会话
      (未定义行为,比现在的"被拒绝丢弃"更危险,见 32 号文档 §二方案 B)。
    - agent-body-filler.js:同进程直接调 AgentManager.callAgent,替掉 HTTP 回环。
      拿到锁才发起调用,锁被占用时等一等重试(最多 3 次,5s/15s 退避),重试耗尽在
      本体目录写一个哨兵文件 .body_fill_failed.json(不动 system_agents.status /
      config.json.status——那两个字段还有 draft→active 毕业逻辑在读,改取值域风险
      大于收益)。
    
    company.js 的 hire 路由第 1 步从 fire-and-forget 换成 await,顺带让第 2/3 步
    (技能注入、5 秒后触发 Innovator 训练)拿到"agent-creator 真的写完了"这个保证——
    第 3 步原来的 setTimeout(5000) 靠的是"假设 5 秒内写完",现在这个假设不再需要,
    但没有顺手删掉那个 setTimeout(不在这次修复范围内,留着无害)。hire 接口本身
    仍是异步的,调用方还是立即拿到 training 状态,没有改成同步等待完成。
    
    agent-chat.js 的锁声明改成从共享模块 require,行为逐字不变——用现有 5 个
    agent-chat 相关测试文件(36 条)验证过零回归。
    
    新增 agent-body-filler.test.js(6 条):首次成功、重试后成功、锁被占用时等待
    重试、重试耗尽写失败标记、成功后清掉上一轮的失败标记、只释放自己抢到的锁
    (不误删第三方在途期间抢到的新锁)。用 vi.useFakeTimers() 控制重试等待,
    不用真等 5s/15s。
    
    全量 npx vitest run:151 文件 / 1384 用例,零回归。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 问题 1:work 节点只有 agent.md 进提示词
    
    graph-engine 驱动 work 节点走 PromptBuilder.build(),只读 protocol + agent.md +
    companyContextPath + user.md/memory.md/近期记忆。交互式的 agent-chat.js
    (buildSystemPrompt)另有一条独立实现,会读 soul.md/identity.md/tools.md/
    heartbeat.md/OKR 目标/hippocampus 检索/main_skill/SKILL.md/其它 skills——两条链路
    从未同步过。静默到什么程度:agent 照样跑、照样成功,只是少了人格/工具约束/长期
    记忆检索,没有任何报错能看出原因。
    
    新增 identity-context.js,抽出 agent-chat 那份逻辑里「读哪些文件」的等价实现(不
    读 agent.md/user.md/memory.md/近期记忆——那五样已经被 PromptBuilder 覆盖,避免
    重复),接进 PromptBuilder.build() 的非 SKILL.md 分支(scoped skill 那类不是完整
    身份,不适用)。用户明确选择「全量对齐」:soul/identity/tools/heartbeat/OKR/
    hippocampus/skills 一次性全补,不做裁剪版。
    
    ponytail:没有反过来改 agent-chat.js 去调用这份共享实现——两处现在是「同构但
    独立」的两份代码。agent-chat.js 是长期稳定的高频路径,为这次修复去动它风险和
    收益不成比例,见 identity-context.js 头注的升级路径。
    
    ## 问题 2:org node 角色定义截断到 2000 字,切没切完全不可见
    
    describeRoleDefinition 给 workgraph-planning 的角色定义摘录一直硬切前 2000 字
    (有意为之,规划要的是「职能」不是全文)。但切没切、切掉了多少,规划 agent
    一无所知——agent.md 写得长一点、职能说明排在后半段,这句关键描述可能根本没
    进提示词,且没有任何信号。截断上限不变,改成截断时附一句 ⚠️ 提示 + 完整路径,
    给它一条自己去读全文的退路。
    
    ## 测试
    
    identity-context.test.js(10 条):文件读取的每个条件分支、OKR/hippocampus
    只在有 agentId 时才查、OKR 抛错不拖累其它内容、skills 目录扫描排除 main_skill。
    两个依赖(OkrService/retrieveRelevantPlans)用直接换 CJS module.exports 属性
    的手法隔离——本仓库服务层是纯 CommonJS,vi.mock 对嵌套 require() 链路不生效
    (实测踩过),且这两个依赖没有 env 覆盖点,真实路径不能进单测。
    
    describe-role-definition-truncation.test.js(3 条):截断/不截断/本体不存在
    三种情况,用真实 system/agents/ 下的 zz- 前缀临时本体(locator.bodyDir 没有
    env 覆盖点,只能顺着真实路径走),afterEach 清理不留痕迹。
    
    全量 npx vitest run:150 文件 / 1378 用例,零回归。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 起因
    
    e2e-demo 真实任务卡住:org-planner-sdd 自己判 input_underspecified
    并点名 org-planner-prd,但这张图里 sdd 没有任何 fail 出边——精确匹配、
    兜底边两条路都接不住,节点直接 blocked,靠人工点了「重试」才续上。
    
    ## 根因
    
    fail-fallback-edge 校验(27-P3/P4 §三.7 第五项)只在节点已经画了别的
    fail 边时才检查兜底边齐不齐。一条 fail 边都没有的节点完全不受约束——
    workgraph-planning/agent.md 原文明说这是「合法设计」。但系统里所有本体
    共用 _base 的四段式协议,VERIFY 阶段任何 agent_call 节点都可能自判
    失败,不止专职判词节点。四个骨架模板审计后发现 6 个中间节点存在同一个
    漏洞(sdd 只是撞上的那一个)。
    
    ## 决策
    
    除图的唯一入口节点外,每个节点必须留一条兜底边(判据是 forward 入度,
    不是节点名字模式)。org 层暂不动——node 全是 subgraph 类型,失败来源
    机制不同,且套用同一条规则会让三个 org 模板立刻不合法,属于 27-p3b
    「验收9」还没收口的链路,留给那边一起做。
    
    中途发现无条件收紧会炸 46 个用例/13 个文件(createWorkGraph 被大量
    测试当成建最小图的通用工具)。改法是加 strict 开关,默认关闭(行为
    不变),只在 workgraph-planning 真实提交图落库的那个唯一入口
    (createWorkGraphSafely)打开。
    
    ## 改动
    
    - work-graph-db.js / graph-engine.js:新规则 + strict 开关
    - planner.json / executor-fanout.json:补 6 条兜底边
    - workgraph-planning/agent.md:约束三等四处措辞同步
    - 新增单测:strict 模式 7 例 + 真实链路回归用例(真实引擎代码+真实
      DB+只换最外层 CLI 子进程,复现 sdd 自判失败后引擎自动路由到 prd,
      全程零 retryNode 调用)——比重跑一次真实任务更严格,可回归、进 CI
    
    978/978 单测通过。需求与决策记录见
    docs/todo/system-design/31-work-graph-fail-fallback-coverage.md。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 新增 graph-engine-billing-gate.test.js(7 条):
    - 未注入 billingGateFn 时零影响
    - advanceOne / advanceBatch 欠费时都正确挂起(reason=billing-arrears,
      block_reason 落库,节点停在 ready 没被 claim)
    - startRunSync「启动」清空 block_reason
    - resumeBillingClearedRuns 正确恢复已清零的 run、放过仍欠费的 run,
      且支持按 companyId 限定扫描范围
    - advanceBatch 排序回归用例:已收敛(零 ready 节点)只是恰好预算/欠费也耗尽的
      run,正确收敛到 completed,不会被误判成挂起
    
    隔离验证:临时 worktree 里单独跑(graph-engine.js/work-graph-db.js 当前正被
    另一个会话同时编辑 fail-fallback-edge 规则,工作区里混着两份改动,跑全量套件
    会被那边未完成的改动带崩;这条测试只依赖已提交的 T3-T5 代码,临时 worktree
    里跑通即可确认与那份改动无关)。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 先搭桩子,不接真实计费接口(用户明确选择:真实欠费判据依赖计费系统那边确认,
    现在只把可插拔的挂载点和恢复机制先落地,接口确认后再换真实实现)。
    
    新增:
    - setBillingGate(fn):与 setAgentExecutor/setPlanWorkGraph 同款注入模式,未注入
      时 checkBilling 恒返回「未欠费」,对现有行为零影响。
    - checkBilling 挂进 advanceOne 与 advanceBatch,紧跟 checkBudget 之后——两者是
      不同维度的门(任务级预算池 vs 账户级欠费),不合并成一个判据。
    - 欠费挂起复用现成的 suspendRunTree(预算耗尽走的同一条路),只是 reason 换成
      'billing-arrears'。suspendRunTree 加 reason/summary 参数,并把 reason 写进新增的
      graph_runs.block_reason 列(欠费清零后的自动恢复扫描要按它过滤,不用解析 timeline
      文本);startRunSync「启动」时清空这一列。
    - resumeBillingClearedRuns():欠费清零后的自动恢复触发器——今天从 blocked 恢复靠
      人手点「启动」,欠费清零不会有人去点,这个函数补上「扫一遍 block_reason=
      billing-arrears 的 blocked root run,欠费真清了就代它点启动」。调度触发源
      (webhook/轮询)留给计费接口接通之后再定。
    
    顺手修了一个 advanceBatch 的排序 bug:budget/billing 门必须在「确认真有 ready
    候选」之后才查,跟 advanceOne 的既有顺序一致——反过来会把一个已经收敛、只是
    恰好预算也花完了的 run 误判成「挂起」而不是正确收敛到 completed。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 自动模式的驱动原语 advanceTree 里 advanceOne 换成 advanceBatch:树上「最深
    active run」的选取逻辑不变,但推进这一步现在能一次并发 claim 一批 ready 节点,
    而不是每 tick 只推一个。核实过 advanceTree/runAuto 的返回值全仓库只被读 .ok,
    不依赖 .node/.results 的具体形状,不需要兼容层。
    
    顺带抓到并修了一个真 bug:advanceBatch 的 no-ready-node / taken-by-other 分支
    原来写的是 ok:true,跟 advanceOne 用 result() helper 的惯例(这两种情形一律
    ok:false)不一致。不修的话接进 runAuto 后,一个已经收敛的 run 会被自动循环
    一路空转到 maxSteps 才停——旧测试从没对已收敛的 run 连续调 advanceBatch,
    所以这条一直没被撞出来。
    
    新增两条测试:ok:false 语义与 advanceOne 对齐(直接绕开引擎构造边界状态);
    runAuto/advanceTree 端到端确实吃到并行(用真实 setTimeout 延迟证明并发窗口
    重叠,不是同步假象)。全量 vitest 147 文件 / 1350 用例,零回归。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • planner 骨架(brd→prd→sdd→goal-benchmark→validator)复现:三个 planning 节点
    声明了 produces 却没真落盘,executor 没抛异常就被引擎判 success;goal-benchmark
    的入边 needs:[brd,prd,sdd] 只用来拼提示词里的 ⚠️ 提示,agent 视而不见照样产出
    合法 acceptance。下游 validator 永远读不到三份文档,backward 重试撞循环硬顶后
    只能人工兜底。
    
    settleNode 判 success 之前新增两道闸门(均只对 agent_call 节点生效,subgraph/org
    节点的契约由子 work run 自己的节点过闸):
    - unsatisfiedRequiredProduces:声明的 required produces 一个候选文件都没落盘 →
      fail_reason=produces_missing,不再放行
    - requiredInputsMissing:声明的 required needs 在 graph_files 里查不到实例 →
      fail_reason=missing_required_input,不采信 agent 自报的 success
    
    两者都只判「有没有」,不判「分类对不对」——ambiguous 归类留给下游 ⚠️ 提示词处理,
    避免把「写了东西但引擎猜不出对应哪个 kind」误判成没执行。
    
    包含此前已完成但未提交的 27-P4 T3(graph_runs.max_concurrency 落库)及配套
    advance-batch 测试。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 只做了这一件事,没有跟着做「dream 改成 org/work graph 来跑」——两者被
    单独评估过:org/work graph 是单公司作用域(org graph=公司级 workflow,
    work graph=公司内某任务的节点图),dream 却是跨公司扫描,套进去要么破坏
    作用域假设要么得新开图类型;且 graph 引擎自带的验收机制(goal-contract
    benchmark/test-script)判的是「产物过没过验收脚本」,对「经验提炼得对
    不对」这种自由文本判断没有对应机制——不管套不套 graph 都得单独设计
    rubric,是同一件事。用户明确只要 REST/控制台这一半。
    
    ## REST(server/routes/dream.js,挂 /api/dream)
    
    路由本身没有新业务逻辑,全部委托给已测过的 dream-service.js:
    GET /agents(总览)、GET /agents/:id(详情,含定义文件可写性 + 经验
    审计)、POST /agents/:id/run(手动跑一次,dryRun 只看有没有新原料不
    起 LLM)、POST /sweep + GET /sweep/status(全量扫描后台跑 + 轮询,内存
    态并发锁防止连点两下)。
    
    ## 控制台(web/src/views/DreamConsole.vue,路由 /dream)
    
    一个表格 + 两个弹窗:本体列表(详情/跑一次/dry-run)+ 全量扫描按钮
    (15 秒轮询状态)。导航栏加 🌙 Dream 入口。
    
    ## 验证
    
    server/tests/unit/dream-routes.test.js 4 例,monkeypatch 掉
    dream-service 导出对象上的六个函数(不走真文件系统,不误扫真仓库
    76 个本体),只验路由层自己的逻辑:端点接线、sweep 并发锁状态机。
    
    真起服务器打了真数据:GET /agents/innovator-evolver 正确报
    SKILL.md 因没进 git 历史而不可写、经验审计吐出上次真跑沉淀的 4 条
    经验及原文;dry-run 正确报 no-new-material,没多花一次 LLM 调用。
    浏览器里走了一遍列表→详情→dry-run,与直接打 API 结果一致。
    
    955 个单测全绿。
    
    文档:29-dream-design.md §5.7 记录实现与「为什么不做 graph 化」的
    理由;28-followups.md / docs/README.md 同步更新 dream 现状。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • ## 决策 D:能自动查的自动查,不能自动查的把回查成本降到几秒
    
    「这条结论对不对」机器判不了,需要理解内容 —— **不假装有自动判据**。
    能自动做的只有两件,两件都做了:
    
    ① **引用真伪可以自动查**:提炼产物的 sources 必须都出自这一轮真喂进去的原料,
       编一个出处直接拒(前置闸门,连去敏判定都不用调)。
       编了出处的经验是「无法验证」最危险的形态 —— 人抽查时顺着一个不存在的指针放弃,
       然后**默认它是真的**。
    ② **auditDistilled()**:把每条结论与它引用的原料原文并排列出来,
       指针悬空标 null(原料被清理过 / 出处是编的)。
    
    真跑那 4 条实测:**8 条引用全部取得回原文**,模型没编出处。
    ⚠️ auditDistilled 的输出**不是质量分**,别当成那个用。
    
    ## 任务态清理(28 §5.6)
    
    四道判据全过才删:① 公司在 company_agents 名册里;② 目录名在**任务态白名单**里;
    ③ mtime 早于 N 天(默认 14);④ run_* 对应的 run 已到终态。默认 dry-run。
    
    **第 ② 条是踩出来的**:第一版写成黑名单(「不在保护名单里的都算任务态」),
    dry-run 直接选中了 trend-scout-global/idearun/venv 和 youtube-crawler/idearun/data
    —— 一个 Python 虚拟环境、一个数据目录,都在**真公司**底下,都不是任务态。
    **删错不可逆,留着最多占磁盘 —— 判据必须是「认得出才删」,不是「不认识就删」。**
    
    读不到名册时一家公司都不认(fail-closed),守卫里单独钉了这条。
    公司态(memory/ conversations/)永远走「不许清」那条分支 —— 那是 dream 的唯一原料,
    删掉不可再生(28 §5.6b)。
    
    实跑 30 天窗口:待删 0,跳过 955(556 还新 / 74 不在名册 / 60 公司态 / 11 白名单外)。
    
    全量:145 文件 / 1338 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 定时:每日一次,挂在 DISABLE_BACKGROUND_AGENTS 早退之后
    
    dreamSweep 串行扫所有本体,**不并发**:每轮可能起两次 LLM 调用,
    几十个本体并发 = 同时拉起几十个 claude 子进程 —— 这个代价本仓库付过
    (2h43m 漏出 82 个进程 / 17.5 GB,28-followups §五 第五条)。慢一点没有代价。
    
    空转保护在 dreamOnce 里,所以「76 个本体全扫一遍」的实际成本是 76 次目录读。
    定时器 unref —— 少了它,短命进程会被 24 小时的 timer 吊住不退出。
    只挂定时**不立刻跑一轮**:启动那刻跑没有额外收益,反而让每次重启都烧一遍。
    
    一个本体抛异常不拖垮整轮,但**必须留下是谁抛的**,否则现场只看到「今天没沉淀」,
    查不出是没原料还是一直在抛。
    
    ## 定义文件:受管区块 + 三道闸门
    
    形状是一对 `<!-- dream:begin/end -->` 之间的**受管区块**,区块外一个字节不碰。
    自由追加会让「哪些是人写的、哪些是 dream 写的」几轮之后再也分不开,
    而分不开就没法分别回滚。区块整体重写还顺带**幂等** —— 同一批经验重复跑不会越写越长。
    
    三道闸门,缺一不写:
    ① definition-write-gate:当前内容必须已在 git 历史里(你定的前置条件);
    ② **agent.md 的前 2000 字符必须仍然自足** —— 规划 agent 只看这一段
       (graph-engine.js:915 的「角色定义摘录」),改了等于悄悄换掉它读到的画像;
    ③ 写完复核毕业闸门,不过就**立刻还原** —— agent.md 写坏会让本体雇不进任何公司。
    
    任何一道不过:不写,并把原因逐个文件返回(`definitions[]`)。**不写不该是静默的。**
    
    ## 测试上又纠了自己一次
    
    三条新用例第一版是「在测试里比对形状」而不是真调 —— 和上一轮同一个毛病。
    改成对**真被 git 跟踪且干净**的本体(dream-screener)走真路径:
    真写、验区块外字节不变 / 幂等 / 毕业闸门仍过,finally 还原并断言还原干净。
    agent.md 那条同理真调,验「不写 + 理由点名前 2000 字符 + 一个字节都没动」。
    
    dreamSweep 加 agents 参数,让错误隔离那条能真调而不是真扫 76 个本体。
    
    全量:144 文件 / 1330 测试全绿,仓库干净。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 两个本体(都过得了毕业闸门)
    
    · dream-distiller —— 把一批原始记录提炼成可复用经验,交 ```json 数组
      {title, body, sources};没有值得留的就交 []。
    · dream-screener —— 逐条判「这条能不能给别家公司看」,交 {shareable, reason}。
    
    **决策 A 的独立性写在两处,缺一不可**:
    · 代码层:判定**一条一次调用**,提示词里只放 title + body,**不放原料**;
    · 本体层:dream-screener/agent.md 明写「你看不到原料,也看不到是哪家公司
      —— 这是故意的」。守卫里有一条专门断言这句话在 —— 否则下一个人会顺手把原料
      塞进提示词「让它判得更准」,而那恰好把独立性拆掉了。
    
    soul.md 也承担判据的一半:screener 的人格写的是「你是一道闸门,不是审稿人。
    审稿人的本能是帮它改好,闸门的本能是不确定就不放」。
    
    ## 两处失效方向都是硬的
    
    · 提炼取不出 JSON → **抛**,不是静默返回空。「产出格式坏了」和「没提炼出东西」
      是两件事,混为一谈会让前者以后者的形式永远藏下去 ——
      每天显示「今天没经验可沉淀」,而真相是解析一直在失败。
    · 判定解析不了 / shareable 不是布尔(比如给了字符串 "true")→ 判否,
      并把原文片段带进 reason,让人看得出是「判否」还是「判炸」。
    
    ## 纠了自己两次
    
    ① 测试第一版把解析与失效分支**在测试里重抄了一遍**再断言 —— 那是在验一份复制品,
       代码改了它照样绿。改成加一条 setDreamAgentCaller 缝,只换掉「真起 CLI」那一层,
       解析、判空、失效方向全走真代码。
    ② agent.md 的相对引用按 **cwd 深度 3**({aid}/{cid}/{taskId}/)解析,不是按文件
       自身位置 —— 仓库根是 ../../../../../。我按文件位置写了 ../../../,
       agent-md-refs.test.js 当场判红。**守卫替我抓的,不是我自己发现的。**
    
    ## 还没做的,写进文档 §5.4(别当成 dream 上线了)
    
    · 定时挂载没做(决策 C 说每日一次):现在只有 dreamOnce(agentId) 这个可调用单元,
      没挂调度、没 REST 入口。
    · 还没写定义文件:只写本体根的 memory/experience.md(纯经验积累,删掉无副作用)。
      三个 git 跟踪的定义留给下一步,闸门与 definitionWritability() 已就位,
      缺的是「往里写什么、写成什么形状」那半边设计。
    · **没有一次真 CLI 验收**:CI 里换掉的是 callDreamAgent 那一层,验的是解析与失效方向,
      不是模型写得好不好。两个本体的提示词**至今没有被任何模型读过** ——
      这正是 P3B W4 记过的形态(mock 从不读提示词)。
    · 决策 D(提炼质量怎么评)仍未定;可追溯是它的便宜起步,不是替代品。
    
    全量:144 文件 / 1323 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 步骤 2:「有哪些公司」只认 company_agents 表
    
    system/agents/{aid}/{cid}/ 是**约定不是校验**。实查 96 个二级目录里混着
    train/ workspace/ 和带时间戳的测试残留(tmp-w7-*-1786986291739-*)。
    拿目录枚举公司 = 把测试夹具当经验原料喂给提炼 agent。
    
    改成查表,另补保留段 _global(全局 agent 的运行态落点,没人「雇」它所以不在表里)。
    **目录从此只用来定位文件,不用来枚举公司。**
    守卫里真造了 train/ 和 tmp-w7-* 两个目录,断言它们不进原料。
    
    ## 空转保护:水位线按「条数」不按 mtime
    
    实查 75 份 memory.md 只有 7 份有非模板内容 —— 每日跑起来绝大多数 agent 没有新原料。
    不拦的话每天几十次「读完发现没东西」的 LLM 调用在烧钱。
    
    **判据用条数而不是 mtime**:mtime 会被任何一次无关写入推动(例如别处重写整份
    memory.md),条数只在真的多出记录时增长。用 mtime 的话,空转保护会在
    「文件被碰过但内容没变」时失效。
    
    ## 决策 A 的失效方向写进代码
    
    去敏判定 agent **抛异常也算拒绝**,并把「为什么」写进 rejected[]。
    否则现场只看到「少了一条」,查不出是判否还是判炸。
    「少沉淀一条经验」与「泄漏一次机密」代价不对称,判据必须偏向贵的那一侧。
    
    ## 步骤 5:可追溯
    
    每条原料带 source({公司}:memory.md#{行号}),提炼产物落盘时把 sources
    写进 experience.md 的引用行。没有它,dream 的产出无法验证对错 ——
    与 FDE 那边「LLM 自己汇报分数」是同一个问题。
    
    ## 步骤 3 还差什么(写在文档 §5.3,别当成做完了)
    
    编排 / 空转 / 闸门 / 落盘 / 可追溯都跑通了(6 例守卫),
    **缺两次真 agent 调用的默认实现**:提炼 agent 与去敏判定 agent 的本体还不存在。
    默认实现现在是一句显式 throw(「尚未接线」)而**不是静默返回空** ——
    静默返回空会让调用方以为「跑了但没提炼出东西」,那是本仓库反复出现的失效形态。
    
    也还没写定义文件:writeDistilled 目前只写本体根的 memory/experience.md
    (纯经验积累,删掉无副作用)。三个 git 跟踪的定义留给下一步,
    definitionWritability() 已能报出「现在能不能写、不能写是为什么」。
    
    全量:144 文件 / 1316 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 29-DREAM §四 的五步里先落两步。
    
    ## 步骤 4:本体定义文件的 git 历史闸门(决策 B.3)
    
    用户定案:soul.md / agent.md **直接落盘**,但写之前必须确认这份文件已经在
    git 历史里 —— 别导致直接修改而没有历史。
    
    definition-write-gate.js 两条判据都满足才放行:
    ① 文件被 git 跟踪;② 该文件没有未提交改动。
    判不出来(不在仓库 / git 不可用)一律拒绝 —— fail-closed。
    
    **为什么是「拒绝并报告」而不是「脏就先替它提交一把」**:后者看着更顺手,
    但那正是本仓库记过的一类事故 —— 多会话共用同一个 worktree(27-P3C §15.4)。
    一份未提交的改动很可能是**另一个会话正在写的东西**,替它提交等于把别人的
    半成品写进 main 且署错名。把「要不要提交」留给人。
    
    守卫 5 例,含「拒绝时必须给出下一步」那条 —— 撞上闸门的人(或 agent)
    要能照着做,否则只能猜。
    
    ## 步骤 1:memory.md 的自动写入不再冒充「关键事实」
    
    原实现取任务与结果的第一行、砍到 80/100 字符,写进 `## 关键事实`。
    那不是提炼,是搬运 + 截断。实测产物(cto/_global/memory/memory.md):
    
        - [2026-08-20] [SYSTEM HEARTBEAT] → 🔧 [TOOL_CALL] Bash (ID: toolu_01RsQ3gh...)
    
    **工具调用 id 被当成了「关键事实」**,而 memory.md 会被注入每一轮提示词。
    28 §5.6b 点名的错误版本是「自动把原文写进共享 SKILL.md」——
    这套已经是那个错误的单公司版,只是还没跨公司共享所以还没造成泄漏。
    
    改法:
    · 换段 —— 自动写入落到「原始记录(未提炼)」,`## 关键事实` 只由提炼产物填。
      读的人和 agent 一眼分得清「日志」和「结论」;这一段同时是 dream 的输入面。
    · 滤掉确定的噪声(工具调用标记 / 空结果 / 只有心跳标记的任务),
      但**不猜「哪条重要」**——猜重要性需要理解内容,那是 dream 的活,
      在这里做等于把提炼下放到一个正则里。
    · 不再截成半句:上限放宽到 200 并取完整第一行。截断残渣读起来像事实、
      其实缺主语,比不留更坏。
    
    ⚠️ 踩到一条:噪声判据初版有「任务与结果都短于 8 个字符就丢」。那是按英文长度
    想出来的阈值 —— 中文 8 个字是一句完整的话,实测「结果甲甲甲甲甲」(7 字)
    整条被丢。已删该规则。**凡是拿长度当信息量代理的判据,先问一句这是按哪种文字算的。**
    
    全量:143 文件 / 1310 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 病症(真跑实测)
    
    2026-08-20 那条 run 的 timeline 里,**每个节点**的产物索引写入都失败:
    
        node.output | 产物索引写入失败 contract.json: UNIQUE constraint failed: index 'idx_graph_files_path'
        node.output | 产物索引写入失败 sdd.md:       UNIQUE constraint failed: index 'idx_graph_files_path'
    
    19 次 agent 调用、每次两三个文件,无一成功。
    
    ## 根因:加了新索引、没删旧索引
    
    产物索引加「版本」之前,唯一索引是无条件的
    `UNIQUE(company_id, COALESCE(task_id,''), rel_path)`。加版本之后建表块换成了
    带谓词的 idx_graph_files_current(WHERE superseded_by IS NULL),**但没人删旧的那条** ——
    `CREATE ... IF NOT EXISTS` 只管不重复建,不会删已有的。
    
    于是 recordFile 那套「先把旧行 superseded_by 指向新行、再插新行」的版本化写法
    在存量库上整个失效:旧索引不认 superseded_by,同路径第二行直接撞。
    而重试恰恰会让同一条 rel_path 被产出第二次。
    
    ## 失效方向:静默
    
    writeFileIndex 把 recordFile 的抛降级成一条 timeline,**节点照样 success**。
    后果是产物索引在任何有重试的 run 上都不全 —— 而下游节点正是靠这份索引
    按 kind 找上游产物。不报错、不影响测试、git status 干净。
    
    ## 为什么没有任何测试照到
    
    新建库压根没有那条索引(建表块里早就只剩 current 那条),**只有存量库会踩**。
    
    这是同一类问题的**第三次**(tasks 加列的索引顺序、墙钟锚点迁移、这次)。
    判据固定成一句话:**凡是碰 schema,「存量库重启」那条路必须单独走一遍。**
    
    ## 修 + 验
    
    迁移里一句 DROP INDEX IF EXISTS idx_graph_files_path。
    守卫 graph-files-stale-index.test.js 走存量库重启那条路,并验版本化语义没被削弱
    (同路径两行、当前版本恰好一条、旧行指向新行)。反向验过:不删就红。
    
    生产库已实跑验证:删索引后同路径连产两次不再抛,graph_files 24 行未受影响
    (备份 /tmp/graph.sqlite.pre-idxdrop)。
    
    全量:141 文件 / 1300 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 28-followups §3.1。**审计结论与那条待办原来的写法不一样,两列要分开处理。**
    
    ## source_kind:删
    
    与 origin 一一映射(global_agent↔builtin / company_hired↔created),
    实查生产库 76 行零例外。全仓唯一读侧是 migrate() 里那条 origin 回填,
    而那是给「origin 列出现之前写下的行」用的一次性迁移。
    
    顺序:**先回填、再删列**,两步都用 hasColumn 守着(幂等)。反过来做不报错 ——
    库能开、测试能过,只是那几行 origin 悄悄是错的。守卫
    source-kind-drop-migration.test.js 走存量库重启那条路,专门钉这个顺序,
    反向验过:弄反立刻红。
    
    写侧散在 3 处生产 INSERT + 35 个测试文件。批量改的时候正则误伤了 4 处
    (吃掉了 JS 默认参数、位置占位符、ON CONFLICT 的赋值行),逐个修回并复查了 diff ——
    **这种跨几十个文件的机械改动,靠「全量跑一遍」兜底比靠正则写得对更可靠**。
    
    ## legacy_path:不能删,它还没死
    
    原待办说「所有读取点都已改成从 id 推导」——那半句对新行成立,对存量行不成立:
    · 新行 legacy_path 恒等于 system/agents/{id}(确实冗余);
    · **存量行里存着批次 4 之前的公司侧路径**,而 graph-seed-service 判「悬空行 / 孤儿行」
      靠的正是这个路径指向的目录还在不在。从 id 推导出来的路径永远指向 system/agents/,
      那条判据会整个失效。
    
    实查生产库:76 行里还有 2 行是老路径(org-planner / org-coder 指着已不存在的
    data/companies/mycompany/agents/)—— 正是这条判据要认出来的那种行。
    
    退出条件写进了 graph-db.js 的建表注释(不只留在文档里):
    库里不再有 legacy_path <> 'system/agents/' || id 的行。
    
    ## 顺带记一条:getDb() 会就地迁移生产库
    
    删列那次,生产库的列是被一条**查状态用的**一行 node -e 删掉的 ——
    服务当时没重启,人也没打算迁移。结果是对的(幂等;与备份逐行核对 76 行一致、
    origin 全部按映射回填正确),但「读一下状态」不该顺手改 schema 这件事值得知道。
    闸门只挡测试进程,故意不挡普通脚本(挡了服务自己也起不来)。
    注释里给了只读的正确姿势。
    
    全量:140 文件 / 1299 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 验收 4:从「静态成立」变成真的跑过一次
    
    原状态是 🟡「静态成立,未真跑」—— 人读代码把
    fixture verdict → FAILING_VERDICTS → readNodeProduct → callAgentNode 判 fail
    这条链读通了,但没有任何一次真跑或真链路证据。
    
    现在 graph-real-chain-smoke ⑥ 走完整条:fixture 产出 verdict="attributed"
    → 节点 fail、fail_reason=input_underspecified、沿 fail 边点亮下游、
    判词落在**下游节点**行上(判词的载体是「点亮它的那次状态转换」,不是失败节点自己)。
    反向验过:verdict 改成 passed 立刻红。
    
    写 fixture 时踩到一条值得记的:fail_reason 是封闭域 ATTRIBUTION_REASONS,
    第一版编了个 contract_invalid,被**按设计降级成 null** 并留一条 timeline ——
    引擎不认识的归因一律不信。判据没错,是 fixture 错。
    
    ## 验收 2 / 8:由 2026-08-20 那次真跑验掉(§六ter)
    
    · 2 —— 19 次真 agent 调用之后 `git status --porcelain` 完全为空,交付物都在项目根内。
      这正是 §六bis「后半被并行 agent 打破」那条的干净环境重验。
    · 8 —— 判据取「它引用了只可能来自提示词的内部 id」:triager 判词原文写着
      「org-coder-impl 私有目录 rn_96a8cd696dcc 下只有 _research.md」,
      rn_96a8cd696dcc 是 graph_run_nodes.id,agent 没有别的途径知道它。
    
    ## 验收 9:产出端通了,消费端没等到那个形状
    
    真跑里 triager **确实产出了两半地址**(orgNode + workNode)——
    这一条本身有分量:27-P3/P4 §八.22 第 13 处记的正是「引擎四环全通,
    但产出方 agent.md 里根本没有这个键」,那半条现在通了。
    
    紧接着「忽略跨层地址」那条**不是缺陷,是判据在正常工作**:这次回退发生在
    work 层内部,edge.to 本身就是 agent_call 节点,已是最细粒度,没有跨层可言。
    缺的是**那个形状**:一次 org 层回退边、且目标是 subgraph 节点。
    
    别把「机制没验」和「机制没有」混为一谈 —— 定点重置的逻辑侧有
    graph-cross-layer-address.test.js 三例注入式覆盖,缺的只是真跑里发生过一次。
    
    ## 顺带
    
    · template-verdicts 的样本非空那条补 ENOENT 容忍。它扫**活目录**,
      而 graph-real-chain-smoke 会在 system/agents/{aid}/{cid}/ 下建了又清运行态 ——
      文件在「列出名字」和「读它」之间消失是正常的。walk() 与 violations() 早就容忍了,
      只有这一处漏了,实测偶发红过一次。**扫活目录的守卫,每一处读都要容忍 ENOENT。**
    · 真跑 timeline 里发现一条未修的:每个节点的产物索引写入都撞
      UNIQUE idx_graph_files_path 失败并被降级成日志(重试时同路径再写一次)。
      后果是 files.json 产物索引在有重试的 run 上静默不全 —— 归 P4 一起处理,记在 §六ter。
    
    全量:139 文件 / 1297 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • agent_call 与 subgraph 是**两段不同的默认实现**(defaultAgentExecutor /
    defaultPlanWorkGraph),各有各的 onAcpEvent 累加、各有各的守卫开关分支。
    上一轮只覆盖了前者,「规划子图」那半条链仍然是零真实覆盖。
    
    现在走完:defaultPlanWorkGraph → AgentManager → ClaudeCliProtocol(mock)
    → extractPlannedGraph 解析回复 → createWorkGraphSafely → 建子 run。
    
    断言两处假 planner 天然绕开的接线:
    · 子 run 的定义确实来自 fixture 那张图(节点名 / systemAgentId 逐个对)
    · 规划那一轮烧的 token 也进了预算 —— 走的是 defaultPlanWorkGraph 自己那条
      onAcpEvent,与 agent_call 那条不是同一段代码
    ③ 的守卫断言补上 subgraph 分支的正反对照。
    
    新增 fixture workgraph-planning.json:图 JSON 同时放进 chunks 与 result
    (mock 下 output 取哪一半由 AgentManager 决定,不该由 fixture 来赌)。
    
    反向验过:抽掉 fixture 里的图 JSON、或把它的 usage 归零,对应用例分别变红。
    
    **覆盖边界写在用例注释里**:跳过 resolvePlanningAgentName 里的按需 hire
    (预塞一行 company_agents)—— hire 要一条真实公司记录,那条路径另有
    company-hire-org-layer.test.js 盯着;这条 smoke 验的是规划链路,不是招聘。
    
    全量:139 文件 / 1296 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 一、W7「零边图」不是待办 —— 它早修了,是注释在骗人
    
    `company-service.js` 的判据注释仍写着「判据用**有没有边**」,并留着一段
    「已知残留:单 org node 的最小图没有边,判据③不生效……真跑到那一步再看」。
    
    **那个残留在 `58a2865`(08-17)就修掉了**:`orgGraphMembership` 现在的主判据是
    `authored_by='planner'` 的节点存在(「orggraph-planning 提交过一张图」的直接证据,
    零边也认),边数降为存量行的回落判据;守卫 `company-hire-org-layer.test.js` 的
    `CO_ZEROEDGE` + `W7-fix` 两条盯着。
    
    注释没跟着改,于是它把读的人**引向一个已经修好的缺陷**去重修一遍 —— 我就是这么被
    带偏的:读到那段注释就没再往 `orgGraphMembership` 里跟,还据此往待办清单里写了一条。
    这是「指路牌失效」那一族,和 CLAUDE.md 死链同型,只是方向相反(那边指向不存在的东西,
    这边描述已不存在的洞)。
    
    判据的措辞也改了:**「成型」的判据在 graph-service.orgGraphMembership(),不在这里**,
    免得下一个人照注释理解。P3B 文档与 docs/README.md 的对应条目一并更正。
    
    ## 二、还上「真实路径 smoke 进 CI」那笔欠账的一半(27-P3/P4 §十 硬规矩 2)
    
    原话是「凡是靠注入点验的能力,都要有一条真实路径的 smoke 进 CI 才算验收完成 ——
    这条欠账仍未还」。此前唯一走真实路径的 `scripts/real-pm-smoke.js` 要真 API 额度、
    人工跑、而且验的是 DevLoop 不是图执行。
    
    做法是把「真 LLM」和「真代码路径」拆开:**只把最外层的 CLI 子进程换成 fixture 回放**,
    setAgentExecutor / setPlanWorkGraph 一个都不调。链路完整走
    advanceOne → defaultAgentExecutor → AgentManager → ClaudeCliProtocol
    → onAcpEvent → 落盘 → readNodeProduct → settleNode → spendBudget → 边界守卫。
    
    三条断言各盯一处**假 executor 天然绕开**的接线:
    · ① mock/files 真的落到节点产物目录(假 executor 直接 return,这一环整个不存在)
    · ② token 用量真的从 session/result.usage 数进预算 —— **就是 §六 验收 10 卡了两轮的那一维**
    · ③ boundaryGuardApplicable 在这条路径上为真(注入假 executor 时它恒为假,
         那条唯一有实际后果的分支实际零覆盖);带反向对照,注入后必须变假
    
    反向验过:断掉①的落盘、断掉②的累加,对应用例分别变红。
    
    为此改了一处 mock:session/result 的 usage 原来硬编码 {input:0,output:0},
    现在 fixture 可指定(缺省仍是 0,既有 fixture 行为一个字不变)。
    **不改这一处,token 那一维在 CI 里永远产不出数** —— 验收 10 当初就是卡在这儿。
    
    **没还清的部分写在文档里,别当成还清了**:真 LLM 仍进不了 CI;
    subgraph 那一支(defaultPlanWorkGraph)仍只有注入式覆盖;
    多节点 / 回退边 / 熔断没进这条 smoke,它只推一个节点。
    
    afterAll 清掉真实链路建出的公司态目录(gitignore 挡着不进库,但留着会让别的
    用例遍历 system/agents/ 时多扫一份)。
    
    全量:139 文件 / 1295 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 28-followups §3.3。`PUT /api/agent/:agentId/heartbeat`(前端 Agent 详情页走的就是它)
    用 `getBodyDir(id)` = `bodyDir(parseAddress(id).agentId)` 把公司段丢掉,
    然后**整份重写本体 config.json**。
    
    两个后果,都不报错:
    ① 跨租户串写:co-a:ceo 改心跳,co-b:ceo 跟着变(同一份本体);
    ② 一旦该 (agent, 公司) 有了公司态文件,读侧优先取公司态 →
       **这个端点的写入对它变成静默无效**。界面收到 {success:true},
       实际在跑的还是旧值;GET /info 同理报的是本体缺省,不是在跑的那份。
       症状是「界面写 30 分钟、调度器按 15 分钟在跑」,两边都不报错。
    
    company.js 那三个端点在 28 号已经修好了,这个入口漏了 —— 同一个 bug 的第二扇门。
    
    ## 改
    
    读写都走 heartbeat-service 的 readHeartbeatConfig / writeHeartbeatConfig,
    与心跳挂载读的是同一个答案。入参校验归服务(intervalMinutes 直接进 setInterval,
    NaN / 0 / 负数会让定时器退化成每 tick 触发一次,而每次 tick 真起一轮 LLM),
    非法值在写盘前抛,端点翻成 400。
    
    顺带:404 闸门也改走服务的寻址。`cid:role` 里冒号后面是**角色名**,未必等于本体 id
    (company_agents 的映射)—— 拿角色名当本体 id 判存在性,凡是角色名 ≠ 本体 id 的
    都会被判成「agent 不存在」,而写侧其实找得到。**判 404 和写入必须问同一个问题。**
    
    ## 守卫
    
    heartbeat-company-scoped.test.js 补 3 例(放进同一个 describe 而不是另起一个:
    夹具共用,另起的话上面那个 afterAll 会先把 server 关掉,实测 ECONNREFUSED):
    · 写公司态、本体字节不动,且**写完立刻能从读侧读回来** —— 这条才是「静默无效」的精确形态;
    · GET /info 报的是在跑的那份,不是本体缺省;
    · 非法 intervalMinutes → 400 且旧值还在。
    反向验过:把端点退回旧写法,这 3 条立刻红。
    
    ## 记一条没修的
    
    GET /info 的 404 闸门仍是 `getBodyDir(id)`,对 `cid:role` 会直接 404 ——
    那是 /info 整体寻址的既有缺口(它还要按同一个目录读 identity / agent.md / cron),
    不在心跳这一条的范围内。测试里用裸 agent id 绕开,并在注释里写明别当成修过了。
    
    全量:138 文件 / 1292 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 上一版漏了什么
    
    预算行挂在 root run,我就只在 root 的转换上结算。而**活动发生在树里任何一层**:
    人工重试一个 work 层节点动的是子 run(blocked → running),root 全程 blocked。
    
    后果:子 run 跑整场、墙钟一次都没开始计,旧锚点还留着 —— 第一次 checkBudget
    立刻判耗尽。单测当时全绿,因为它们只喂了 root。
    
    **是真起服务、真点重试才撞出来的**:那条卡住的 run 重试后子 run 起来了,
    root 的 started_at 仍是 08-18。
    
    ## 改
    
    · 任何一层进 running 都开始计时(预算行仍写 root,readBudget 自动上溯)。
    · 离开 running 时先查树里还有没有 running 的 run —— 有就不结算,
      否则一层停了会把整段计时提前掐断。
    · 树里已经有 run 在跑时,另一条进 running 不重锚,否则前一段被抹掉。
    
    ## 存量重锚必须在开库时做
    
    钩子只在「进 / 出 running」时触发,而升级那一刻正停在 running 的 run
    不会再进一次 running —— 旧锚点一直挂着,下一次 checkBudget 直接判耗尽。
    
    ⚠️ 第一版把这段迁移写进了 graph-db.js 的 migrate(),而 graph_runs 是
    work-graph-db 建的表、graph-db 的迁移块跑在它之前 —— 新库上直接
    `no such table: graph_runs`,getDb() 起不来。已挪到建这张表的模块里。
    和 §16.2 那次「加索引写进建表块」是同一类顺序问题,这是第二次。
    
    守卫 budget-wallclock-migration.test.js 走的是**存量库重启**那条路
    (照抄 tasks-executor-migration 的 freshDb 写法):老锚点归零、running 的续上、
    其余断开、没配 wallclock_ms 的不许被塞字段、坏 json 不许把开库卡死、幂等。
    
    ## 真跑验证
    
    重启服务 → 迁移在真库上跑到(run_9168bd24f46c 的 started_at 归 null、active_ms=0)
    → 重试 org-planner-sdd → POST /next → **节点 success,attempt 2**,
    预算 8/100 次调用、78,858/5,000,000 tokens,org-planner-benchmark 已转 ready。
    
    这条 run 从 2026-08-18 16:19 卡到现在,是 27-p3c-batch3-handover §五
    「还没解决的原始问题」。
    
    全量:138 文件 / 1289 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 病症:blocked 和墙钟预算互相拆台
    
    墙钟按 `now - spent.started_at` 算,而 started_at 是「第一次花钱的绝对时刻」。
    blocked 的定义恰恰是**挂起等人工** —— 预算耗尽、节点判罚都会把 run 打到 blocked。
    于是:人隔夜来处置 → 等待那段被算成「已用」→ 一起跑就报 wallclock_ms 耗尽
    → 再 blocked → 再等人。**一条过夜的 run 从此永远醒不过来。**
    
    而且报出来的原因具有误导性:写着 wallclock_ms(跑太久),实际是等人等太久。
    
    真事:run_9168bd24f46c(e2e-demo 做官网)08-18 15:48 起跑、16:19 判 blocked,
    只花了 7/100 次调用、69,961/5,000,000 tokens —— **预算用掉不到 2%,两天没能重跑**。
    它正是 27-p3c-batch3-handover §五 那条「还没解决的原始问题」。
    
    ## 修法
    
    spent 里改记 `active_ms`(累计活跃时长),started_at 退化成「当前这一段的起点」,
    不在 running 时为 null。已用 = active_ms + 当前段。
    
    起止结算挂在 graph-run-sse.afterRunTransition 这个**单一漏斗**上,不去枚举
    「哪些地方会让 run 停下来」—— 枚举漏一条就是静默少算。
    
    ⚠️ 钩子必须放在那个 early return **之前**:进 running 既不是收敛、也不空坑,
    两个条件都不满足,放后面就变成「只结算停下来、从不开始计时」,
    症状是 active_ms 恒 0、墙钟形同虚设,而且照样全绿。守卫里专门有一条钉这个位置
    (反向验过:把钩子注释掉,那一条立刻红)。
    
    spendBudget 不再兼职锚墙钟 —— 一个事实一处持有者。
    
    ## 存量行
    
    active_ms 缺失的行:旧锚点里混着等待时间,真实活跃时长复原不出来,
    按 0 起算、一笔勾销。有意为之 —— 另外两维(调用次数 / tokens)是精确的且没受影响,
    真正兜住花钱的是那两维,墙钟只是跑飞保护。所以那条卡住的 run 不需要动库,
    重试进 running 时自动重新锚定。
    
    ## ponytail
    
    只认 running 一个态。manual 模式下两次点击之间 run 也停在 running,
    那段人类思考时间仍会计入 —— 上限已知,比原来跨天累计小几个数量级。
    要更准得按 agent 调用的起止配对计时,那是另一套账。
    
    ## 顺带
    
    goal-task-acceptance 两条用例原先 deepEqual 整个 budget 对象,现在剥掉 spent
    只比申报的额度 —— 它守的是「用户填的额度没被静默换成默认值」,
    跟运行期账本长什么样无关;账本另有断言(起跑那一刻 agent_calls/tokens 仍是 0)。
    
    全量:137 文件 / 1284 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 病症
    
    agent-chat-tenant-isolation.test.js 每跑一次,往共享生产库 data/graph.sqlite
    落 2 条 goal + 2 条 task。实测 29→31。库里 31 条 goal 里 **22 条是它的残留**,
    测试期间一直全绿。
    
    根因是批 5 的存储迁移留下的半截清理:/task/dispatch 的元数据从
    system/tasklog/*.json 换进了 tasks 表,而这个用例的 afterAll 只会清那个目录 ——
    清理代码还在、清的东西已经不在那儿了。
    
    ## 这是第四次,但换了个方向进来
    
    前三次(BENCHMARK_DB_PATH / USERS_DB_PATH / SKILLS_DIR,记在
    test-isolation-env.test.js 文件头)都是「设了 env,生产代码不读」。
    那条守卫查的正是这个形态。
    
    这次是**一个字都没设** —— 没有可查的东西,守卫自然查不出来。
    静态扫源码也判不出「这个测试会不会开库」(要跨 require 链推导)。
    
    ## 修法
    
    1. 用例自己开一次性库(fs.mkdtempSync + GRAPH_DB_PATH),afterAll 连目录删掉。
       赋值必须在 require 路由**之前** —— graph-db.js 的 DB_PATH 是模块级 const。
    2. graph-db.getDb() 加运行期闸门:VITEST 为真且没设 GRAPH_DB_PATH → 抛,
       报错里点名 env、点明「要在 require 之前设」。
       放在 getDb() 不放模块顶层:只为取常量而 require 是合法用法
       (goal-task-model.test.js 就这么用),顶层抛会误伤。真正开库才是越界那一刻。
    3. 守卫 graph-db-prod-guard.test.js,4 例:撞库要抛且报错可照做、设了 env 要能开、
       没有 VITEST 时不生效(防止把生产一起锁死)、样本非空。
       反向验过:注释掉闸门那一行,第 1 例立刻红。
    
    ## 顺带修一条 flaky(我自己上一版留下的)
    
    agent-docs-port.test.js 先 walk 出文件清单再逐个读,中间别的用例把 zz-* 夹具的
    运行态目录清掉 → ENOENT。**报错长得像端口违例,其实跟端口无关**,实测 8 次跑中 2 次红。
    只放过 ENOENT 一种(文件没了),其它读错误照抛 —— 一律 try/catch 吞掉的话,
    权限错、编码错会被当成「无违例」。
    
    ## 存量清理
    
    删掉生产库里 created_by LIKE 'zz-tenant-%' 的 22 goal + 22 task
    (备份 /tmp/graph.sqlite.bak-c3a5d46)。零 run 引用它们。
    其余 9 条 goal 是真实数据,一条没动。
    
    全量:136 文件 / 1276 测试,连跑 8 次全绿(改之前 8 次里红 2 次)。
    跑完后生产库行数零增量。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 主体由另一个会话完成(其会话已关闭,由我接手核对并提交),我补了两处修复
    与一条守卫。
    
    ## 它修的是什么
    六处「服务端自己给某个 agent 发一条 chat」各自写死
    `hostname:'localhost', port: systemConfig.port`(3010)。单测把路由挂到
    `app.listen(0)` 的临时端口上,回调却照样打进**线上 3010** —— 每跑一次汇报
    用例就给真 CEO 发一条 chat、起一个常驻 claude CLI 子进程、留一个
    callback-T-* 会话目录。症状完全静默:测试全绿,只有进程数和内存在涨。
    
    (这条是被我在批 5 新加的 task-dispatch-store 用例触发出来的:那之前没有
    任何测试会走到汇报回调。)
    
    抽成 selfChat:端口从 req.socket 取(收到这条请求的那个监听端口,天然就是
    「我自己」),测试环境整体短路。
    
    ## 我接手时发现并修掉的两处
    `task/transition` 的两个调用点删掉了 `const chatReq` / `const postData` 的
    声明,**却留下了 `chatReq.write(postData); chatReq.end();` 两行**。
    `node --check` 照样通过 —— 那是运行时 ReferenceError 不是语法错;而它在
    `setImmediate` 里抛就是未捕获异常,**直接杀进程**。
    
    真起服务打了一遍 POST /api/agent/ceo/task/transition:200,服务仍在。
    修之前这条路必炸。
    
    ## 守卫:server/tests/unit/self-chat-callbacks.test.js
    1. chatReq / postData 只许出现在 selfChat 函数体内 —— 「删了声明留下调用」
       这类改造残留,语法检查和单测都抓不到,只有真跑到那条路径才炸;
    2. 不许再有第二处自己拼 http.request 打 systemConfig.port(分叉的开始);
    3. 测试环境必须短路。
    
    第一版守卫把 selfChat 上方那段**注释**里引用的写法当成漏改报了红 ——
    那是文档不是代码,已跳过注释行。
    
    全量:135 文件 / 1272 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## ① system/tasklog/ 改名保留(§十二.5)
    派单存储统一进 tasks 表后,旧目录改名为 system/tasklog.bak-unify/ 而不是
    直接删 —— 跑顺一个版本再清。回填脚本改成**两处都认**(先原位置再备份位置,
    顺序不能反:万一有人从备份恢复出一份新的 tasklog,那份才是要回填的)。
    
    ## ② agent 提示词里的端口 3000 → 3010
    packages/agent-core/base/ 下那几份是**所有 agent 的工具说明**,印着
    curl http://localhost:3000/... 而实际端口是 3010。agent 照着打拿到的是
    连接被拒、不是「端口错了」,它多半会去猜别的原因。
    
    这不是假想:批 5 回填出来的历史派单正文里,有人手写了「(端口 3010,
    不是 3000)」在纠正它 —— 靠人肉在每条任务书里打补丁。
    
    守卫 agent-docs-port.test.js:扫 base 模板与 agent 本体,禁 localhost:3000。
    判据刻意宽松(不校验「必须等于 config.port」—— 端口可被 PORT 覆盖,写死会在
    部署环境误报)。带「样本非空」前置断言。第一版扫出 5 条误报,全是某 agent
    目录下 Python venv 里 playwright 的 ThirdPartyNotices —— 那是第三方文件不是
    提示词,已把 venv/site-packages/workspace/conversations/train 一并跳过。
    
    ## ③ 边界守卫不再把并行会话的改动算到 agent 头上(§15.5 第 1 条)
    守卫的前提是「窗口内新增的仓库改动 = 这个节点写的」。多会话共用 worktree 时
    这个前提是假的 —— git status 的差集里没有「谁写的」这一维。实测已发生:
    批 3 的开发改动把一条 run 判成 boundary_escape 卡住。
    
    可检测的信号:节点**开跑那一刻**仓库就已经有源码目录的未提交改动 → 有人正在
    这个 worktree 上开发 → 窗口内新增的源码改动无法归属 → **降级为只记证据、
    不判罚**(与既有的 unattributable 桶同一语义),而不是关掉守卫:证据照留,
    人一眼看得出「这次没判罚,因为当时仓库是脏的」。
    
    BOUNDARY_GUARD=strict 强制判罚(CI / 干净环境),=record 一律只记。
    守卫 8 例,含关键对照:仓库脏时不判、干净时同样一处越界仍判 fail ——
    守卫没被削弱,只是不再乱扣帽子。
    
    ## ④ TaskTimeline 的标签不再撒谎
    conversationCount 在批 5 之后只会是 0/1(「有没有起过执行会话」),而界面
    写的是 "Convs: N" —— 读的人会以为这条任务只有一次对话。改成有/无的表达。
    agents 的语义变化在注释里说明。
    
    注:innovator-hunt/todo.md 与 reddit-scraper/todo.md 里的端口也改了,
    但它们是 gitignored 的运行态文件,不入库。
    
    全量:134 文件 / 1268 测试全绿,web:build 通过。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 第 6 步:调度器只管图任务(一行判据,但漏了是静默的)
    listQueue / deriveQueueBlocks / sweep 三处都加 executor='graph'。
    派单任务虽然也进 tasks 表,但它的执行载体是「直接起一次 agent 对话」,
    由 /task/dispatch 自己触发。漏这一条的后果:调度器拿一条根本没有 run 行的
    task 去出队 → syncRunNodesToDef 判空 → abandonRun → **把一条正在被 agent
    执行的派单收成 cancelled**。守卫用例把它摆成「最像可出队」的样子
    (queued + auto + 无依赖),只有 executor 这一维拦得住它。
    
    ## 第 5 步:GET /api/tasks 改读表,删掉 122 行的会话反推
    原来的 getAggregatedTasks 是启发式:扫全部 agent 的 conversations,按会话 id
    里的 T-xxx 归组,再用三条策略(meta 的 parentTaskId / from agent 的活跃会话
    时间包含 / master conv 的标题相似)**猜**父子关系。猜出来的树没法验证对错,
    而且每次请求都要遍历所有 agent 的会话目录 —— route 那 10 秒缓存就是为它加的。
    
    现在父子关系是 tasks.parent_task_id,派单时就显式写下来了,不用猜。
    task-service.js 391 行 → 236 行;_inferParentChild / _loadTaskMetas /
    TASK_META_DIR 一并删除(改完就是零调用方)。
    
    **getTaskTimeline 保留**,但来源拆清楚:任务树从表来(parent_task_id,
    可验证),时间线正文仍从 agent 的 conversations/ 扫 —— 那是一手记录,
    没有替代品。删掉的只是「从会话反推任务树」那一半,不是会话本身。
    
    两处口径变化(写在代码注释里):
    - agents:原来是「会话里出现过的 agent 集合」,现在是「谁派的 + 派给谁」。
      对派单任务这两者本来就是同一件事
    - conversationCount:原来是扫出来的文件数,现在是「有没有起过执行会话」(0/1)。
      为一个列表字段遍历所有 agent 的会话目录,正是这次要去掉的那笔开销;
      真要看内容走 /api/tasks/:taskId/timeline,那条仍读一手记录
    
    ## 漏改一个消费方,真打接口才发现
    routes/tasks.js 自己还在算 t.conversations.length,而 conversations 是会话
    反推那套的产物、已随之删除 → 500「Cannot read properties of undefined」。
    测试没覆盖这条路(它只在真跑接口时才走到)。已改成用 t.conversationCount。
    
    真打验证:GET /api/tasks 返回 17 条、12 个字段齐全 —— 条数比改前多,
    因为测试跑出来的派单现在也进表了,这本身就是「存储真的换了」的证据。
    
    全量:132 文件 / 1257 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • **本文件除了 saveTaskMeta / loadTaskMeta 的内脏和两处扫目录,其余一行没动。**
    /task/dispatch 与 /task/report 的 URL、请求体、响应体形态全部原样 —— 它们
    印在 packages/agent-core/base/common/tools-base.md 里,是所有 agent 的工具
    说明,改一个字等于同时改所有 agent 的提示词。
    
    ## 换掉文件存储的三个理由
    - 去重要「readdirSync 整个 tasklog + 逐个 JSON.parse」,任务一多就是 O(n) 次
      磁盘读,且没有索引可用
    - 派单任务在库里看不见 —— 控制台任务列表读 tasks 表,派单那套是平行世界
    - 同一个 T- 前缀两套 id 体系,读日志时分不清
    
    ## 落点
    - saveTaskMeta / loadTaskMeta 变成 tasks 表的适配器(upsert + 反向映射),
      meta 的形状一个字段不少:调用方按字段名取值,形状变了就是契约变了
    - 去重与父任务推断改成带索引的 SQL(idx_tasks_assignee),**判据一个字没改**,
      换的只是从哪儿把候选捞出来。查不动时留声不静默 —— 静默跳过等于去重形同虚设
    - 加 summary / execution_conv_id 两列(用户选的方案 A)
    - sourceConvId **删掉**:全仓零读取方,正是本仓库反复立案的「实现在、零调用方」,
      不往新表里再搬一份
    - objectiveId 不单独存:它恒等于 okr node id 的首段(OkrService.parseNodeId 与
      reportRunOutcome 同一口径),且两处读取方都是 `if (objId && meta.nodeId)`
    
    ## 真打过一遍,不只是测试绿
    照 tools-base.md 印的请求体原样 POST:200 + 老形态响应;再打一次 → 409 且
    existingTaskId 正确;落库 executor='agent' / assignee=qa-judge / status=running,
    自动建的 goal 标题「来自 ceo 的派单」、okr_node_id 落对;/task/report 之后
    status→completed、summary 与 finished_at 都在;loadTaskMeta 往返出来的形状与
    老 JSON 逐字一致,objectiveId 从 nodeId 首段推出 O-001;system/tasklog/ 不再
    产生新文件(仍是回填前那 4 个)。
    
    守卫:server/tests/unit/task-dispatch-store.test.js(4 例,盯的是「形状没变」)
    
    全量:132 文件 / 1255 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »