• 自动模式的驱动原语 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 »

  • ## 决策 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 »
  • ## 一、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 »
  • ## ① 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 »

  • agent 之间派单(POST /api/agent/task/dispatch)与「跑一张 org graph」是
    两种执行载体,硬说成一回事是骗自己:前者直接起一次 agent 对话,后者走
    调度器出队 + 图执行。所以显式建模而不是让调度器去猜 ——
    executor(graph|agent)、assignee_agent_id、parent_task_id(派单链,
    与 depends_on 的「同级先后」不是一回事)。
    
    迁移的落点对:旧行 executor 缺省 'graph'(批 1-4 建的全是图任务),
    另两列 NULL。
    
    ## 加这一步时踩到并修掉的一个会让存量库开不了的 bug
    
    第一版把 CREATE INDEX ... ON tasks(assignee_agent_id) 写进了建表块。
    建表块整体先于迁移块执行,而存量库上 CREATE TABLE IF NOT EXISTS tasks
    是 no-op —— 表还是批 4 的形态、没有那一列,于是这句索引直接抛
    「no such column」,getDb() 起不来 = **所有存量库一启动就炸**。
    而新建库(单测默认走的那条路)完全正常,只测新建库根本看不见这个洞。
    已把索引挪进迁移块、排在 ALTER 之后。
    
    守卫:server/tests/unit/tasks-executor-migration.test.js —— 真造一个
    批 4 形态的库(删索引 + 删三列)再重开,断言三列与索引都补回、
    executor 默认值是 'graph'。
    
    另记一处自己踩的:SQL 模板字符串里的注释用了反引号,把 db.exec 的模板
    提前闭合了。DDL 注释里一律不用反引号,已写进那段注释。
    
    已知不对称:ALTER TABLE ADD COLUMN 加不了 CHECK 也加不了外键,所以存量库
    上 executor 的取值域只由应用层守,与新建库的 CHECK 不同源。写在注释里,
    免得下一个人以为库层拦得住。
    
    全量:129 文件 / 1245 测试全绿(+2 新守卫)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 批 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 Dir »
  • 队列与调度(§五.1):
    - 下达任务不再因「公司已有 active root run」吃 409,改为建 queued run + queued task
      排队;出队走 updateRunStatus 的 CAS + BEGIN IMMEDIATE 准入事务,触发挂在
      transitionRun 这个唯一漏斗上,60s sweep 兜底。
    - 依赖已不可能满足的排队任务就地推导成 blocked,且每次 tick 重算不落死,上游重跑
      成功后能原路解开。
    
    恢复路径(批 3 复核 F1/F2):三条恢复转换的 from 全在活跃集之外,撞坑满时退回队列而不是
    永远躺在 blocked;退不回去的裸 run 干净拒绝,不留半截状态。
    
    控制台三处可见性修复 —— 症状全是纯视觉、不报错、git status 干净:
    1. 顶栏墙钟恒 `now - started_at`,没有终点。一条昨晚就 blocked 的 run 第二天还在涨到
       「16h 58min」,而那是页面上唯一的时间信号 —— 「已经停了等人处置」被显示成了
       「还在跑再等等」,这两件事需要的动作相反。终点改取 run.updated_at,并标「已停」。
    2. 越界日志只报「留下 N 处改动」,文件清单躺在 timeline.ndjson 里翻不到。判罚就是按
       这份清单下的,而人要判断「这真是 agent 写的吗」唯一的依据就是路径(并行的开发会话
       改宿主仓库会被算到当时在跑的节点头上,那是误判,看一眼就知道)。改为列出 entries。
    3. run 收敛成 blocked 时只把 task 写成 blocked、不写原因,任务列表那一行只剩一个橙色
       的「挂起」,必须逐层点进 org 图 → work 图 → 节点详情才知道发生了什么。改为把
       「卡在哪个节点 + 什么 fail_reason + 判词摘要」写进 tasks.blocked_reason。
       原因绝不带 UPSTREAM_BLOCK_PREFIX —— 带了会被调度器当成依赖推导出的 blocked 自动
       推回 queued,而这类 blocked 本来就等着人处置(守卫在 task-blocked-reason.test.js)。
    
    测试:126 文件 / 1201 用例全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • 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 Dir »
  • 把「人类提的一个需求」与「机器执行的一件活」拆成两级。这不是新概念:
    27-NAMING §四 早就定义了 goal 1 : task N,graph_runs.goal_id / task_id
    两列从 P3 起就一直在写,却没有任何表接得住 —— 本次补上那两张缺席的表。
    
    ## 交付
    - goals / tasks 两张表 + CRUD + depends_on 环检测 + ID 生成器
      T-{base36}-{4hex}:原格式同毫秒必撞,而 tasks.id 是主键,
      「一个 goal 拆 N 条 task」是循环建行,撞了就是 500
    - goal / task 全套端点;POST /:cid/tasks 向后兼容(不带 goal_id 自动
      建 goal);退役 /api/goals 与 /:cid/goals 两个 OKR 别名(零调用方)
    - 终态回写 + goal 收敛 + OKR 写一次:钩子挂在唯一漏斗
      graph-run-sse.transitionRun 上,而不是枚举路径 —— stopRun 与
      「预算耗尽 → blocked」两条最常见的释放路径都不走 settleRun,
      枚举一次漏一次。goal 收敛用 CAS 闸门,并行完成的 N 个 task 只有
      一个赢家做不可重放的副作用(写 OKR / 唤醒上级)
    - blocked 在 task/goal 两层都不是终态:它是「挂起等人介入」,
      与 completed/failed/cancelled 不同类。否则预算耗尽后加预算恢复、
      跑完,两次条件更新静默命中 0 行,最终 run=completed 而
      task=blocked/goal=failed/OKR=failed 永久对不上(实跑复现过)
    - goal 级是唯一汇报出口:删掉 run 级 reportRunOutcome(零其它调用方),
      1 goal:1 task 形态下它会把上级 agent 就同一件事唤醒两次
    - dispatch 挑第一条可跑的 draft,而不是恒取 seq 最小那条 ——
      否则「第 1 条依赖第 2 条」这种合法输入会让 goal 永远下达不了
    - 路径缺陷:writeTaskGoalArtifact 不认 GRAPH_RUNS_DIR 而读侧认,
      设了 env 就写进 A 处、从 B 处读,describeTask 拿到 null 后静默退回
      兜底文案「执行图节点任务」;三处模块加载时快照改为调用时读
    - 存量回填脚本(幂等 + --dry-run):不做的话批 1 反而让历史任务
      更看不见 —— 正是本批要修的问题的反向放大
    
    ## 过程
    三轮多 agent 编排 + 每轮对抗复核,共报出 19 条缺陷(10 条首轮实现的、
    4 条第二轮修复引入的、5 条判定为非问题)。全部已修并有用例覆盖。
    新增测试 4 个文件,含不变式测试 runs-root-single-holder(凡是声明了
    GRAPH_RUNS_DIR 覆盖的模块,该覆盖必须真的被用到)。
    
    全量:116 文件 / 1057 测试全绿。
    
    ## 未做(属批 2-5)
    task-planning 与 goal 级 org-validator 验收(批 2)、调度器与
    run_status='queued'(批 3)、并行(批 4)、两套任务体系统一(批 5)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 承接 310a2b2 的两个遗留:
    
    ① 四类被错标成心跳
       company-heartbeat-service._sendAgentChat 给五类调用(心跳巡检 / Pipeline 步骤 /
       日程 / 告警 / 日报)无差别打 isHeartbeat:true,于是对话列表里全都显示成
       「⏱️ 自主心跳巡检」、meta.type 一律 'heartbeat'。改成显式传 kind,五类各自标注。
       isHeartbeat 仍然发,但只对真心跳为真,老消费方行为不变。
    
    ② 310a2b2 让 Pipeline 重试变成了冷启动
       Pipeline 步骤的 convId 是 `pipeline-{runId}-step-{i}`,被驳回重做时**复用同一个**。
       310a2b2 按 isHeartbeat 一刀切回收,等于每次重试都要走 buildPrompt 场景 C 重注入历史。
       现在回收策略挂在 CALL_KINDS 表上,判据只有一条:**convId 会不会被下一轮复用** ——
       会复用的(pipeline / 人类对话)留着走 TTL,不复用的(心跳 / 日程 / 告警 / 日报)跑完即回收。
       不按「是不是后台调用」分:五类后台调用里 pipeline 就是要留的那一类。
    
    守卫补到 12 条(原 3 + kind 标注 5 + 回收策略 4)。对 310a2b2 跑过:8 红 4 绿。
    convId 一律用固定字面量而非拼时间戳 —— 撞上 test-id-stability 那道防线后按它的判据改的,
    固定之后第二次跑还顺带走了「复用已有对话」那条分支。
    
    文档:
      - CLAUDE.md 注意事项加一条(新增会话创建路径时必须同时定回收路径,附症状与守卫位置)
      - 28-followups 守卫清单补第五条,并把「前四条守单一持有者」与第五条「守资源生命周期」
        的形状差异写明;顺手修掉表格已有 4 行、结尾却仍写「这三条」的失配
      - .env.example 补 AGENT_SESSION_IDLE_MS
    
    全量单测 667/667 绿。重启后 /api/health 正常,心跳照常挂载。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 28 号把本体根从 system/templates/ 搬到 system/agents/,agent-locator 就是
    为了当这个路径的唯一持有者而建的。但搬完之后仓库里仍有 13 处自己拼这个路径,
    等于 locator 只是多了一份副本、没有真正收敛:
    
      server/routes/company.js            9 处(6 个 TEMPLATES_DIR + 3 个 DATA_DIR,
                                              同一文件里只有 1 处用了 locator)
      server/services/task-service.js     1 处
      server/src/core/skill-config-loader.js  1 处
      server/src/core/devloop/loop-controller.js  1 处
      scripts/extract-agent-prompts.js    1 处
    
    危害不是「不好看」,而是下一次再搬时改 12 处、漏 1 处:漏掉的那处指向一个
    不存在的目录,而本仓库读这类路径几乎都包在 fs.existsSync() 守卫里 ——
    目录没了不报错,只是静默跳过。这个形状刚在 scripts/demo-harness-company.js
    上实测过(base 本体没挂上,demo 跑出来的 agent 没有四段式骨架,脚本照常打印成功)。
    
    局部变量名保留不动(TEMPLATES_DIR / DATA_DIR / SYSTEM_AGENTS_DIR / agentsRoot /
    OUT_DIR),只把取值改成 locator.AGENTS_ROOT —— 名字是局部别名,无害;
    「路径这个事实」则收敛成一处,下次再搬只改 agent-locator.js。
    
    新增 server/tests/unit/agents-root-single-holder.test.js 锁住这条不变式:
    扫 server/ packages/ scripts/ 下所有源码,禁止 path.resolve/join 自己拼
    「以 ../ 开头、以 system/agents 结尾」的字面量。带「样本非空」前置断言,
    防止扫描路径写错时退化成永远通过。改坏一处实测能精确点名到文件行号。
    
    线索来自并行会话(它在准备改 company.js 时发现同一文件里 7 份定义)。
    实际比它看到的更多:它只查了 TEMPLATES_DIR,同文件另有 3 个 DATA_DIR、
    另外 3 个文件各 1 处。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 设计见 docs/done/system-design/28-agent-registry-unification.md。
    
    改造前 Agent 本体散在三处(system/agents/ 11 个、system/templates/ 50 个、
    data/companies/{id}/agents/ 17 个),同一个 AgentScaffold 被三个 baseDir 实例化。
    后果是同一角色在 N 家公司下有 N 份互不相干的副本,模板更新不会同步;
    org-coder / org-planner 这类走 hireFromBaseMetaAgent 造出来的本体
    legacy_path 指回自己公司目录,永远无法被第二家公司复用。
    
    本次:
    - G1 单一注册表:system/templates/ 整体并入 system/agents/,base → _base。
      7 个同名 id 逐个裁决,被丢弃的一份归档到 docs/_archive/ 而非删除。
      source_kind(template/global_agent/company_hired) → origin(builtin/created)。
    - G2 运行态下沉:CLI 的 cwd 从本体根改为 system/agents/{aid}/{cid}/{taskId}/。
      memory / conversations 落公司态 {aid}/{cid}/,按公司隔离不串。
      sessionPool 的 key 含 (companyId, taskId),两家公司调同一 agent 不复用会话。
    - G2b 产物不下沉:交付物仍落 data/companies/{cid}/,system/agents/ 只留运行痕迹。
      写入边界按链路分叉——graph 节点走节点私有目录(被 indexNodeOutputs 索引),
      chat 走公司 workspace,同一提示词里不再出现两条互斥指令。
    - G3 公司只存引用:data/companies/{id}/agents/ 删除,新增 company_agents 关联表。
      不复用 org_graph_nodes——后者是执行流图,work 层本体被雇时不写它,天生覆盖不全。
    - G4 缺则创建:本体缺失时从 _base 复制骨架进 system/agents/{id}/,
      agent-creator 填 agent.md/soul.md 后才从 draft 转 active(哨兵判据,
      骨架未填时哨兵仍在,不许被 hire)。产物即刻是全系统可复用的本体。
    
    新增 server/services/agent-locator.js 收编原先散在 6 处的寻址逻辑。
    
    边界守卫(graph-engine)的豁免从「本体根上五个文件名」放宽为整棵 system/agents/:
    新本体的骨架本来就要进 git,gitignore 挡不住 ?? system/agents/{newId}/,
    旧判据会把一个真正成功的招聘节点判成 boundary_escape fail。
    
    存量迁移见 scripts/migrate-agent-registry.js(幂等,备份到 data/_migration-backup-*)。
    
    真 Claude CLI E2E 已验证:新建公司 → orggraph-planning 自动规划 →
    自动招聘 org-planner/org-coder/org-validator 三节点流水线;
    data/companies/{cid}/agents/ 不存在,company_agents 4 行,
    system_agents 56→56(零新建私有本体,纯复用),git status 零增量。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • 控制台原先只能看引擎的事件轨(stream.log / timeline.ndjson),里面是结构化事件、
    自由文本还被截断到 600 字;引擎更是根本不消费流式 chunk(onAcpEvent 只取
    session/result 的 usage)。所以 LLM 的逐字输出从来不在它的轨道上。
    
    新端点 GET /api/graph-runs/:id/nodes/:nodeRunId/transcript **惰性读** Claude CLI
    自己的会话文件:不进 SSE、不进节点行、不落第二份库 —— 那正是引擎当初决定不存全文的
    三条理由要避免的(输出无上限而 getRunState 每次轮询 SELECT * 全量回传 /
    产物已有 output_ref 这个唯一真相源 / fail_reason 是选边匹配键不能掺自由文本)。
    这里没有推翻那个设计,只是多开一个读取入口。
    
    目录推导与条目规范化抽到 server/services/agent-transcript.js,
    scripts/tail-agent.js 改为复用它 —— 一份实现两个入口。今天已经修过三次
    「两份必须保持一致的实现漂了」,不想制造第四次。
    
    ## 界面上明说的三个前提(不藏)
    
    1. transcript 按 (公司, 角色) 分、**不按节点分**,只能用节点 started_at/completed_at
       截时间窗口 —— 近似而非精确对齐,窗口边界原样显示。
    2. <env_status>(harness 每轮注入的环境块,几 KB 机器噪音)服务端剥掉,
       但**剥了几处如实显示**。静默删内容比留噪音更坏。
    3. 默认只看 agent 说的话;喂进去的内容(提示词全文、工具返回)要勾选才出来 ——
       提示词里有写入边界与公司上下文,不默认摊开,但可以看。
    
    subgraph 节点不给这个 tab(它自己不调 agent,活在子图里)—— 给了就是个空 tab,
    而空 tab 会被读成「没记录」。正在跑的节点靠「刷新」取最新,**不轮询**
    (整份会话几百 KB,跟着 run 状态轮询太贵)。
    
    ## 三处「看着对、实际错」都有用例钉住
    
    ① listRoles 的目录前缀差一个 '-':path.join 丢掉空的最后一段,切出来的角色名
       全带前导 '-'(实测 '-sdd-planning')。我是靠肉眼看输出发现的,所以补回归测试。
    ② 路由取本体取错层:本体绑在定义层节点上,graph_run_nodes 只有 def_node_id,
       第一版直接读运行时行 → SqliteError: no such column。
    ③ 找不到文件时回 ok:false + 期望目录,不回空列表 ——
       空列表会被读成「这个 agent 没说过话」,而真相是「我们没找到文件」。
    
    另外测试当场抓到我自己那个诚实机制的漏洞:整条正文都是 env_status 的条目会被剥空,
    normalize 早返回 null 导致它剥掉的那一处**没计入总数** —— 号称如实回报却少报。
    改成 parts 空但剥过东西时仍返回,由 readEntries 先记账再决定入不入列表。
    
    ## 验证
    
    880 全绿(88 文件)。3 处变异精确变红后逐字还原。
    运行中的实例上走查过:org-planner-sdd 的原文逐条带时间戳显示,
    第一条就是它去读上游 prd.md / contract.json —— R-19 那条链路被 agent 真的用上了。
    subgraph 节点(org-planner)确认没有这个 tab。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 为什么加
    
    「输入输出」tab 把产物的绝对路径摆出来了,但那是服务器本地路径 —— 浏览器里点不开,
    走查时只能靠肉眼确认「路径长得对」,而真正要确认的是「这份 brd.md 里写了什么、
    下游读到的是不是它」。
    
    ## 前端
    
    新增 web/src/components/graph-console/FilePreviewModal.vue,输入/输出两处路径
    改成可点按钮。渲染三档:.md 走 marked(已是既有依赖,没新增包)、.json 重新缩进、
    其它原文。两处刻意的选择:
    - .json 解析失败时**退回原文**并说明,不替它藏起来 —— 产物 json 写坏了本身就是要看的信息
    - 超过 512KB 时后端回 truncated,界面如实说「不是完整内容」,不装作读全了
    output_ref 那一行不做成可点:它是目录不是文件。
    
    后端没有新造端点:复用现成的 GET /api/company/:cid/files/content,它的根目录正是
    data/companies/<cid>,而 graph_files.rel_path 本来就是相对公司根的,正好对上。
    
    ## 修掉六处路径越界(真漏洞)
    
    company.js 里 3 个 GET + 3 个 PUT(templates / company / workspace)用的是同一个写法:
        if (!absPath.startsWith(safeBoundary)) → 403
    「一个目录名是另一个的前缀」时它会放行:
        cid = co-a,path = ../co-a-evil/company.json
        → abs = .../data/companies/co-a-evil/company.json
        → startsWith('.../data/companies/co-a') === true → 放行
    于是公司 co-a 能读 co-a-evil 的文件;PUT 那三处是同一缝隙上的跨公司**写**。
    抽 resolveInside() 用 path.relative 判断,六处共用一份(复制六份判据迟早会漂)。
    新增 7 条用例走真 HTTP 验,PUT 那条断言的是**文件没被改**而不只是响应码。
    
    ## 顺带修掉一个被你的实际使用触发的 bug
    
    跑全套时 graph-seed-service 的对账用例红了,报
    `org_graph_nodes WHERE company_id='mycompany' expected 10 / actual 9`。
    不是并发偶发 —— 单跑也红。原因:seedCompanyAgents 对 type='meta-bootstrap' 的角色
    是 continue(既不落 org_graph_nodes 也不落 system_agents),而 verifyCounts
    复算期望值时把 agents[] 全算进去。mycompany 的 10 个角色里 orggraph-planning
    正是 meta-bootstrap → 差 1。
    
    也就是说:**界面上点一遍新建公司就会踩到**。这条缺陷上一轮就被预告过
    (当时那个 agent 说「不在我的独占文件里」没修),今天被真实使用兑现了。
    修法是让 verifyCounts 复用同一个 isMetaBootstrap 判据,不复制那个 type 判断。
    新增独立测试文件(不并进 graph-seed-service.test.js —— 往它的 hermetic 夹具里
    加角色会连带改既有断言),用 GRAPH_SEED_ROOT 自建最小夹具。
    
    那个文件里第二条用例是刻意的:只断言「对账干净」时,seed 一个都没种也会“通过”,
    两种原因症状相同。第一版就是这么错的 —— 我误以为 runStartupSeed 会种公司角色
    (它只做 reconcile + 全局 agent + 模板),是第二条断言把「夹具没种」和
    「过滤没生效」分开的。
    
    ## 验证
    
    871 全绿(87 文件)。3 处变异精确变红后逐字还原
    (startsWith 回退 → 精确打红跨公司读/写两条;verifyCounts 去掉过滤 → 打红对账条)。
    拿运行中的实例 + 用户真实公司 mycompany 走查过:读到真实 brd.md 内容,
    `../idearun/company.json` 与 `/etc/hosts` 均 403。web:build 通过。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 起因是一个看起来无害的问题:<repo>/data/users.sqlite 为什么是 0 字节。
    答案是没人用它——user-db 走三级上跳落在仓库外的 ~lemonAI/data(跨兄弟仓库
    共享的平台数据,与 skill-db / benchmark-db 一致;仓库内 data/ 放 graph/companies/
    devloop 这类租户数据)。那个空文件是遗留,已删。
    
    顺着查下去发现三处同型缺陷,形态都是「产生 → 传递 → 落库 → 读回」漏掉读回,
    而且是最有欺骗性的一种:测试设了隔离用的 env,没有人读,于是数据全写进
    **共享的生产库**,测试照样全绿,afterAll 删的 /tmp 文件从来没被创建过、清理还“成功”。
    
    1) USERS_DB_PATH 压根不存在
       user-db.test.js 只能直写共享用户库(注释老实写着 "uses hardcoded
       data/users.sqlite"),afterAll 是空的、拿「邮箱按 UUID 唯一所以不会撞」
       当成了不用清理的理由。不撞是真的,不堆积是假的:357 个用户里 356 个是夹具。
       加 USERS_DB_PATH 覆盖口(口径同 graph-db 的 GRAPH_DB_PATH),测试改为
       先设 env 再清 require.cache 再 require(DB_PATH 是加载时求值的常量,
       顺序错了照样静默失效),afterAll 真删库文件。
    
    2) BENCHMARK_DB_PATH 有人设、没人读
       benchmark-db.test.js:13 与 integration/api-routes.test.js:43 都在设它,
       而 benchmark-db.js 那一行不读 → 共享 benchmark 库攒了 342 行
       skill_name=benchmark_test。补上读取后,两个测试文件一行不改就真隔离了
       (日志里 Initialized SQLite at /tmp/... 第一次是真的)。
    
    3) SKILLS_DIR 是写错的变量名
       config.js:13 读的是 SKILLS_CODE_DIR,所以 skillsCodeDir 一直指向那个
       2.7 万条的真实共享 skills 目录。两个测试文件的变量名改正。
    
    新增 guard server/tests/unit/test-isolation-env.test.js:
    测试赋值过的**路径类** env(_DIR/_PATH/_ROOT 结尾)必须有生产读者。
    只管路径类是刻意的——那是会造成静默数据污染的地方,而 HOME(os.homedir 间接读)、
    JWT_SECRET、PORT 这类天然不在范围内,不需要维护会腐烂的白名单。
    顺带钉住「共享 data 在仓库外」这个有意的分层,免得被“修”回仓库内。
    guard 第一版把自己文档注释里的举例当成真赋值、自己判红了,所以两侧都先剥注释——
    凡是静态扫源码的检查都要先想清楚该量的是代码还是文本。
    
    数据清理:共享 ~lemonAI/data/users.sqlite 删掉 358 行夹具
    (不是 356——修好之前又跑了两轮测试,每轮 +2,正好印证泄漏一直活跃),
    删前备份 + 计数闭合校验(保留行 + 匹配行 == 总数才动手),
    删后真人 bboonnee@gmail.com 完好,总数 1。
    
    验证:854 全绿(84 文件)。跑完全套后 users / benchmark_logs / skills
    三个共享库的 sha256 逐位不变——修好之前这不可能。
    3 处变异逐一精确变红(去掉任一 env 读取、把变量名改回错的)后逐字还原。
    
    未动:JWT_SECRET / API_KEYS 生产侧无读者或名字不符(config.js 读 OPEN_API_KEYS),
    不造成数据污染,另报;共享 benchmark 库那 342 行夹具未清,等确认。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 第六次真 CLI 冒烟 gapClosed=true:org 层契约落库 4 行(此前恒 0),
    且那段文字真的进了 workgraph-planning 收到的提示词(52677 字节,第 746 行起)。
    
    ## workflow 四路并行的产出
    
    - **org 层两半一起接**(关键顺序):先接读回方 buildPlanningPrompt 的 ③bis 段
      (org 边入边 → 上游绝对路径;出边 produces → 「你规划的子图必须有节点产出这些」),
      再接声明方 orggraph-planning 模板。只补模板的话就是又一次「声明了没人读」——
      org 图节点全是 subgraph,而 describeInputs 对 subgraph 直接返回空。
      resolveInboundLines 从 describeInputs 抽出共用,两侧口径物理上不可能漂移。
    - **清掉 404 死路径**:模板三处教的 POST /api/company/:id/work-graphs 从未注册。
      真跑复验:agent 侧零调用,改为按新 Step 7 写项目根绝对路径,还自己 require
      validateWorkGraph 预检了一遍才交回,round=1 一次过。
    - **四张骨架补 produces/needs**:从「靠模板文字施压」变成结构保证,每张过规则 6。
      真跑里 agent 照抄了 planner.json 并跟着任务删掉 sdd 节点——是改编不是复制。
    - **graph-seed 顺序依赖**:靠生产代码里已有的 GRAPH_SEED_ROOT 缝隙指向冻结快照。
      不是「跑几次都绿」结案:构造了原先必红的 churn,修前 12 次 3 红、修后 15 次 0 红。
    
    ## 我自己收的 4 条 major + 1 条 minor(都在 graph-engine.js)
    
    复验挖出「引擎自己拼的提示词在教 agent 做模板明令禁止的事」,同一条提示词里对立:
    1. 「已自行落库成功时回一行 workGraphId: wg_xxx」vs 模板「你永远拿不到真的 wg_ id,
       永远别写」。agent 没有任何受支持的落库途径,写出来必是编的,而 extractPlannedGraph
       优先匹配那个形状、查库查不到就整轮作废。删掉邀请,保留引擎解析真实 wg_ 的能力。
    2. 内联图示例的边一个 produces 都没有——agent 唯一见过的「边长什么样」是零声明的。
    3. 「端点尚未上线」暗示将来会上线,于是它还想去试一次。改成「不存在、也不会上线」。
    4. indexNodeOutputs 收集 declared 不按 role 过滤,把出边的 needs 当成本节点的产出。
       Chrome 走查实测:产品需求文档.md 落成 artifact,下游 prd 输入变 ⚠️。
       更坏的一面是错标(brd_notes.md 命中中继 needs[brd] → 归因给错的产出方)。
       1ea9efa 加的 role 列,这里正是它存在的理由。
    5. (minor) contract.prev.json(retryNode 的契约快照)被索引成 artifact 噪声。
    
    新增 guard:引擎提示词不许与模板对立(静态读源码,剔掉注释行——
    第一版把「解释为什么不许说某句话」的注释也扫进去了,量错了对象)。
    
    测试 850 全绿(83 文件),既有断言未改。5 处变异精确变红后逐字还原。
    Chrome 走查确认控制台正常分支(此前只验过降级分支):契约药丸、绝对路径、
    needs 中继说明、tab 上的 ⚠️ 计数全部渲染,零 console 报错。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 第五次真 CLI 冒烟(gapClosed=true)暴露的两条,都是「这轮成立是侥幸」型。
    
    1) indexNodeOutputs 靠文件名定 kind
       真跑成立纯属 brd 节点恰好把文件命名成 `brd.md`。换写 `业务需求文档.md`
       就落成 kind=artifact → 下游入边声明的 brd 查不到实例 → 提示词退化成
       「⚠️ 索引里查不到实例」,即刚关掉的断点换个形式重现。agent 给产物起中文名
       是完全正常的行为,不该由它决定索引对不对。
       改成两遍:先按文件名认领,再在**一对一无歧义**时(还差一个 kind 没人产、
       正好还剩一个认不出的文件)按声明认领。有歧义不猜 —— 猜错比留 artifact 更坏,
       错的 kind 让下游读到一份不是它要的文件,artifact 只是查不到、会如实报 ⚠️。
    
    2) describeInputs 任务级共享不去重
       真跑里 brd 与 prd 两个节点各写了一份 contract.json,都是 kind=contract,
       于是往后每个节点的提示词里「contract(任务级共享)」逐个累加。节点一多是噪声,
       且给 agent 两个同类路径等于把一个我们自己都没定的选择推给它。
       按 kind 保留最新那份,与沿边那一半的 byKind.set 同口径。
       ponytail 标了根因(节点私有目录的文件也会按文件名落成 task-shared 的 kind)
       与真正的修法(任务级产物只认 tasks/{taskId}/),后者要动 R-9.3 现有语义,
       归到那条线一起定。
    
    测试 829 全绿(+3),既有断言未改。4 处变异精确变红后逐字还原。
    含 verify agent 回填的 §4.6「第五次真 CLI 冒烟」小节。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • R-19.A 把 declareEdgeContract 的调用方补上之后,inboundContracts 终于非空,
    「沿边传递」这类数据流在真跑里生效了(真实规划 agent 自己在边上写出了
    produces/needs 声明)。本次收掉它留下的三个缺口。
    
    1) role 落库(第 14 次「四环漏一环」)
       normalizeContracts 产出 role,declareEdgeContract 入参没有它、表也没这列 ——
       产生 → 传递(丢) → 落库 → 读回。后果不止是控制台不精确:规则 6(data-contract)
       靠「有人 needs 但没人 produces」判错,只存 kind 就没法从库里重算,而
       patchGraph 第 5 步的重新校验读的正是库 —— 增量 PATCH 又恰恰是
       「此前满足的 needs 失去产出方」的唯一发生地,校验偏偏在最该生效的通道上失效。
       - graph_edge_contracts 加 role 列(CHECK + 老库 ALTER 后回填 produces:
         NULL 在规则 6 里既不算产出也不算需求,会误拒一张合法的图;闸门宁可漏判不可错拦)
       - 两层调用点透传 role;readGraph 把 contracts 读回来并递进 validateGraph
    
    2) 控制台三字段(前端在、后端没喂)
       GET /api/graph-runs/:id 补 edges[].contracts / companyRoot / taskFiles,
       否则 R19-C 那三块界面整块降级:声明和索引都在库里,界面上看不见。
       contracts 只在非空时出现,与 failReason 同一个约定。
    
    3) 收拢 companyRootDir/taskDirRel 镜像
       goal-contract.js 里那份镜像靠注释约定「与 graph-engine 逐字同构」维持,
       控制台路由成为第三个调用方时这个约定就不成立了。抽到 company-paths.js,
       GRAPH_RUNS_DIR 改为调用时读(原先两个模块各自在加载时快照,
       加载顺序不同会算出不同的根)。goal-contract 那条「镜像漂了就报警」的用例
       按其自身预告改成比函数返回值。
    
    测试 826 全绿(+11:role 8 / 路由 3),既有断言未改。
    11 处变异逐一验证精确变红后逐字还原;其中一条断言初版在 fixture 上永远自洽
    (图里没有无声明的边),补了一条无声明的边才真正有判别力。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 第四次真跑的断点:org-planner-prd 失败,它自己的归因原话是
    「不是上游没产出,是它的产出**结构上到不了我手上**」。核实属实 ——
    上游 brd.md / contract.json 落在**节点私有目录**,prd 的提示词只有它自己的私有目录
    + 只读 artifacts/,而 brd--success-->prd 这条边**不携带任何文件引用**。
    边一直只承载控制流(condition/edge_kind/fail_reason),R-4 要的「边的输入输出」
    被落成了从 output_ref **推断**的 UI 展示、没有实体,而推断的那一端下游读不到。
    
    ## 定案(27-REQ R-19)
    
    - 建 sqlite 表而不是 files.json:JSON 会重演 _registry.json 的并发重写与 git 冲突
      (本轮刚被它咬过,W13 只能把 usedCount 挪去另一个被 ignore 的文件)
    - **两类数据流都要**:纯边绑定表达不了 R-9.3 的「planner 生成、validator 执行」——
      org graph 是 planner→coder→validator,acceptance 跳过 coder,这两点之间没有边
    - workspace 根**代码推导不入库**:存绝对路径会烂,W13 清的那 10 条 legacy_path
      悬空行就是这个病。表里只存相对公司根的路径
    - 路径走 tasks/{taskId}/,不走 workspace/:后者语义是「Agent 自治领地、框架不干预」
      (06-沙盒自治),与「框架管着、可检索的交付物区」是两种所有权
    - 契约(kind)与实例(path)分规划时/运行时两阶段 → 引擎可在节点跑之前校验
      「你声明要吃 brd,但上游没人产 brd」,这类图结构错误此前只能等跑到那步才发现
    
    ## 实现
    
    work-graph-db:graph_files(实例)+ graph_edge_contracts(契约)两张表。
    graph_files 的当前版本走**部分唯一索引** WHERE superseded_by IS NULL ——
    与 idx_org_graph_nodes_company_role_active 同一模式:要「同键只有一个活的」
    又要留历史,条件得写进索引而不是靠删行。API:recordFile / findFiles /
    declareEdgeContract / edgeContracts / inboundContracts / rebuildFilesFromNodes。
    
    graph-engine 两侧都接:
    - 输入侧 describeInputs:按**入边声明的 kind** + **本 task 共享产物** 查实例,
      把绝对路径拼进提示词。声明了却查不到的**明说缺哪个 kind**,不静默省略。
      刻意不是「注入一句『索引在某文件,自己去检索』」—— 那仍靠 agent 自觉拼路径,
      真跑已证明它拼不出来;引擎直接递到手上,不给它猜的机会
    - 产出侧 indexNodeOutputs:节点 success 时登记落盘文件,kind 先看出边声明、
      再按文件名兜底;attempt-*.json 是引擎自读的归因文件,不当产物登记
    
    文档:27-REQ 新增 R-19(四条,含两段原话);27-P4 §4.6 按定案重写
    (原「公司根一个 files.json」作废)。
    
    测试 788/788 全绿。后续(下一轮):规划侧声明契约、goal-contract 路径归并、
    前端「输入输出」tab 展示解析结果、真 CLI 复验 prd 能拿到输入。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • LEMONBOSS_DISABLE_WATCHDOG=1 时不启动。
    
    第三次真跑记录:Master 智能巡检带 full 工具权限、会在工作树里改源码,还会自主
    下任务单;而图执行的边界守卫用整仓 git status 差集判归属,于是并行 Master 的写
    被算到「当时正在跑的 work 节点」头上,把一个只是超时的节点额外署成 boundary_escape。
    守卫做不到区分是谁写的(W3「拦不住」第 5 条),所以真跑期间工作树里不能有第二个
    agent 在活动 —— 这是规程问题,不该指望守卫变聪明。此前没有任何关闭开关。
    yibo authored
     
    Browse Dir »
  • 测试 788/788 全绿(76 文件,起点 786),未改任何既有断言。
    
    ## 1. idleTimeoutMs 硬编码 120s(「work 节点 → success」跑不通的唯一技术原因)
    
    agent-manager.js 里 `this.config?...?.idleTimeout || 120_000`,且 `...options`
    摊在前面被下一行覆盖,两个后果叠加:
    - 默认 120 秒,比 stream-executor 自己 JSDoc 写的 30 分钟(1_800_000)小一个数量级,
      也和同进程 ClaudeCliProtocol 早已放到 24h 的 idle 上限完全对不上
    - 调用方**无法覆写** —— 传了 options.idleTimeoutMs 也会被盖掉
    
    真 CLI 下的后果:任何「先读几千行源码再动笔」的节点都在动笔前被杀
    (readme-writer: idle timeout: no stream data for 120000ms)。
    
    改成 调用方 > agent 配置 > DEFAULT_IDLE_TIMEOUT_MS(30min),并把常量真的定义出来
    —— 上一轮 Master agent 自己诊断出这个问题也动手改了,但引用了未定义的
    DEFAULT_IDLE_TIMEOUT_MS,跑起来是 ReferenceError。
    
    ## 2. W7 判据在零边最小图上失效
    
    判「org graph 成型了没有」原先看**边数**,理由是「节点 hire() 自己就会造,
    拿节点数当信号会废掉『连雇两个人第二个不入图』」——理由成立,但边只是
    「writeGraph 跑过」的代理,而真跑里 orggraph-planning 裁出过一张**零条边的单节点图**,
    代理当场失效:三条判据全落空 → work 层本体被写进 org_graph_nodes,
    下一次下达任务时被 advanceOne 当成 org 节点执行。
    
    改成记录**来源**而不是猜:org_graph_nodes 加 authored_by(idempotent ALTER),
    writeGraph 写 'planner'、hire 写 'hire',判据看有没有 planner 写下的节点。
    hire 先记的行随后被 planner 纳入时升格,反向不降级。
    存量行 authored_by 为 NULL,回落到原来的边数代理,行为不变。
    
    新增两条用例(既有 9 例造的全是有边的成型图,正好绕开这个形状),
    并做过变异验证:把判据改回纯边数,两条精确变红,还原后逐字一致。
    
    ## 3. 前后端「能不能下达任务」的规则各写一份且不一致
    
    前端 ACTIVE_RUN 含 blocked、后端 ACTIVE_ROOT_RUN_STATUS 不含(唯一索引
    idx_graph_runs_one_active_root 里也没有)。后果:一家公司只要失败过一次,
    控制台上就再也下不了第二个任务,而后端其实放行。
    
    规则只留服务端一处:GET /graph-runs 返回 canDispatch / activeRootRunId,
    前端照抄结果,不再自己复述取值域。字段缺失时保守放行,交给端点 409 兜底。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 测试 786/786 全绿(76 文件,起点 760/72),未改任何既有断言。
    
    ## 本轮三件新东西,全部经真 CLI 验证
    
    W12 补完 · 失败原因四环打通
    - 引擎自判的失败(executor 抛异常、边界越界、子 run 无归因)现在也落
      graph_run_nodes.fail_reason,不再只有 agent 归因产出的才有值
    - 概览 tab 展示「[引擎判定] boundary_escape」+ 终端样式 excerpt,
      取不到 fail_reason 时回落 output_excerpt,并标明来源
    - 真跑实查:两行 fail_reason='boundary_escape' 都在库里,
      产生→传递→落库→读回四环在真 CLI 下走通
    
    W13 · 运行态文件根治(守卫误判的根因)
    - 17 个 todo.md / plan.md / scheduled_tasks.json git rm --cached + gitignore;
      源码(agent.md/config.json/soul.md/tools.md)一个未碰,模板本体仍跟踪
    - _registry.json 的 usedCount 挪进已忽略的 data/template-usage.json
    - 真跑验收:整轮(agent-creator、Master 各一次真 CLI、3 次 hire)结束时
      worktree git status 与基线逐字相同,_registry.json 零 diff
    
    单步进度感 · 真 CLI 一个节点 3-7 分钟
    - 画布卡片秒表 + 详情面板常驻条「运行中 2m40s · 最近事件 …(2m40s 前)」
    - 不做假进度条:引擎不知道 agent 还要多久
    - 顺手修 NodeDetailPanel 不跟 SSE 走(否则秒表也不会动)
    
    W9 失败上下文进提示词、W10 跨层地址两半、W11 Hot Swap 显式告警同批完成。
    
    ## 真跑结论:链路更长,但「work 节点 → success」仍未跑到
    
    走到:orggraph-planning 落图自雇 → agent-creator 补定义 → workgraph-planning
    真跑规划 + **先雇后提交**(W5/W6 再次成立)→ 子 run 起来 → work 节点 RUNNING
    → 7m19s 后 fail。两层原因叠加,都与 W1-W14 无关:
    
    1. **新缺陷**:agent-manager.js:324 把 idleTimeoutMs 硬编码 120s,
       readme-writer 通读源码静默超 2 分钟被掐死(动笔前就死了)。
       同进程 ClaudeCliProtocol 早把自己的 idle 上限放到 24h,
       stream-executor 的 JSDoc 默认值写的是 30 分钟 —— 这里把两者都压掉一个数量级;
       且 ...options 透传的 idleTimeoutMs 会被下一行覆盖,调用方无法覆写。
    2. **并行 agent 污染**:retrain-watchdog 每 5 分钟唤起的 Master 在同一工作树里
       改了两个源文件,被守卫 git status 差集算到该节点头上 → 额外署 boundary_escape。
       这是 W3「拦不住」第 5 条第一次真实发生。
    
    顺带记一笔:这一轮任务是 **LemonBoss 自己的 Master 自主下的单**,
    走的正是 POST /api/company/:id/tasks,它还自行诊断出了上面那个 idle timeout
    并动手改(引用了未定义的常量,是坏的)。
    
    ## 已知未修(下一轮)
    
    - idleTimeoutMs 硬编码 120s —— 「work 节点 → success」跑不通的唯一技术原因
    - W7 在**零边最小图**上失效:判据③要求存在 active org_graph_edges,
      零边图上三条判据全落空,work 层本体被写进 org_graph_nodes。
      服务层那 9 例测试造的都是有边的成型图,正好绕开这个形状
    - 前端 ACTIVE_RUN 含 blocked、后端 ACTIVE_ROOT_RUN_STATUS 不含:
      一家公司只要失败过一次,控制台上就再也下不了第二个任务(后端其实放行)
    - 真跑期间工作树里不能有第二个 agent 活动,否则守卫判罚不可信 —— 应写进真跑规程
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • ## 1. 子 run 转终态不回写父节点 → 任务永久卡死(界面走查发现)
    
    复现:org 节点的子 work run 第一次失败 → 父节点判 fail → 入口节点没有 fail 出边
    → blocked → 用户在中栏把子 run 修好跑到 completed → **父节点仍钉在 blocked,
    root run 也钉住,org 层永远推不下去**。唯一人工出路是对 org 节点点「重试」,
    而那会把整张子图重置到下一个 attempt,刚跑完的成果全丢。
    
    根因:propagateToParent 的幂等守卫写的是 `parentNode.status !== 'running' → return`,
    判据用错了维度——它想防「同一个结果被结算两次」,实际拦掉的是「子 run 换了个结果」。
    subgraph 节点的状态本来就是从子 run 派生的,子 run 换了终态就该重新派生。
    
    修法:新增 reviveParentFromChild,只认「转好」(fail/blocked → success),
    且要求 parentNode.child_run_id 正是这个子 run。恢复走 **blocked → ready → running
    → success 三步合法转换**,而不是给全局放宽 NODE_TRANSITIONS——转换表是给所有调用方的
    安全网,不该为一条恢复路径把 blocked → success 对外开放;三条 node.status 事件
    也让「这个节点是被救回来的」在 timeline 上看得见。
    
    补测试时才暴露的第二半:父 run 多半也钉在 blocked,而 RUN_TRANSITIONS 没有
    blocked → completed,settleRun 在转换非法时是**静默跳过**的。所以复活时要先把
    父 run 从 blocked 拉回 running(这条合法),否则节点复活了 run 还是卡着。
    
    ## 2. 边界守卫把并行 agent 的写算到当前节点头上(真 CLI 冒烟的断点)
    
    真跑实测:招聘模板库里没有的角色走 hired_from_base,自动流水线起 agent-creator,
    而它的 CLI workDir 就是 system/agents/agent-creator/——一个被 git 跟踪的目录。
    它在里面重写自己的 todo.md、删一条 scheduled_tasks,被整仓 git diff 扫到,
    归属算到当时正在跑的 work 节点,一个**真正成功**的节点被判 fail。
    只要这些运行态文件还被 git 跟踪,每次含 hired_from_base 的招聘都稳定复现。
    
    修法:splitUnattributable 把 agent 运行态文件(todo.md / plan.md / memory.md /
    _lessons.md / .claude/scheduled_tasks.json)从越界证据里摘出去,单独记一条
    unattributable,不参与判罚。摘除范围刻意窄——同目录下的 agent.md / config.json
    等源码仍然算越界。
    
    路径提取按「首个空白之前」切而不是固定偏移:diffRepoStatus 已去掉前导空格,
    porcelain 状态位长度不定(修改类 "M path" 1 字符、未跟踪类 "?? path" 2 字符),
    固定 slice(3) 恰好对**修改类**失效——而 agent-creator 改自己 todo.md 正是修改类。
    这条单独立了一个回归用例。
    
    ponytail: 这是缓解不是根治。根治要把运行态文件从 git 移出去(27-P3B W13),
    本次只保证守卫不再冤枉节点;同一次招聘里 agent 若写了运行态文件之外的源码,
    仍会算到当前节点头上。
    
    测试 760/760 全绿(72 文件,起点 753/71):新增 graph-engine-revive-parent.test.js
    4 条 + graph-boundary.test.js 3 条。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 真 CLI 冒烟(worktree 隔离,PORT 3010)九轮来首次把 work 节点跑起来:
    orggraph-planning 真跑 3 分钟自主裁成单 org node 零边图 → workgraph-planning
    第 1 轮产出合法图 → **自己按 Step 6 把 brd-planning / planner-validator 雇进公司**
    (W5/W6 核心验收成立,且 W7 判据生效、两者都没写进 org_graph_nodes)→
    第一个 work 节点 brd-planning 真起 CLI 子进程,产出 brd.md / contract.json /
    attempt-0.json 全部落在 run 目录内。图内预算仅 2 次调用 / 13998 tokens(上限 15)。
    
    W5/W6 · workgraph-planning 自雇:撤掉「你不招人也不辞人」,Step 6 加招聘环节,
      写死「先雇、再提交图」的顺序,给出失败分级与幂等要求
    W7 · work 层招聘不写 org_graph_nodes:按 type 与 work graph 骨架点名两条判据区分层级,
      P2 「hire org 角色 → org graph 出现该节点」的语义保持不变
    W8 · 控制台「规划组织结构」按钮(抽成 ReplanOrgGraphButton.vue,带过渡态与秒表),
      接 POST /:companyId/org-graph/replan(该端点此前无任何前端调用方);
      新增 fixtures/devloop-mock/orggraph-planning.json
    W12 · 节点失败留痕:node.output 带 output_excerpt、新增 node.error 事件。
      真跑当场救场——守卫误判那条归因正是靠它才查得出来,
      换成之前看到的只有「节点 fail、无原因、无痕迹」
    
    测试 753/753 全绿(71 文件,起点 736/68)。
    
    已知未修(下一轮):子 run 转终态不回写父 node_run 导致任务永久卡死;
    applyBoundaryGuard 用整仓 git diff 判归属会把并行 agent 的写算到当前节点头上;
    W12 的 fail_reason 未落库(概览 tab 仍为空);POST /:cid/tasks 无前端调用方。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »

  • 测试 736/736 全绿(68 文件,起点 719/66,净增 17 条,未改任何既有断言)。
    
    W1 · seed 接进启动流程 + 悬空行回收
    - server/index.js 与 agent-server.js 均在 listen() 前调 runStartupSeedSafely()
      (幂等、失败降级记日志、不挡服务启动)
    - seedTemplates() 此前**没有任何运行时调用方**,只有测试与手动脚本引用,
      于是 system/templates/ 新增本体永远进不了 system_agents,而
      work_graph_nodes.system_agent_id 是指向该表的外键
    - 占位 id 回收:legacy_path 已失效的 company_hired 行改为可被 template 就地转正,
      保留行 id 以保外键。此前 seedTemplates 遇占用即 skip,导致污染不可逆:
      lookupSharedAgentMd 恒 null → hire 静默退回全量拷贝 → 模板改动对已雇公司不生效
    - 生产库实查:company_hired 0 行(原 10 条悬空全清)、template 45、
      verifyCounts().ok=true、foreign_key_check 空
    
    W2 · 项目根分配(四环闭合)
    - ensureProjectRoot 复用 path-resolver.allocateProjectRoot(sandbox 模式)
    - 分配 → 进 agent_call 与 planning 两处提示词 + callAgent metadata
      → 落 artifacts/project-root.json(wx 独占)→ readProjectRootRecord 读回
    - 子 run 经 createRun 继承 root_run_id,跨层共享同一项目根
    - 核实 data/companies/ 在 .gitignore 内,故项目根内的合法写入不会被 W3 守卫误报
    
    W3 · 产出边界守卫(部分,文档如实标注)
    - 真 CLI 是子进程直接写盘,引擎拦不住,守卫只能拒收结果、不能回滚
    - 7 条「拦不住」的情形逐条列明;文档自述「在 cwd 隔离做掉之前,
      真跑仍应在可丢弃的工作副本里做」
    
    W4 · 修验收 fail-open(性质严重)
    - benchmark-validator / testdata-validator 的 Step 表教 agent 写 verdict:"fail",
      而引擎 FAILING_VERDICTS={attributed,inconclusive} 不含它 → 契约非法的产物
      被判 success 放行。失效方向是放行不是拦截
    - org-validator 的审计字段 verdict → audit_verdict:它与 work 层判词写同一路径
      (nodes/<nodeRunId>/attempt-<n>.json),此前只靠「该节点恰好是 subgraph」
      才没被 readNodeProduct 当判词读,属隐患藏在巧合里
    - 新增防线 template-verdicts.test.js,从 graph-engine.js 源码取
      FAILING_VERDICTS / ATTRIBUTION_REASONS 扫全部模板;经三次变异验证有效
      (改回 "fail" / 改回 verdict / 造 "ok" 均变红并报精确行号)
    
    真 CLI 冒烟未执行,两条理由(终验 agent 自行判断,符合任务给的门槛):
    ① W3 按其自身文档明确「不是沙箱」且要求真跑在可丢弃副本里做,而当前是主仓库、
       持有未提交交付物、且有并行 agent 在写,W3 自述的误判项必然触发;
    ② 断点已零成本静态证明:getAgentConfig 解析裸名只查 system/agents/、
       从不查 system/templates/,resolveAgentName 不做按需 hire,实测 prd-planning /
       sdd-planning / benchmark-validator / testdata-validator / validator-triager /
       org-validator 全部 throw「Agent not found」。花 $3-8 复现已知断点无意义。
    故「做完这四件就能真跑」不成立,真跑挪到 W5/W6 之后。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 界面走查(真 server + HTTP + 真 AgentManager,底层 mock 协议)已把
    manual 模式全流程点通:org 节点起子图 → 下钻 work graph → 单步 →
    第 3 步触发回退(徽标 0/3→1/3、run 级 1/5 双层计数同时可见)→
    归因记录与 remediation_note 完整送达 → 收敛后父节点结算、左栏前移。
    
    mock 侧补两处缺失接线(都是「四环漏一环」,非新功能)
    - ClaudeCliProtocol._sendMockPrompt 按 agent 类型查 fixture:图执行侧的
      agent 名带公司前缀(resolveAgentName 返回 {cid}:{id}),fixture 不该
      按公司复制一份。DevLoop 侧名字不含冒号,行为不变
    - graph-engine.defaultAgentExecutor 接住 mock/files 事件:此前只有
      DevLoop 的 loop-controller 接,图执行侧没接,于是 mock 下
      readNodeProduct 永远找不到 attempt-{n}.json,validator 系节点恒 blocked。
      写入限制在节点产物目录内,越界丢弃并留痕
    
    新增 5 份 P3 fixture(workgraph-planning / prd-planning / sdd-planning /
    coder-validator / validator-triager),coder-validator 用 sequence 演示
    一次回退。图必须放 chunks 不能只放 result——callAgent 的 output 来自
    流式 chunks,session/result 的文本不进 output。
    
    控制台入口:CompanyDashboard 加「🕸️ 执行控制台」按钮(路由早已注册,
    此前无任何入口,只能手敲 URL)。
    
    scripts/p3-demo-setup.js:手工测试准备脚本(雇人+写图+起 run),头部
    写明它是两个已知缺口的绕行,缺口修好后第 1 步即可删。
    
    _registry.json 补齐 6 条本体登记(brd/prd/sdd-planning、goal-benchmark、
    benchmark/testdata-validator)——同轮新增的 template-refs 防线测试正是
    因为缺这 6 条而变红,防线抓到了「交付与报告不符」。
    
    文档
    - 27-REQ R-18:招聘归 workgraph-planning(方案 a)、停用 mock 改真 CLI
    - 27-P3B 真实运行前置(新增):11 个工作项分三批,含现状核实 10 条证据
      (seedTemplates 无运行时调用方、10 条悬空 company_hired 行、
       FAILING_VERDICTS 不含 "fail" 导致验收假放行等)
    
    测试 719/719 全绿(66 文件)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »
  • 命名轴是「节点在哪一层图里」,把散落的老 agent 归到它实际所处的层级:
    
      innovator → org-innovator      type='cxo',是 org graph 层的节点
      hunter    → innovator-hunt     innovator work graph 第 1 阶段
      digester  → innovator-digest   第 2 阶段(输入是 hunt 产出)
      evolver   → innovator-evolver  第 3 阶段
    
    依据:retrain-pipeline.js 的 stepConfigs 就是按 hunter → digester → evolver 顺序
    跑的,行 231 明写 steps: ['re_hunt','re_digest','evolve'] —— 这三个本来就是
    innovator 的 work graph 三阶段,只是名字没体现出来。
    
    ■ 迁移(scripts/migrate-agent-naming.js,--dry-run + 幂等可重跑)
    - 8 个目录(system/agents/ + system/templates/),用 git mv 保留历史
    - system_agents 表 4 行的 id 与 legacy_path
    - 公司实例目录与 company.json.agents[](本次无公司雇过这四个,实际 0 条)
    
    ■ 代码引用点(5 处,手工改)
    - retrain-pipeline.js: stepConfigs 的 agent 字段 ×3、agentToStep 映射的 key ×3、
      call_sub_agent 的 evolver 判断
    - agent-chat.js: agent 列表过滤里的 innovator
    - company.js: 模板训练触发的 agentId
    
    ■ 刻意不动的三类同形异义(改了会坏)
    1. skill 内部子目录名 {skillDir}/hunter/knowhow.md —— 那是 hunt/digest/evolve
       三阶段的产物目录约定,与 agent 名同形但不同义。涉及 open-api.js:402、
       retrain-pipeline.js:148/374、evolution-loop.js:178、sync-skills.js 的 INTERNAL_DIRS
    2. skill 命名空间白名单 —— skill-dir-manager.js 的 CORE_SKILLS、
       migrate-trained-skills.js 的 SYSTEM_SKILLS,那里的 'innovator' 指同名 skill
    3. conversations/ 与 system/tasklog/ 下的历史记录 —— 运行态数据,不改写历史
    
    evolver-youtube-crawler 按 §3.4 列为待定,迁移脚本按精确目录名匹配,未误伤。
    
    这四个重命名顺带解决了 27 号文档 §七 挂了很久的 6 个命名冲突 id —— 重命名后
    system/agents/ 与 system/templates/ 下不再有同名目录(只剩 agent-creator 与
    evolver-youtube-crawler 两个,前者是基础设施角色按规则不改名)。
    
    验证:npm run test:unit 693 全绿(64 files);test-company-e2e 114/114;
    迁移脚本二次执行 dirs=0 dbRows=0 skipped=8(幂等)。
    
    未触碰另一进程正在进行的 P3 文件(graph-engine.js / graph-run-sse.js /
    work-graph-db.js / 27-p3-p4-work-graph-console.md / 27-requirement.md)。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse Dir »