-
设计见 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> -
用户授权清理 system/templates/ 的过期引用。全仓库核了 34 处, 结论与预期相反:没有该删的死代码,但有两处正在静默失效。 真正坏掉的(demo-harness-company.js): path.join(ROOT, 'system/templates/base/agent.md') → system/agents/_base/ path.join(ROOT, 'system/templates/base/config.json') → system/agents/_base/ 两处都包在 fs.existsSync() 里,所以目录消失后**不报错**,只是 静默跳过 base 本体的挂载 —— demo 跑出来的 agent 没有四段式骨架, 但脚本照常打印成功。又一个静默降级。 顺带:real-pm-smoke.js 的提示文案、migrate-agent-naming.js 里 已失效的遍历项、docs/README.md 的 qa-judge 落点。 CLAUDE.md 目录速查表原来只有 `system/templates/devloop-*/` 一行, 连 system/agents/ 都没列。换成两行并写清三个容易踩的点:目录才是 数据源头(seedAgents 直接扫目录)、_registry.json 只补 benchmark_score、_ 前缀是保留 id 会被跳过。 **刻意不动的**:server/tests/ 下 30 处引用。它们分两类,都得留 —— ① 记录迁移过程的注释;② 断言目录已不存在的守卫 (`expect(git(['ls-files','--','system/templates'])).toBe('')`、 `expect(fs.existsSync(...)).toBe(false)`)。删掉后者等于删掉 「迁移已完成」的证明。legacy_path 那几个测试夹具同理,是历史数据形态。 验证:三个脚本 node --check 通过;改后的四个路径实测存在。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
并行的 agent 重构那条线同步来的,已核实三个函数都在: - agent-locator.js missingBodyDefinition() 判什么算没填完 - graph-service.js promoteDraftIfComplete() 转正动作 - graph-service.js isUngraduatedStatus() hire() 挡人的判据 关键是最后一个只挡 draft / training 两个状态,不是「≠ active 就挡」。 库里有 36 个本体的 status 是 enabled/idle/ready/trained,按「不等于 active」判会被全部误判成没填完 —— 这个坑已经被踩过一次。 取值域的唯一持有者是 graph-service,不要在别处复述 —— 与本文档 §六.3 记的 canDispatch 教训(前端复述了一份取值域还多写一个 blocked,导致公司失败一次后再也下不了任务)是同一个病。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
另一条工作线正在做 agent 目录结构重构(system/templates 并入 system/agents),约 900 个变更尚未提交。逐条核实交集: 好消息 —— 本文档引用的全部函数/常量(settleRun / markRunReady / stopRun / defaultPlanWorkGraph / PLAN_MAX_ROUNDS / writeTaskGoalArtifact / reportRunOutcome / ACTIVE_ROOT_RUN_STATUS / saveTaskMeta / getAggregatedTasks)在 650 行改动后全部存活,五条 行为性结论逐条复验仍成立,work-graph-db.js 完全没被碰(第二轮 复核最关键的那批发现不受影响)。27-P3/P4 那条「只写函数名不写 行号」的体例在这里直接兑现了价值。 坏消息 —— §四.9 有一条写反了,已纠正并留档: - system/templates/ 彻底取消,本体落 system/agents/task-planning/ - _registry.json 不再是数据源头(seedAgents 直接 readdirSync 扫 目录当真相,registry 只按 id 补 benchmark_score),所以「不登记 会把 CI 跑红」不成立 —— 建目录才是关键动作,登记是可选的 - 真正的闸门是新增的「毕业」判据:agent.md + soul.md 非空且不带 _base 骨架哨兵,否则 status 停在 draft、hire() 返回 body_not_ready。「先建骨架后填」的中间态雇不进公司 - 本体正文路径三条硬规矩(cwd 深度 3):自身文件 ../../、仓库根 ../../../../../、交付物必须用 $W 绝对路径。写错全都不报错、 只静默降级 —— 静默故障家族的最新成员 排期影响:批 1 照常(work-graph-db.js 无人触碰);批 2 的 task-planning 本体等重构落地后再建,否则要按旧形态做一遍再改; 批 5 开工前重新核 agent-chat.js(+455 行)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
companyService.get 已经在读这个文件,零新增读路径;建表要多一张表、 一套读写、一次迁移,而目前公司级配置项只有这一个。涨到 3 个以上时 再考虑建表。 这是 P3C 最后一个开放问题,§十一 至此全部关闭。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
R-20.6: 1. 退役 /api/goals 与 /api/company/:cid/goals 两个 OKR 别名,确认执行 2. goal 级验收仍用 org-validator,在 goal 层再调一次同一个本体。 不复用 task 级结论 —— 两次验的是基数不同的东西:task 级问 「这一个 task 的 task-goal 达成没有」,goal 级问「N 个 task 合起来人要的东西做出来没有」。单 task 的 goal 跳过这次调用。 goal 状态机加 failed,判词落 goals.verdict 3. 预算先不考虑,不做 goal 级闸门;但 planning_tokens / validation_tokens 两个记账字段保留 —— 它们不是闸门,是让 goal 层那两次 agent 调用的花费可见 4. queued 开放改 depends_on。当初担心的「改完位次怎么算」不成立: queuePosition 只按 priority→seq→created_at 排,depends_on 决定 的是够不够格出队,两者正交 5. 两套任务体系统一到 tasks 表,新增 §十二(批 5) 定案 5 核实出一个反直觉的前提:两套不是并列的,新体系的回调链路 长在旧体系身上 —— reportRunOutcome 收敛后调的正是 /api/agent/task/report。所以「删掉另一套」= 删存储与会话反推 (system/tasklog + TaskService.getAggregatedTasks),不能删入口 与回调:/task/dispatch 的 URL 与请求体写在 tools-base.md 里, 改它等于同时改所有 agent 的提示词。 tasks 表为此吸收三个字段(executor / assignee_agent_id / parent_task_id):agent 派单与 graph 执行的载体确实不同, 硬说成一回事是骗自己;调度器只管 executor='graph'。 顺带修好:派单去重从扫目录变一条 SQL,两套 T- id 合一。 开放问题 2/4/6/7 全部关闭,只剩 5(max_parallel 放哪)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
上游失败时下游自动转 blocked 已定案,开放问题里还留着同一个问题 的疑问句 —— 这正是「代码改了、文档停在上一轮」那类漂移。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
R-20.5 四条: 1. task-planning agent 提进 v1(原计划 v2 人手拆)。产出一律 draft 清单,人确认编辑后才下达 —— 那一步是唯一一道成本闸门, 拆出 5 条 task 就是 5 份 DEFAULT_TASK_BUDGET 2. 并行仍留批 4,v1 只排队(max_parallel_tasks=1,唯一索引不动) 3. manual 任务不参与自动出队:startRun(manual) 只点亮入口就停下等 /next,而 run 已经是 running 占着坑,自动拉起会堵死队头 4. 上游失败 → 下游自动推导为 blocked 并标红,给「重跑上游」和 「强制放行」两个出路。blocked 每次 tick 重算而不落死, 上游重跑成功后下游自己回 queued 定案 1 牵出一处新缺陷:task-planning 跑在 goal 层、任何 run 存在 之前,它烧的 token 没有预算池可扣 —— 正是 defaultPlanWorkGraph 注释里点名的「隐形的一半花费」在 goal 层的新实例。上一轮刚以 「零消费方」为由砍掉的 goal 级预算字段,以记账字段 planning_tokens 的形式回来(闸门仍不做)。 链路照抄 defaultPlanWorkGraph 不另造:调 agent → 校验 → errors 原样回喂,最多 3 轮;3 轮不过降级成 1 条同描述的 task,绝不能把 goal 卡在 planning。新本体必须与 _registry.json 登记同 commit, 否则 template-refs.test.js 当场把 CI 跑红(§八.21 有前科)。 验收 19-24 新增:拆分正常 / 失败降级 / 记账 / 不该花钱的路径不花钱 / 上游失败下游标红且可恢复。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
逐条对着代码核初稿,最要命的一条是基础事实搞错了: - createRun 在建行的事务里就按 readDefGraph 把 graph_run_nodes 全插好了。 初稿三处基于「queued run 没有节点行、进去就是空指针」的论证全部作废。 真实情况是 queued run 与 ready run 在数据上只差 run_status 一个字段, 所以「靠没有节点行自然报错」那道想象中的防线根本不存在, 唯一的闸门是 advanceOne 里 run_status !== 'running' 那一句,必须加断言钉死。 由此派生出一个既存、但被排队从毫秒窗口放大成必然事件的缺陷: 节点行在 createRun 时快照,而 getRunState 每次读都取当前定义。 排队期间点「重新规划组织」→ 新节点没有 run 行 → 出队即卡死。 补 syncRunNodesToDef,出队前对齐。 其余六处: - 出队原语写错:markRunReady 把 from 写死成 planning,复用不了; 正解是 updateRunStatus(runId,'queued','ready') 这一条条件更新 - 唯一索引冲突是 throw 不是 changes=0,初稿的错误处理只覆盖一半 - 触发点枚举本身是漏的:stopRun 与预算耗尽→blocked 两条最常见的 释放路径都不走 settleRun。改为挂在唯一漏斗 transitionRun 上, 一个钩子覆盖全部路径(枚举法在这里靠不住,而漏的症状是静默的) - report_to 只活在请求闭包里,排队后回调链路静默断掉 → 必须落库 - okr_node_id 逐 task 下发会反复覆写同一个 OKR 节点, done 会被后来的 failed 翻掉 → task 的 run 一律不带 okr_node_id - manual 任务占着 running 等人点「下一步」,会永久堵住队头 → 不参与自动出队 补漏:存量回填脚本(不做的话批 1 反而让历史任务更看不见)、 任务级 rerun 与节点级 retry 的命名撞车、queuePosition 排序口径 必须与调度器同源、砍掉没有消费方的 goals.budget_json。 错误结论留档不删(§三 ⚠️ 块 / §五.1 / §九.1 开头)—— 它们是最容易再犯一次的那类。验收从 12 条涨到 19 条。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
控制台走查暴露的「一个 company 只能有一个任务」,根因不是并发约束, 是「人类提的需求」和「机器执行的单元」被压在同一条 root graph_run 上。 三个实地核实的前提,让这次不是发明新概念: - 27-NAMING §四 早就定义了 goal 1 : task N 的基数 - graph_runs.goal_id / task_id 两列早就在,company.js 注释也写着 「三者基数不同不可混用」—— 但全仓库没有任何写入方,goal_id 是悬空外键 - G- 前缀已被 OKR 正名释放;/api/goals 与 /api/company/:cid/goals 这两个 OKR 向后兼容别名全仓库零调用方,可以退役后把路径要回来 设计要点: - goals / tasks 两张新表 + graph_runs 新增 run_status='queued' - queued 的唯一理由是把出队的并发临界区交给既有唯一索引兜底: idx_graph_runs_one_active_root 覆盖 ready/running/paused 而不覆盖 queued, 超发会被索引直接拒绝,不需要应用层的「先查后插」 - 排队推翻了 P3「拒绝而不排队」的定案,但那条定案的理由(没有出队 worker = 静默丢任务)今天仍成立,所以调度器的四个触发点是功能主体而非收尾 - 已下达的描述冻结:入队即写 task-goal.json 并被 describeTask 消费, 改库不改 artifact 会让两份对不上,两份都改等于篡改审计输入 - 现有 T-${Date.now().toString(36)} 在「一个 goal 批量建 N 个 task」下 同毫秒必撞,且 tasks.id 是主键 —— 改用仓库既有约定的 4hex 后缀 R-20 四条定案回记至 27-requirement.md。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
控制台原先只能看引擎的事件轨(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>
-
控制台「运行日志」看不到 LLM 逐字输出,不是坏了,是引擎按设计不存: 1. **引擎根本不消费流式 chunk**:defaultAgentExecutor 的 onAcpEvent 只处理 session/result(累 usage)与 mock/files,文本 chunk 一个字都不接。 2. **事件里的自由文本截断到 600 字**(EVENT_TEXT_MAX),graph-engine.js 那段注释 写了三条理由:完整输出无上限而 getRunState 每次轮询 SELECT * 全量回传; 完整产物已有地址(output_ref)不该有第二个真相源;fail_reason 是选边的匹配键 不能掺自由文本。 所以 stream.log / timeline.ndjson 里只有结构化事件 + 短摘要。 但 Claude CLI 自己把完整会话写在 ~/.claude/projects/<cwd 路径的 / 换成 ->/<sessionId>.jsonl 而 agent 的 cwd 就是 data/companies/<公司>/agents/<角色>,目录名可推。 这个脚本把那份 jsonl 渲染成可读的实时流: node scripts/tail-agent.js <公司> 列出哪些角色有 transcript node scripts/tail-agent.js <公司> <角色> 跟看(增量轮询,Ctrl+C 退出) node scripts/tail-agent.js <公司> <角色> --last 40 只看最后 40 条 只渲染 assistant 的 text 当「LLM 输出」:user 侧的 text 是喂进去的东西(提示词全文、 每轮注入的 <env_status>),跟看时纯噪音 —— 第一版没滤,实测满屏 env_status。 user 侧只留 tool_result(agent 到底读到了什么,这个有用)。 在运行中的实例上验过:mycompany/sdd-planning 正在跑时列表、--last、跟看三种都对, 角色名写错时明确报「目录不存在 + 先跑列表看有哪些」,不静默空跑。 ponytail: projects 目录名按 CLI 现行的「/ → -」规则推导,规则变了会找不到目录 (会明说,不会假装没输出)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
用错了变量:写的是 `background: var(--bg-surface, #fff)`,而 `--bg-surface` 不是「不透明表面」,它是 2% 的叠加色调(App.vue 里深色 rgba(255,255,255,0.02)、 浅色 rgba(0,0,0,0.02))。于是弹框几乎全透,把身后 42% 的遮罩透了出来; 文字色 var(--text-primary) 又跟着主题走成近白色,叠起来就是一片看不清。 以为兜底的 #fff 永远轮不到 —— 变量确实存在,只是语义不是我以为的那个。 该看的是周围的约定:**整个控制台是硬编码浅色的** (GraphConsole.vue:363 `.console { background:#fff; color:#1a1a1a }`, NodeDetailPanel.vue 里 CSS 变量用了 0 次)。我的弹框是唯一读主题变量的组件, 所以只有它坏。改成与邻居一致的硬编码浅色,遮罩阴影 .18 → .28 让边界更清楚。 在运行中的实例上用真实产物核过(mycompany 的 brd.md):白底、标题/表格/列表 渲染正常。871 全绿(纯样式改动,无逻辑变更)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
## 为什么加 「输入输出」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> -
## 起因 integration 测试里 `process.env.API_KEYS = 'test-api-key-123'` 是死代码: 生产读的是 OPEN_API_KEYS(config.js:31),而这个测试文件里 API_KEYS 只出现 这一次赋值,没有任何 /api/open/* 请求、没有任何 key 相关断言。已删。 (更正上一轮的判断:它不是「为了错误的原因而绿」——它什么都没测, 所以也谈不上错。两个缺陷互相抵消成了无害。) ## 为什么错名字能活这么久 api-key-auth.test.js 原有 7 条用例**全在测 extractApiKey**(怎么从 header/query 取出字符串),而做鉴权决定的 requireApiKey 一条都没有。OPEN_API_KEYS 那条分支 从来没被执行过 —— 没有测试执行过的路径,喂给它什么名字都不会有人知道。 补 4 条(拦/放两侧各两条):无 key → 401「需要 key」/ key 无效 → 401「key 不对」/ 命中系统 key 列表 → 放行且挂 req.apiKey / 命中用户 api_key → 放行且挂 req.user。 ## 补完当场抓到一个真缺陷(这一条才是重点) requireApiKey 把 findByApiKey 返回的**原始数据库行**挂上 req.user,带着 password_hash。JWT 那条路(jwt-auth.extractUser)一直是过 toPublicUser 才挂的 —— 两条鉴权路径行为不一致,而 req.user 是 handler 唯一的身份来源。 当前没有实际泄漏(open-api 的 handler 只读 req.user?.id / ?.email, 没有哪个把整个对象回给调用方),但将来任何一个 res.json({ user: req.user }) 都会把 bcrypt 哈希发给外部 API 调用方。改成过 toPublicUser。 顺带确认 requireApiKey 本身是 fail-closed 的:无 key 401;key 既不匹配用户 api_key 也不在配置列表 401。OPEN_API_KEYS 为空只是让系统级 key 全部失效, 不放开任何东西 —— 所以那个错名字不构成安全漏洞。 ## 验证 862 全绿(85 文件)。4 处变异逐一精确变红后逐字还原。 其中 T3 值得记:第一版「无 key → 401」只断言状态码,而把 if (!key) 改成 if (false) 之后测试仍全绿 —— 因为纵深防御,key=null 会落到第二道门照样 401。 两者的区别在**文案**(「需要 key」vs「key 不对」),而我在另一条用例的注释里 自己写了「两种失败要分得开」却没断言它。补上文案断言后 T3 才真的变红。 只断言状态码的鉴权测试基本都有这个盲区。 数据核查:跑完全套后共享 users.sqlite 的 sha256 不变、用户数仍为 1 (新用例用 USERS_DB_PATH 打到 /tmp —— 这个覆盖口是上一轮刚补的, 在它存在之前这组用例会往共享生产库里插测试用户)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
## 问题 server/middleware/jwt-auth.js 的兜底密钥 'lemon-dev-secret-change-in-production' 提交在仓库里,而 .env.example **没有 JWT_SECRET 这一项** —— npm run setup 就是 从 .env.example 生成 .env(CLAUDE.md「快速启动」),所以照文档装出来的部署 必然用这个公开密钥签会话,且启动时零告警。 危害不止「密钥弱」。extractUser 拿 decoded.userId 回库重查用户 (所以伪造 role:'admin' 这条路本身是堵住的,这个设计是对的),但 user-db 的 id 是自增小整数、不是 uuid:拿已知密钥签 {userId: N}、从 1 试起 就能命中真实账号,拿到的是该账号在库里的真实 role。命中 admin 就是 requireAdmin 守着的整个 /api/users/* 全部敞开。不需要密码、不需要 OAuth, 只要读过这个仓库。 本机 .env 有 64 字符密钥,未受影响;受影响的是默认部署路径。 ## 三处改动 1. .env.example 补 JWT_SECRET(含 openssl rand -hex 32 生成命令与危害说明) 2. jwt-auth.js:NODE_ENV=production 且未设 JWT_SECRET 时**拒绝启动** (签名密钥缺失不是可以边跑边补的事);开发环境保留兜底但每次起进程吼一次。 三种情形都实测过:生产无密钥→抛错拒启;生产有密钥→正常且不告警;开发无密钥→放行+告警。 3. 新增 guard server/tests/unit/env-example-secrets.test.js: 「安全类 env + **非空**硬编码兜底」必须在 .env.example 里有一行。 只管非空兜底是刻意的 —— 空串兜底(FEISHU_ENCRYPT_KEY、OPEN_API_KEYS)是 「未配置」的正当写法,缺了 fail closed;把它们也算进来只会制造噪音, 然后被加白名单,然后白名单腐烂。危险的是「缺了也能跑、还跑得像配好了」那一类。 ## 顺带修掉一个能把全新克隆直接搞坏的 .env.example 写 PORT=3000,而 config.js 默认 3010、web/vite.config.js 的代理 目标硬编码 3010 → npm run setup 装出来的环境里前端每个 API 调用都 502, 而症状离原因很远。改成 3010,并加一条 guard 钉住「example / config 默认 / vite 代理」三者一致。 ## 验证 857 全绿(85 文件),既有断言未改。3 处变异精确变红后逐字还原。 其中 S3 那条变异(把兜底常量改成内联字面量)是必要的:guard 第一版只认内联 字面量,而我重构时把兜底提成了命名常量 DEV_FALLBACK_SECRET,于是 guard 对它 诞生的那个 case 恰好失明 —— 被它自己的自检当场抓到,遂加上同文件常量绑定解析。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
起因是一个看起来无害的问题:<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>
-
第六次真 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>
-
第五次真 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> -
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> -
第四次真跑的断点: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> -
## W11 最后一块:agent 侧的假承诺 方案 (b) 此前只把 agent-chat.js 的空转喊出来了 —— 那是喊给**运维**听的。 而 agent 读的是 system/templates/base/agent.md,那一侧还写着: - 开场「你当前默认只被挂载了 meta-research 这个元技能」—— 那个包不存在,没挂载过 - 阶段三「*系统给你换上 meta-execute 技能时,你才会拥有修改核心文件的破坏性权限*」 —— **纯假承诺**:权限自始至终由 config.json 的 cli.toolPolicy 决定,与 phase 无关, 而 base/config.json 从 RESEARCH 第一秒就给了 bash 与 str_replace **一个以为自己被拦着的 agent,比知道没人拦它的 agent 更危险。** 改成如实表述:开场加一段说明「技能包不存在、换装不会发生、权限全程不变、 四段式真正给的是上下文隔离而不是权限隔离」;三处「换上 X 技能」改成 「记忆已清空,你手上只有 <artifact>」;阶段三那句改成「写入权限从第一阶段起 就没变过 —— 前三步不落盘是纪律要求,不是系统给你的限制」。 至此 (b) 才算闭合:运维侧有告警、agent 侧不再被误导、文档侧记清了根因。 ## 自动模式真跑:跑出一个结构性缺口 关看门狗后自动模式接管(pause → start{mode:auto},运行中的 run 要先暂停才能切模式), 它自己把 brd 之后的节点接上跑。结果: - brd → success(产出 brd.md + contract.json) - prd → **fail(impl_bug)**,run 收敛 blocked,预算 calls 3/100 tokens 52,096 - worktree 零污染(与跑前逐行一致) prd 自己的归因说清了原因,而且是对的:**它拿不到上游的产出**。 brd 的 brd.md / contract.json 躺在**它自己的私有目录**里, 而共享区 artifacts/ 只有 project-root.json 与 task-goal.json —— 同一张 work graph 内部**上游→下游没有任何数据通路**。 这不是引擎 bug,是 §四.A.5 设计里只做了「跨 org node 走 artifacts/ 共享区」, 没做「同图内相邻节点怎么把 contract 交出去」:CONVENTIONS §12 的隔离原则是 「下游只能看到上游的契约、看不到实现」,但契约被写进了私有目录, 下游同样读不到;而写 artifacts/ 是只给验收契约产出者的特权动作。 W12 补完在这一轮同时得到验证:fail_reason=impl_bug 落库,概览 tab 显示 「[归因产出] impl_bug」+ 完整 remediation_note,不再是「失败、无原因」。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
LEMONBOSS_DISABLE_WATCHDOG=1 时不启动。 第三次真跑记录:Master 智能巡检带 full 工具权限、会在工作树里改源码,还会自主 下任务单;而图执行的边界守卫用整仓 git status 差集判归属,于是并行 Master 的写 被算到「当时正在跑的 work 节点」头上,把一个只是超时的节点额外署成 boundary_escape。 守卫做不到区分是谁写的(W3「拦不住」第 5 条),所以真跑期间工作树里不能有第二个 agent 在活动 —— 这是规程问题,不该指望守卫变聪明。此前没有任何关闭开关。
-
测试 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>
-
测试 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>
-
## W14 · POST /:cid/tasks 补上前端调用方 该端点此前**全仓库没有任何前端调用方** —— 界面走查确认过:用户在控制台上 没有任何办法给公司下达任务,只能 curl。这是「实现在、零调用方」在 P3 里的 第 N 次出现(W8 刚给 replan 补上,tasks 还欠着)。 新增 DispatchTaskButton.vue: - 手动/自动**在下达时就选**,不是跑起来再改 —— execution_mode 是 run 的属性, 起 run 时定;后补一个下拉会让人以为能中途换 - 自动模式在这一步就给成本预警(真 CLI 一次中等任务 $10-20 / 1-2 小时), GraphRunPane 的「启动」那道确认是针对已有 run 的,下达是更早的闸门 - 409(v1 并发约束)连同服务端 hint 原样透出,比翻译成「出错了」有用 - run 在跑时按钮直接不出现,而不是让人点了吃 409 挂载:有图未跑 run 时给主操作位(这时候它是页面唯一该做的事), 有 run 时收进标题栏。顺手修 .left-actions 缺 gap 导致两个按钮重叠。 ## 第二次真 CLI 冒烟(worktree 隔离,主仓库 data/ 零写入) 链路比上一轮走得远得多,两个硬缺陷的修复都在真跑中直接验证: 1. **R-18.1 端到端成立**:POST /create 自动触发 orggraph-planning 真跑, 它自己把图落库(3 节点 4 边,含两条 backward fail 边)**并自己雇了 org-planner / org-coder / org-validator** —— Step 7 的招聘真执行了。 2. **W5/W6 自雇再次验证,且规划质量明显更高**:workgraph-planning 规划出 brd → prd → benchmark → validator 四节点主干,两条回退边一条带 fail_reason=input_underspecified、一条无 reason 作兜底 —— 它自己用上了 D.2.2 的细分路由并满足 fail-fallback-edge 规则。四个本体全部自雇。 3. **守卫归属修复直接命中**:timeline 里 「本次调用窗口内有 1 处 agent 运行态文件改动,无法归属到本节点,不计入越界 unattributable = ['M system/agents/agent-creator/.claude/scheduled_tasks.json']」 ——正是上一轮把成功节点判成 fail 的那个文件,这次被正确摘出, org-planner-brd → **success**(上一轮它跑起来了但被误判)。 4. tokens 真的在计量(24,218),mock 下恒为 0。 产出位置正确:brd.md 落在 runs/<rootRunId>/nodes/<nodeRunId>/, 项目根 projects/p-4hjo36 按 root run 分配。worktree 源码跑完已复原到跑前状态。 测试 760/760 全绿(72 文件)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
## 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>
-
真 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>
-
测试 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> -
界面走查(真 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> -
按 docs/README.md 既有约定(done/ 放「已实现/当前有效」),把两份已落地的 阶段设计文档移入 done/system-design/: - 27-p1-virtual-reference.md P1 虚拟引用寻址,commit 226489d - 27-p2-org-graph.md P2 Org Graph,70a4727 + f20d0d8,真实建公司端到端验证过 用 git mv 保留历史(R098 / R087)。移动断掉的 33 处相对链接已全部修复: moved 文件内的出向链接改 ../../todo/system-design/,todo/ 下 6 个文件的入向链接 改 ../../done/system-design/。全 docs/ 扫描无断链。 README 此前严重过时——只列了 2 份 27 文档,实际已有 11 份。本次补全 todo/ 表格, 每份标注状态与作用(总纲 / 需求源头 / 命名规范 / 端到端 / 参考场景 / P3+P4 已实现 / 验收契约 / P4 设计中),并在 done/ 表格补上 P1、P2 两行。 ■ 刻意没有移动 27-p3-p4-work-graph-console.md 它虽然已实现(c3abf4a),但文档正被另一进程持续修改(工作区 M 状态)。 移动会打断它的编辑路径,等它收尾后再归档。 ■ 三份文件的链接修复留在工作区未提交 27-naming-conventions.md / 27-p3-p4-work-graph-console.md / 27-requirement.md 里,我的链接修复与另一进程的在途编辑混在同一文件中,单独拆分风险大于收益。 工作区内容是正确的(链接已指向新路径),会随它下次提交一并带上。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
■ 关于编号 原「P4 回退 + Loop-Validator」已并入 P3 实现完毕(c3abf4a:双层熔断、五项结构 校验含 fail-fallback-edge、三层暂停级联、contract_hash 重试判据)。本文档回收 这个空出来的编号,装 P3 明确排除、现在成为最大能力缺口的内容。 ■ 核心:fan-out 并行 P3 把图结构和 join 语义都做好了,唯独没做并行本身: - graph-engine.js:1078 出边评估「所有入边满足才置 ready」——fan-in join 正确 - graph-engine.js:908 拾取 SQL 是 LIMIT 1——三个并行分支只会一个个串行跑掉 设计为 advanceBatch(runId, {maxConcurrency}),不新增第二个推进原语: 手动模式 maxConcurrency=1 时行为与今天的 advanceOne 完全一致,P3 已有调用方 和测试不用改;自动模式一次拾取整批。批量拾取必须在一个事务里一次性置 running, 否则两个并发 advanceBatch 会拾取到重叠集合。 ■ 走查发现的关键事实:隔离挂载点已经存在 session-pool.js:29 的 getKey(agentName, agentConfig, workDir, scope) 签名里 早就预留了 scope 参数,但第 45 行实际调用时没传。所以「同一 agent 多实例隔离」 这个并行前置条件的改动量比预想小得多——用 graph_run_nodes.id 作 scope 即可。 连带要放宽 CONVENTIONS §12 第一条不变量:(pid, agent) → (pid, agent, scope)。 这是 P4 里唯一要动 DevLoop 规格的地方,且 _spec/ 是生成物,必须改 HTML 后跑 npm run spec:extract。该放宽同时是 P6 的前置——DevLoop runProject 波内串行 (loop-controller.js:609 注释「串行跑」)正是被同一条不变量挡住的。 ■ 产物统一目录 + files.json 这是此前提出但一直未实现的需求。按 task 分目录 data/companies/{cid}/tasks/{taskId}/,根目录 files.json 作统一检索索引, 由引擎在产物落盘后自动追加(不靠 agent 自觉写,否则索引必然漂移), 并支持从 graph_run_nodes 行 rebuild。与 output_ref 不重复:后者是节点级指针, 前者是公司级聚合视图,由前者派生。 ■ 三个阶段,C 完全独立 A 隔离(T1-T2) → B 并行(T3-T6,T10);C 产物索引(T7-T9) 与 A/B 无依赖,可最先做。 并发上限对齐 plan-cost-concurrency.md §④ 的既有数字(run≤5 / 公司≤3 / 单机≤20 session),不另立一套。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
命名轴是「节点在哪一层图里」,把散落的老 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> -
按 27-p3-p4-work-graph-console.md v0.3 实现,三轮开发+校验后测试全绿 (693/693,P2 26/26、DevLoop 230/230 未被打破,web:build 干净)。 服务层 - work-graph-db.js 定义层(work_graph_*) + 运行时层(graph_runs/graph_run_nodes/ graph_run_loop_records) 五张表;状态转换表照 unit-registry 模式; 复用 org-graph-service 四项校验 + 新增第五项 fail-fallback-edge - graph-engine.js advanceOne 幂等原语;agent_call / subgraph 两种 executor; 手动与自动共用同一原语;双层熔断(单边 loop_count + 按 root_run_id 共池的 backward_count);任务级预算挂 root run;三层暂停/停止级联; 重试按 contract_hash unchanged 判据 - graph-run-sse.js 按 run_id 广播,状态转换+timeline+SSE 三者原子化 - goal-contract.js 验收契约格式与执行器(R-15.6 验收先行) 引擎泛化(R-15.4):org graph 与 work graph 走同一套 advanceOne, 定义层两套、运行时层一套,三层下钻用 parent_run_id 链。 R-16 失败归因:validator-reflection 专职归因节点;fail_reason 多边路由 (精确匹配→兜底边→死路);子 run 收敛透传 success/fail + fail_reason + remediation_note 三值;infra_flake 不烧任何计数。 路由 / 前端 / 模板 - routes/graph-run.js 八个端点;company.js 任务入口 - WorkGraphCanvas.vue 扩展 UnitsDAG(subgraph 角标、backward 虚线曲线、 N/3 循环徽标、异常流程视觉降权);GraphConsole.vue 三栏 + 四 tab 详情 - 五个新 agent 模板:validator-reflection / org-validator / planner-validator / coder-validator / workgraph-planning 命名统一(R-15.3 + NAMING §三):org 节点改为 org-{职能}, 三个 org-graph 模板末节点收敛到 org-validator;org_graph_edges 加 fail_reason 列并把唯一索引补上该列(避免精确边与兜底边互相吞掉)。 三轮校验修掉的主要缺陷:subgraph 重跑不起新子 run 导致永久死锁; SSE/timeline 零调用方;org 侧 fail_reason 写入被丢弃;fail-fallback-edge 只做了一层;backward_count 跨 run 树重置架空熔断外层; seedEntryNodes 误点亮只有 fail 入边的节点。 已知缺口见后续提交:remediation_note 仅在 backward 边落库、 total_tokens 预算维度无生产方。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
命名轴从「业务领域」({domain}-{职能}) 改为「所在图的层级」: org graph 节点 org-{职能} org-planner / org-coder / org-validator work graph 节点 {父 org 节点全名}-{职能} org-coder-validator 三个模板因此首尾同名,只有中段职能不同——规划和验收本身不分领域: dev org-planner → org-coder → org-validator content org-planner → org-writer → org-validator ops org-planner → org-monitor → org-validator ■ NAMING §三 补上此前完全缺失的一条轴:节点名 ≠ 本体名 R-15.3 指出的关键区分。禁止裸用 validator 的理由是「一家公司内两个节点不能 重名」,该理由针对节点、不针对本体。本体若被迫带前缀,coder-validator 会复制成 org-coder-validator / content-coder-validator 多份,与 P1「同一份本体、多处引用、 各自私有语境」直接冲突。落到 schema 上就是 node_name / system_agent_id 两个字段。 同时写入 §3.3(三个 validator 是三个独立本体,验的东西本质不同,与 §八bis 「不特化」原则不冲突)与 §3.4(存量 agent 归位:innovator→org-innovator、 hunter→innovator-hunt、digester→innovator-digest、evolver→innovator-evolver, 依据是 retrain-pipeline.js 里三者就是 innovator work graph 的三阶段)。 §3.4 顺带解决 27 号文档 §七 挂了很久的 6 个命名冲突 id。 ■ 代码层已改 system/org-graph-templates/{dev,content,ops}.json + README、 system/templates/orggraph-planning/{agent.md,tools.md} ■ 代码层未改(已在 §3.4 末尾标注) §3.4 的四个存量 agent 重命名需要单独一次迁移(目录改名 + system_agents 表 + retrain-pipeline.js 等引用点),本次未做。 验证:三张模板图改名后实跑 validateGraph 仍全部通过四项校验; npm run test:unit 676 全绿;test-company-e2e 114/114。 未触碰 27-p3-p4-work-graph-console.md(另一进程正在做 P3 开发,且其中命名已符合 R-15.3)与 27-requirement.md(原文照搬文档)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
R-16(归因由专职 subagent 做,回退边多条): - §四.D.2.2 归因改为 org-validator work graph 内的 validator-reflection 专职节点,与 DevLoop 的 Integrator/BugTriager 分工同构;给出四类 fail_reason 取值与 DevLoop 四分类的对照 - §四.D.0 子 run 收敛改为透传三值(success/fail + fail_reason + remediation_note),归因结论才能穿透子 run 边界供父层选边 - §四.D.2.1 熔断改为双层计数:单边 loop_count 防同路径死循环,新增 run 级 backward_count 防"换个借口继续退"——多条回退边各自计数会把 硬顶架空成 3xN;infra_flake 不烧任何计数 - §三.7 写入前校验新增第五项 fail-fallback-edge(强制兜底边) - schema:graph_runs 加 backward_count - 任务表加 T13/T14,验收标准加 9b/9c/9d - skill_defect 属 L4,v1 保留归因取值但动作降级为挂起等人工 base agent 四段式(新增 §九bis): - 核实后原表述"已强制一套四段式生命周期"只对一半 - Context Flush / P0 事前阻断阀 / OKR 联动确实在跑 - Hot Swap 换技能包静默失效:system/skills/ 目录不存在, agent-chat.js:1908 的 existsSync 守卫使其空转且不报错 - 后果:EXECUTE 才拿破坏性权限只剩 prompt 话术约束,无机制保障 - phase 参数无取值/顺序校验,且被直接拼进文件路径 需求文档:补回 R-15.1 全文、R-15.4 后半、R-15.5、R-15.6 及速查表 对应行(此前被误回滚丢失),新增 R-16 条目。R-15.3 的命名表保留 NAMING §三 重写后的版本,未还原旧版。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
■ 前端路由遮蔽(既有 bug,被 P2 的 T8 暴露) web/src/router/index.js: CompanyAgentChat 的 path 是 /:id/agents/:agentId? —— agentId 可选,导致它抢占 /:id/agents,而导航栏「Team」正是指向这个地址。 结果 CompanyDashboard 的团队 tab(连同 P2 新增的 Org Graph 区块)完全不可达, 点 Team 打开的一直是 AgentChat。改为 :agentId 必填。 ■ Org Graph 空边时凭空画出流程链(P2 T8 的 bug) 0 条 forward 边时所有节点入度都是 0,全部落进拓扑序且 degraded=false, 模板照常在节点间渲染 → 箭头,凭空捏造出一条并不存在的流转顺序。 P0 迁移进来的存量公司(有节点无边)全部中招。新增 orgGraphNoEdges 判定: 这种情况只平铺、不画箭头,并明确提示「尚未定义流转顺序」。 ■ create() 仍在建 goals/ 目录(我在 OKR 正名 74ea1ff 里的遗漏) company-service.js 的 subdirs 列表没跟着 goals→okr 一起改,新建公司仍然产出 goals/ 空目录。改为 okr/。 ■ test-company-e2e.js 从「第 4 个断言就崩」修到 114/114 全绿 该脚本长期整体失效,HEAD 版本同样全断言失败。四类问题: 1. 路径过期:data/{id}/ → data/companies/{id}/;data/templates/ → system/templates/;data/agents/master → system/agents/master 2. Phase 4 用 ceo 模板测「骨架是否完整复制」——但 system/templates/ceo/ 只有 plan.md + todo.md,没有 agent.md/soul.md/config.json,测不出东西,且缺 config.json 就没有 type='cxo',company.ceo 也标不上。改用同为 cxo 且文件 完整的 coo。(ceo 模板本身的残缺是另一个待补的问题) 3. 断言停留在 P1 之前:P1 虚拟引用(226489d)之后 agent.md 不再物理拷贝, 改为 config.json.systemAgentRef + company-context.md。按新模型改写 4. 断言停留在 T5b 之前:模板不存在时返回 hired_from_base 而非 training 顺带记录一个设计缺口(未改,仅在测试注释里说明):system/templates/base/ 目前只有 agent.md + config.json,所以 base 继承出的骨架不含 soul.md / identity.md / tools.md / todo.md,也不含 AgentScaffold 的标准子目录结构 (memory/short-term、knowledge、workspace 等)。这些依赖 agent-creator 填充。 另删除 OKR 迁移的 *.bak-goals 备份目录及其 .gitignore 规则。 验证:npm run test:unit 676 全绿;test-company-e2e 114/114;web:build 通过; Chrome 实机确认 Team tab 可达、空边提示正确。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
- §八.1 单一入口:R-13.4 已定案,标记关闭,保留放宽条件的论证供将来参考 - §八.3 建公司耗时:实测约 30 秒(非几十秒到几分钟的估计);补充说明前端 空态未真正区分「规划中」与「确实无图」,是刻意简化 - §八.5 前端画布:已按占位形态交付,不阻塞 P2 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
真实建公司验证时发现的两个串联问题——只有起真服务才暴露得出来, 工作流的集成 agent 全程没起过服务,测试也覆盖不到。 1. 地址错误:orggraph-planning 是被 hire 进公司的,住在 data/companies/{id}/agents/ 下,寻址必须是 `{companyId}:orggraph-planning`。 原代码请求裸名 /api/agent/orggraph-planning/chat → 404 Agent not found, agent 从未被触发,公司永远停在空壳状态。 2. 失败被静默吞掉:fetch 只在网络层出错时 reject,4xx/5xx 不会 reject, 所以原来的 .catch() 从头到尾没打印过任何东西。补 res.ok 检查 + 成功日志, 这类"地址写错"的问题以后能第一时间看见。 修复后端到端验证通过(临时公司 p2-verify,验证完已清理): - POST /create → company.goal 落盘、orggraph-planning 被招聘且因 meta-bootstrap 被正确排除在 org graph 外 - 日志出现 "✅ 已触发 p2-verify:orggraph-planning" + ACP 会话创建 - 30 秒内 agent 产出 4 节点 5 边的图并完成 4 个角色招聘 - agent 选中 dev.json 且**扩充**出模板没有的 code-tester(goal 要求带单测), 并在规划记录里明确排除了 content.json 的歧义("Markdown/HTML 是被处理的 数据格式,不是要产出的内容作品")——模板作为"起点集合"的设计意图成立 - 产出的图实跑 validateGraph 通过四项校验 - 四个角色 config.json 均为 inheritedFrom=base(元 agent 继承路径生效), system_agents 里 scope='company' + owner_company_id 正确 - orggraph-planning 收尾后 heartbeat.enabled=false,正确休眠 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> -
用 9 个并行 agent 按「文件独占」划分实现 P2 的 T1-T9(T0 已于 2255f09 完成)。 详见 docs/todo/system-design/27-p2-org-graph.md §五。 阶段 A · 数据层 - graph-db.js: org_graph_nodes 加 status/reports_to_node_id(PRAGMA 探测后 ALTER, 幂等);全量唯一索引换成 WHERE status='active' 的部分索引,使「fire 后重新 hire 同角色」可行;org_graph_edges 重建为执行流边(condition/edge_kind/status) - graph-service.js: ensureOrgGraphNodeIndex 同步改建部分索引,且 upsertOrgGraphNode 的 ON CONFLICT 必须写成 ON CONFLICT(company_id, role_name) WHERE status='active' —— SQLite 的冲突目标要与部分索引谓词逐字对应,否则整条 INSERT 直接失效 - org-graph-service.js(新): validateGraph 四项校验 + writeGraph/patchGraph/ getGraph/removeNode,事务化,节点软删除级联失效相关边 - routes/company.js: 四个端点 POST/PATCH/GET /org-graph + POST /org-graph/replan; /create 接入自动招聘 orggraph-planning + 异步触发(失败降级为空壳公司); /fire 接入节点软删除 阶段 B · 自动生成 - system/org-graph-templates/{dev,content,ops}.json + README:三张三段式骨架 (规划→执行→验收,验收失败回退到执行节点),角色名遵循 {domain}-{职能} - system/templates/orggraph-planning/:新 system agent(type='meta-bootstrap'), 含 §4.1 八步工作流 - company-service.js: 模板不存在时从 system/templates/base/ 元 agent 继承骨架 (自动带 RESEARCH→PLAN→EXECUTE→VERIFY 四段式)+ agent-creator 填充, 新建 agent scope='company'。这是 27-E2E §八bis 元 agent 继承的落地点 阶段 C · 展示 - CompanyDashboard.vue: 组织流程拓扑链渲染 + 空态降级 + 环深度保护 并行编排中发现并修复的三处(均不在原任务划分内): - AppNav.vue 建公司表单缺 goal 输入框 —— T0 只改了后端,前端表单没同步, company-goal 实际传不进来。这是上一轮 T0 的遗漏 - graph-seed-service.js 会把 type='meta-bootstrap' 的 orggraph-planning 塞回 org graph,与 hire() 的 guard 不一致 - graph-agent-p1-acceptance.test.js: hire 未知角色的返回从 needs_training 改为 hired_from_base(T5b 的行为变更),断言同步 验证(独立复核,非 agent 自述): - npm run test:unit 676 passed / 63 files(基线 652,+24 为新增的 org-graph 用例) - npm run web:build 通过 - 三个模板 JSON 实跑 validateGraph 全部通过;四类非法图各自被对应 rule 拒绝; writeGraph→getGraph→removeNode 级联验证通过(删中间节点后 3 条边全部失效) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>