{ "meta": { "schemaVersion": "0.1", "title": "dsh-web-relay 主 agent 语境能力 lesson 库(机器可读)", "updatedAt": "2026-09-13T16:23:14.079Z", "carrierOf": "docs/main-agent-lesson-schema-v0.1.md", "statusConvention": "proposed(主 agent 产出)→ approved / in-runbook(用户或外部 AI 确认,记 confirmedBy/confirmedAt);in-runbook=要点已并入 main-agent-runbook 正文;superseded=内容已并入他条(扩展值,机器消费时应跳过)", "confirmedAt": "2026-09-04T13:14:35.897Z", "confirmedBy": "user(主对话确认:同意三态分类建议)", "confirmationNote": "2026-09-05:用户确认 025-027 转 approved。" }, "lessons": [ { "id": "L-2026-0903-001", "title": "high+review:true 步骤不得由主 agent 自批 approved", "category": "collaboration-rhythm", "layer": "L3", "trigger": "importance=high 且 review=true 的步骤实施完成,准备置状态时", "decision": "置 review / 提交 auto-review(external→dialog→manual),由外部 AI、dialog 或用户在面板批准;仅 low/review:false 可自批(reviewedBy=mainagent)", "rationale": "审核来源是 v1.9 审计链的一环;自批会伪造 reviewedBy 并破坏版间门与降级链语义", "evidence": "expr-2026-09-03_01-27-21;main-agent-runbook-v0.1.md §2.2", "confidence": "high", "status": "in-runbook", "suggested": "in-runbook", "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-002", "title": "被唤起先读 steps.json 顶层 iterations/autoDecision 再表态(叙述式声明可能未解析)", "category": "repository-knowledge", "layer": "L3", "trigger": "收到 dsh-web-relay handoff / 主 agent 对任务表态前", "decision": "先读 expr-.steps.json 顶层(protocolVersion/iterations/autoDecision/currentIteration/status)再表态;叙述式声明(Answer 文本里的 iterations/autoDecision)可能未落盘,以落盘值为准", "rationale": "叙述式'自动迭代N个版本'等声明未落盘时,引擎按普通任务执行(01:27 案例顶层仍 1/false)", "evidence": "expr-2026-09-03_01-27-21;expr-2026-09-02_01-07-59 / 13-07-59 / 02-36-44(叙述式未落盘复现,自 L-2026-0903-007 并入);main-agent-runbook-v0.1.md §3 SOP.1", "confidence": "high", "status": "in-runbook", "suggested": "in-runbook", "crossRef": [], "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-003", "title": "tag 版本延续 v3.x,勿用旧 v1.9.x 命名", "category": "repository-knowledge", "layer": "L3", "trigger": "自动迭代发布、决定版本号/打 tag 前", "decision": "版本号延续 package v3.x(v3.3.x/v3.4.x/…)语义化续线;拒绝 v1.9.x/v2.x 旧协议时代命名;bump 用 node 重写 package.json 避免 BOM", "rationale": "仓库 tag 已演进至 v3.2.x~v3.4.0;回退旧命名会造成 tag/审计混乱", "evidence": "expr-2026-09-03_01-27-21(v3.4.x 被采纳);expr-2026-09-02_02-36-44(冲突负例,自 L-2026-0903-011 并入);main-agent-runbook-v0.1.md §4", "confidence": "high", "status": "in-runbook", "suggested": "in-runbook", "crossRef": [], "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-004", "title": "PS5.1 Set-Content UTF8 写 BOM 会破坏 JSON.parse,版本 bump 用 node 重写", "category": "tooling-trap", "layer": "L3", "trigger": "Windows 下用 PowerShell 写 package.json / JSON 文件后 node JSON.parse 失败", "decision": "用 node fs.writeFileSync(...,'utf8') 重写(去 BOM);勿用 PS5.1 Set-Content -Encoding UTF8", "rationale": "PS5.1 UTF8 编码写 BOM,BOM 破坏 JSON.parse(9/3 版本 bump 实测)", "evidence": "main-agent-runbook-v0.1.md §6 陷阱表", "confidence": "high", "status": "in-runbook", "suggested": "in-runbook", "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-005", "title": "规划-现状冲突:审计行号取证→交外部 AI 评审 restructure,不自裁", "category": "judgment-heuristic", "layer": "L3", "trigger": "外部 AI 规划与仓库现状冲突 / 疑似重造时", "decision": "行号级取证 + 历史 trace 对照 → 差异表经 /ask 交外部 AI 评审 restructure;不擅自替换外部 AI Step List", "rationale": "restructure 规划权在外部 AI/面板/用户;自裁曾导致越权(01-27-21 自我纠正)", "evidence": "expr-2026-09-03_01-27-21;main-agent-runbook-v0.1.md §2.3/§5", "confidence": "high", "status": "in-runbook", "suggested": "in-runbook", "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-006", "title": "外部 AI 声称 iterations/autoDecision 未落盘时,先核对 steps.json 顶层再判定模式失效", "category": "repository-knowledge", "layer": "L3", "trigger": "外部 AI 声称已声明自动迭代(iterations/autoDecision)而引擎未按自动模式执行时", "decision": "先核对 steps.json 顶层是否真的落盘,再判定'模式失效/引擎缺陷';勿以 Answer 文本为据", "rationale": "与 002 同源(以落盘为准),面向'对外部声称做复核'的判定场景", "evidence": "expr-2026-09-02_01-07-59(叙述式未落盘) vs expr-2026-09-02_01-33-59(严格 JSON 落盘正例)", "confidence": "medium", "status": "approved", "suggested": "approved", "crossRef": [ "L-2026-0903-002" ], "recordedAt": "2026-09-03T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-007", "title": "自动迭代先核对 steps.json 顶层落盘声明(叙述式 iterations/autoDecision 常未落盘,勿信 Answer 文本)", "category": "repository-knowledge", "layer": "L3", "trigger": "(与 002 重复)收到含'自动迭代N个版本'声明的任务时", "decision": "(同 002)以 steps.json 顶层落盘声明为准;本条目已并入 L-2026-0903-002", "rationale": "考古新增时未发现已存在的 002;2026-09-04 用户确认并入 002", "evidence": "expr-2026-09-02_01-07-59 / 13-07-59 / 02-36-44 / 01-27-21(已并入 002 evidence)", "confidence": "high", "status": "superseded", "suggested": "merge-into:L-2026-0903-002", "supersededBy": "L-2026-0903-002", "crossRef": [ "L-2026-0903-002" ], "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-008", "title": "被打回先查审核通道缺陷(摘要截断/dialog 无 JSON/批次连坐)再 reopen,非内容问题不改产物", "category": "judgment-heuristic", "layer": "L3", "trigger": "步骤被打回,准备 reopen / 补证据前", "decision": "先判断打回是否审核通道缺陷(审核摘要静默截断、dialog 无干净 JSON、batch 原子连坐)→ 举证驳回 reopen;确为内容问题才改产物补提", "rationale": "13-36-43 两次打回均为通道缺陷误判(截断/v3.3-1 修复、dialog 无 JSON);18-46-49 批次连坐放大成本", "evidence": "expr-2026-08-31_13-36-43;expr-2026-08-31_18-46-49", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-009", "title": "一步一版时 currentIteration 计数失真,以步级 Vn 标签+done 状态判断真实进度", "category": "repository-knowledge", "layer": "L3", "trigger": "steps.json currentIteration 与步内 Vn 标签 / 实际 done 状态不一致时", "decision": "以步级 Vn 标签 + 步骤终态判断真实进度,不依赖 currentIteration 计数;如引擎发模板化'进入 Vn'消息与事实不符,以文件/事实为准并上报", "rationale": "一步一版(flat steps)模式下 ci 停在 2 而 V5/V3 已完成(14-34-33/15-00-11),计数与事实脱节", "evidence": "expr-2026-08-31_14-34-33;expr-2026-08-31_15-00-11", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-010", "title": "提审前先落实体产物(artifact_required),text-complete 与 artifact-complete 脱节会招致无效打回", "category": "engineering-action", "layer": "L2", "trigger": "步骤 complete / 提审(置 review)前", "decision": "artifact_required=true(默认)的步骤必须先产出实体产物并挂载 step.artifacts 再提审;纯分析步显式 artifact_required=false", "rationale": "text-complete(只写 notes)与 artifact-complete 脱节是打回主因(15-00-11/18-46-49)", "evidence": "expr-2026-08-31_18-46-49;expr-2026-08-31_15-00-11", "confidence": "high", "status": "approved", "suggested": "approved", "crossRef": [ "L-2026-0903-014" ], "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-011", "title": "版本/tag 命名以仓库 package v3.x 现状续线,拒绝回退 v1.9.x/v2.x 规划命名", "category": "repository-knowledge", "layer": "L3", "trigger": "(与 003 重复)外部 AI 规划要求打 v1.9.x/v2.x 等与仓库现状冲突的 tag 时", "decision": "(同 003)以 package v3.x 现状续线;冲突规划先在审计中标注 tag 冲突并纠正;本条目已并入 L-2026-0903-003", "rationale": "考古新增时未发现已存在的 003;2026-09-04 用户确认并入 003", "evidence": "expr-2026-09-03_01-27-21;expr-2026-09-02_02-36-44(负例,已并入 003 evidence)", "confidence": "high", "status": "superseded", "suggested": "merge-into:L-2026-0903-003", "supersededBy": "L-2026-0903-003", "crossRef": [ "L-2026-0903-003" ], "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-012", "title": "外部 AI 通道失能时代行其职要明示'代构造'并留独立审方兜底,防自审自产", "category": "collaboration-rhythm", "layer": "L3", "trigger": "外部 AI ask 通道反复中止/429 不可用,需要主 agent 代构造方案/Step List 时", "decision": "代构造的 Vn 要点/Step List 须在产出与 trace 明示'主 agent 代构造',并尽量留独立审方(dialog/manual)兜底审核", "rationale": "自己代构造又自己审核=自审自产(15-00-11 靠 dialog 独立审核兜底);角色混淆风险需透明化", "evidence": "expr-2026-08-31_15-00-11;expr-2026-08-31_13-36-43", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-013", "title": "自评自证(reviewedBy=mainagent)必须显式披露并留打回重审选项", "category": "collaboration-rhythm", "layer": "L3", "trigger": "主 agent 以 reviewedBy=mainagent 通过(含 high)步骤或收口时", "decision": "显式披露'自评自证,非外部 AI 逐审',并在交付物/档案中留'打回触发 auto-review 重审 / 一键收口'选项", "rationale": "autoReview=true 但审方=执行方是治理风险点(01-27-21 3 步 mainagent 自评);透明披露+重审选项可审计", "evidence": "expr-2026-09-03_01-27-21(交接档案 §3.4)", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-014", "title": "artifacts 必须在提审前实挂载(notes 文本不算产物),否则招致连坐打回甚至熔断", "category": "engineering-action", "layer": "L2", "trigger": "提审前 / 被打回后补产物时", "decision": "产物文件实挂 step.artifacts(路径+内容),notes 文字不作为产物证据;缺失即补文件再提审", "rationale": "15-26-14 产物只写 notes 未挂 artifacts → 三连打回触发真实熔断(paused→resume)", "evidence": "expr-2026-08-27_15-26-14(熔断实战);expr-2026-08-31_18-46-49", "confidence": "high", "status": "approved", "suggested": "approved", "crossRef": [ "L-2026-0903-010" ], "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-015", "title": "发布纪律:功能代号/tag/semver 对齐;预发布 vX.Y.Z-iter.N → 正式 tag;避免 tag force 覆盖与跨大版本单跳", "category": "repository-knowledge", "layer": "L3", "trigger": "自动迭代多轮发布、打 tag、bump 版本时", "decision": "功能代号(v2.5/v2.6 等)、tag 与 package semver 三者对齐;预发布用 vX.Y.Z-iter.N,迭代完成升正式 tag;避免 tag force 覆盖与跨大版本单跳", "rationale": "15-42-26 v1.5.0 被 force 覆盖、22-15-02 功能号 v2.5/2.6 配 tag v2.2.0、20-20-48 1.5.2→2.0.0 单跳,均造成审计混乱", "evidence": "expr-2026-08-27_15-42-26;expr-2026-08-27_22-15-02;expr-2026-08-27_20-20-48", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0903-016", "title": "restructure 后保持审核上下文(步骤标题/摘要)与执行列表同步,防审核者按旧标题盲审", "category": "judgment-heuristic", "layer": "L3", "trigger": "restructure 变更步骤(id 复用/标题替换)后即将提交审核时", "decision": "restructure 后同步更新任务记录摘要/审核提示,标注被替换步骤的旧标题→新标题映射,防 dialog 等按旧摘要审核", "rationale": "20-20-48 v2.3-1 步骤 id 复用后 dialog 审核者按旧'任务记录摘要'审核并指出矛盾", "evidence": "expr-2026-08-27_20-20-48", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T13:14:35.897Z" }, { "id": "L-2026-0904-017", "title": "能力考古须三源交叉(expr 三件套 + 主会话轨迹解码 + git tag/commit 发布史)并做 tag 覆盖度自检,防机制层能力漏登记", "category": "judgment-heuristic", "layer": "L3", "trigger": "能力考古 / 复盘收口时", "decision": "三源交叉取证:① expr 记录/steps/trace ② 主会话轨迹(如 session-a0a01dfa,需解码)③ git tag/commit 发布史;机制层能力(feature-capability:影子沙盒/Prompt 案例库/Swarm/突破度等引擎交付物)与行为层(A-D 判断模式)并行提炼;收口前逐 tag 核对'每个发布功能在清单是否有对应条目',输出'已知未入库'清单", "rationale": "v1.8→v1.9 考古漏 v3.0~v3.2 三个机制(expr-02-05-00 从未深读、影子沙盒被降格为 B10 证据、Swarm/突破度未登记);根因 R1-R6(本体缺机制层/漏斗缺功能向关键词/漏主会话轨迹数据源/提炼偏 process/收口缺覆盖度自检/时间窗按协议字面收窄)", "evidence": "docs/v3-roadmap-context-2026-08-28.md;expr-2026-08-28_01-29-26、expr-2026-08-28_02-05-00、expr-2026-08-29_00-00-00;git tag v3.0.0~v3.4.0", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T13:14:35.897Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-018", "title": "大版本多模块实施用\"文件解耦 + 多子代理并行 + 主 agent 统一验收\"(index/client/docs 分路,每路 node --check+冒烟);仅文件可解耦时才并行,代码同文件场景串行为主", "category": "engineering-action", "layer": "L2", "trigger": "多模块大版本(v0.9.0/v1.0.0 类)需提速实施时", "decision": "按文件边界拆子代理并行(各路只动自己文件),每路完成后 node --check+冒烟,主 agent 最后统一收口;同一文件/仓库写操作场景回到串行(防 git 冲突)", "rationale": "8/25 v0.9.0 三路、v1.0.0 多路子代理并行实测成功;8/28 讨论确认代码场景串行为主(物理 git 冲突+隐式依赖)", "evidence": "session-a0a01dfa [U 08-25 05:42~05:54];[U 08-28 01:35]", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-019", "title": "版本断言:任何重构/改版前先读 package.json 版本与 git HEAD,不符即停,不强行 Patch", "category": "repository-knowledge", "layer": "L3", "trigger": "收到改版/重构任务,动手前", "decision": "先断言 package.json 版本号/HEAD 符合预期再改;不符则停下报告,不强行 patch(防在多版本漂移状态下叠加改动)", "rationale": "用户采纳外部 AI 规约建议(8/24);v1.8 子代理明确\"先断言 1.1.0 后动手\";曾出现 statusHandler 硬编码 1.3.0 漂移与 1.0.1 未入表", "evidence": "session-a0a01dfa [U 08-24 23:15];d925feb1 子代理报告;commit 92b57d6", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-020", "title": "git 原子交付规约:里程碑强制 tag/commit;回退用 checkout/reset 而非跨会话重放历史 Edit;必要时全量快照备份", "category": "engineering-action", "layer": "L2", "trigger": "版本里程碑/需要回退到旧版本时", "decision": "完成即 git tag/commit(原子);回退 git checkout/reset --hard 到 tag;严禁靠会话历史重放 Edit;无 git 环境用全量文件快照", "rationale": "v0.7.0 曾被\"手工重放旧 Patch\"方式搞乱(8/24 用户带回);外部 AI 4 建议被用户采纳", "evidence": "session-a0a01dfa [U 08-24 23:15/23:40]", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-021", "title": "实测数据驱动协议演进:大任务后以真实用量复盘(626 turns/$ 成本)反推协议缺口(→ alternatives/importance;固定成本≈$0.05/turn → 优先减 Turn)", "category": "judgment-heuristic", "layer": "L3", "trigger": "大任务收口后 / 每次协议迭代规划前", "decision": "先盘点真实 turns/成本与返工来源,再决定协议级改进(减 turn/合并审核/权重),而非拍脑袋加功能", "rationale": "626 turns 复盘直接催生 v1.7 alternatives/importance/批量审核;固定成本模型下减 turn 是第一优先级", "evidence": "session-a0a01dfa [U 08-25 21:28~22:14](v1.7 P1-P9)", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-022", "title": "跨工作区上下文桥接:handoff 注入的 workspacePath ≠ 默认 cwd 时,读/写/回写一律以注入路径为准", "category": "repository-knowledge", "layer": "L2", "trigger": "收到含 workspacePath 的跨工作区 handoff/试验", "decision": "记录、steps、trace 都解析到注入 workspacePath 下(web-relay/…),不落当前 cwd;收口 POST /trace 带 workspacePath", "rationale": "expr-…_02-09-32 跨工作区桥接实测(8/25);多工作区并行时防止写错目录", "evidence": "session-a0a01dfa [U 08-25 10:09~10:12]", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-023", "title": "web-gemini 渠道问题分层排查:唤醒缺失/steplist 截断/面板无反应 → 扩展↔bridge↔解析三层定位,不单一归因", "category": "judgment-heuristic", "layer": "L3", "trigger": "web-gemini 通道出现任务无唤醒/回复截断/面板无响应", "decision": "按 扩展(轮询/防截断/多Tab)→ bridge(超时/提交)→ 插件解析(prompt 构造/steplist 提取)分层取证定位;修复后留复测", "rationale": "8/27-8/28 多次 web-gemini 渠道问题(唤醒缺失/steplist 截断/面板无反应)逐层定位并修复(v2.1.0 content 双信号防截断等)", "evidence": "session-a0a01dfa [U 08-27 22:31~23:26];commit 2b9d23a", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0904-024", "title": "版本号多源一致性:package.json/README/说明书/statusHandler 四处同源,防硬编码漂移", "category": "tooling-trap", "layer": "L3", "trigger": "发布 bump / 文档同步 / status 显示版本时", "decision": "版本号以 package.json 为单一数据源;README/说明书/statusHandler 同步;文档历史表不遗漏已发小版本(曾 1.0.1 未入表)", "rationale": "statusHandler 曾硬编码 1.3.0 漂移(v2.0.0 修复);1.0.1 发布未入 README 历史表(子代理提示)", "evidence": "commit 92b57d6;session-a0a01dfa [U 08-25 22:20]", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-04T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-04T15:20:00.000Z" }, { "id": "L-2026-0905-025", "title": "E2E 端到端真实校验是长链深层 bug 的防线:静态预检/单测易漏参数错位与字段丢失,发布前必须做一次真实运行时链路验证", "category": "engineering-action", "layer": "L2", "trigger": "涉及 fs 写参/状态字段持久化/数据透传的改动,在发布前", "decision": "除单测外补一次 E2E(真实 finalize/restructure/complete 触发)验证字段与写参在运行时闭环;失败按错误清单修复后重验", "rationale": "v3.6.0 实测抓到 3 个真 bug(P1 fs.writeText 5 参错位致案例库从不写入;P3 incrementalStreak 未持久化;P3 restructure 审 normalizedNew 丢 breakthrough_type),单测全绿仍漏", "evidence": "expr-2026-09-04_17-45-25 E2E 实证;commit 8535fda", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-05T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-05T01:00:00.000Z" }, { "id": "L-2026-0905-026", "title": "\"缺证据打回→补证重提\"三方闭环:complete 必带实体证据(执行日志/测试数/commit),外部 AI 按证据审核", "category": "collaboration-rhythm", "layer": "L2", "trigger": "主 agent complete/提交审核时;外部 AI 审核证据不足时", "decision": "主 agent:complete 前把实施证据写入 notes(文件/命令输出/测试数/commit);外部 AI:证据不足即 rejected 并指明缺什么,避免放水", "rationale": "v3.6.0 5 步外部审核 2 次打回-补证闭环,拦截\"虚假完成\";证据型完成是三方可信的基石", "evidence": "expr-2026-09-04_17-45-25(Step1/5 打回补证)", "confidence": "high", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-05T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-05T01:00:00.000Z" }, { "id": "L-2026-0905-027", "title": "热路径成批变更 + 受控重启纪律:core 加载链改动攒批、一次重启验证多步,减少中间态不一致", "category": "engineering-action", "layer": "L2", "trigger": "需重启才生效的宿主(lib/index.js 等)改动", "decision": "能热(client/纯模块)就不重启;host 改动成批后单次重启收口验证;重启清单随版本记录(E2E/外部审核/版本号显示项)", "rationale": "v3.6.0 以\"client 刷新即生效 + host 改动聚合一次重启\"完成 5 步收口;设计文档 §10 三类支撑(门禁/影子/热路径)", "evidence": "docs/auto-iteration-shadow-support-design.md §10;expr-2026-09-04_17-45-25", "confidence": "medium", "status": "approved", "suggested": "approved", "recordedAt": "2026-09-05T00:00:00.000Z", "confirmedBy": "user", "confirmedAt": "2026-09-05T01:00:00.000Z" }, { "id": "L-2026-0905-028", "title": "宿主级机制开发必须 DRYRUN/模拟先行;禁止对真实宿主直接执行 kill/拉起", "category": "engineering-action", "layer": "L3", "trigger": "实现涉及真实宿主进程生命周期的机制(watchdog 重启/优雅停机/编排)并准备验证时", "decision": "先给实现加 DRYRUN/模拟模式(只打日志不产生真实副作用),用死端口/假目标做失联模拟验证完整触发链(miss→门→拉起流程),再以单测锁门序语义;真实 kill/spawn 链只在用户按部署文档启用时执行,验证期间绝不对运行中的宿主做 kill", "rationale": "watchdog DRYRUN 失联模拟(端口 3999)暴露 stormGate 计数膨胀 bug(先记录后判门→暂停期仍累计),若直接对真实宿主实机验证会同时引入误杀风险与难以复现的偶发;模拟先行把状态机 bug 在零副作用下暴露", "evidence": "expr-2026-09-05_01-53-03(v3.9.0)S3:DRYRUN 模拟两轮(第一轮暴露 bug→attemptRestart 先判门再记录修复→第二轮计数稳定 3 不再膨胀);watchdog.test.js attemptRestart 用例;commit 8b16cfa", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [], "recordedAt": "2026-09-05T02:00:00.000Z" }, { "id": "L-2026-0905-029", "title": "Windows 计划任务启动器 .cmd 必须纯 ASCII(cmd.exe 按 ANSI/GBK 解析,UTF-8 中文注释会变成乱码命令导致后续 set 不执行)", "category": "tooling-trap", "layer": "L3", "trigger": "编写/修改供 Windows 计划任务或 cmd 直接执行的 .cmd/.bat 启动器,且内含注释时", "decision": "批处理文件注释与文案一律 ASCII;需要说明性文字放同名 .md 或 watchdog 日志;避免中文 UTF-8 写入 .cmd(LF 或 CRLF 均可,但字节必须 ASCII)。改后必测:cmd /c call 观察无 \"is not recognized\" 报错且环境变量/子进程参数正确", "rationale": "v3.9.3 dsh-web-watchdog.cmd 中文注释(UTF-8)被 cmd.exe 按 GBK 解析出乱码命令(\",随后启动\" is not recognized),goto/set 流程被破坏,DSH_WEB_ARGS 未设置 → watchdog 拉起宿主丢失 --trusted-host;重写纯 ASCII 后 env 传播恢复(宿主命令日志含 trusted-host)", "evidence": "C:\\Users\\Administrator\\dsh-web-watchdog.cmd(v3.9.3 ASCII 版);watchdog 日志 13:25:51 宿主命令含 --trusted-host;commit 25bbadd", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [ "L-2026-0903-004" ], "recordedAt": "2026-09-05T13:30:00.000Z" }, { "id": "L-2026-0905-030", "title": "测试文件必须严格 *.test.js 命名(套件 glob 只认 .test.js 结尾,否则静默漏跑)", "category": "tooling-trap", "layer": "L3", "trigger": "新增 node --test 测试文件、确认全量测试计数时", "decision": "测试文件统一 .test.js 命名(如 client-restart.test.js),禁 test-.js 形式;新增后核对全量计数(文件数=23、用例数递增),发现计数未增即查命名/glob", "rationale": "AutoIteration 实验 V2:test-client-restart.js 不以 .test.js 结尾被 test/*.test.js glob 静默跳过——单跑 2/2 通过但全量仍 152,险些漏测;改名 client-restart.test.js 后 154/154", "evidence": "expr-2026-09-05_13-58-07 V2;commit c1bbb1b;docs/AUTO-ITERATION-ANALYSIS.md 观察点 E", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [], "recordedAt": "2026-09-05T14:10:00.000Z" }, { "id": "L-2026-0905-031", "title": "AutoIteration 声明必须用机读 JSON 块(叙述式 {\"iterations\":N} 常不被 extractAutoIterDecl 解析)", "category": "collaboration-rhythm", "layer": "L3", "trigger": "向外部 AI 发起多版本(iterations≥2)任务、prompt 声明迭代参数时", "decision": "声明独立成 ```json 块({\"iterations\":N,\"autoDecision\":true,\"finalAcceptance\":\"…\"})而非中文叙述;发起后先读 steps.json 顶层确认 iterations 落盘,未落盘即修正后再推进(lesson 002 同族)", "rationale": "实验 V1:叙述式「声明:{\"iterations\":3,…}」首行未解析 → steps.json iterations=1,版间门无法多版推进;人工修正落盘后才 V1→V2→V3 自动演进", "evidence": "expr-2026-09-05_13-58-07(V1 修正记录);docs/AUTO-ITERATION-ANALYSIS.md 观察点 A;关联 L-2026-0903-002", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [ "L-2026-0903-002" ], "recordedAt": "2026-09-05T14:10:00.000Z" }, { "id": "L-2026-0905-032", "title": "宿主重启编排须单步幂等小命令且把树杀作为回合最后动作(多步宿主操作命令易被运行时中断→半状态→用户手动兜底)", "category": "engineering-action", "layer": "L3", "trigger": "由 agent 工具调用编排宿主重启(prepare → 停 watchdog → Start-ScheduledTask → 延迟树杀 host)等涉及杀进程/改守护的多步命令时", "decision": "拆成可验证的小步:① 触发 /admin/prepare-restart 单独一步确认 ready;② watchdog 重启(Start-ScheduledTask)单独一步确认单实例;③ 延迟树杀作为回合最后动作(Start-Process 独立进程),随后立即结束回合让用户/后续回合验证;每步前先只读盘点当前 PID 状态,避免重复执行半状态命令;依赖 watchdog 单例锁 + 首检保证任何手动兜底安全", "rationale": "v4.0 E2E 阶段多次「多步编排命令被运行时中断」(watchdog 已 kill 而 Start-ScheduledTask/树杀未执行)→ 宿主无人托管/未换代 → 用户手动启动 ≥3 次;watchdog 触发链本身可靠(日志实证 ~6 次自主拉起),不可靠的是 agent 单次长命令的原子性", "evidence": "AUTO-ITERATION-ANALYSIS.md §七(重启账目:watchdog 自主 ~6 / 用户手动 ≥3);宿主 PID 18008/18164 PPID=人工 launcher", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [ "L-2026-0905-028" ], "recordedAt": "2026-09-05T14:20:00.000Z" }, { "id": "L-2026-0906-033", "title": "宿主重启编排严禁先杀 watchdog:先保守护存活/新守护接管,再动宿主(先杀守护+编排中断=双死 585 分钟)", "category": "engineering-action", "layer": "L3", "trigger": "编排宿主重启/维护 DSH-WEB-Watchdog 计划任务(Stop/Start、taskkill watchdog、restart-now)时", "decision": "顺序铁律:① 先确认 watchdog 在跑(Start-ScheduledTask 幂等,勿 taskkill 运行中的 watchdog);② 触发 restart-now 或独立延迟树杀(不杀 watchdog);③ 操作后立即验证 watchdog 日志出现新行(重启记录)再继续;计划任务配 RestartOnFailure(RestartCount=5/RestartInterval=1m)+ ExecutionTimeLimit=0(防 72h 被杀)+ MultipleInstances=IgnoreNew,确保 watchdog 被强杀/崩溃 ~1min 内复活", "rationale": "2026-09-06 凌晨 585 分钟死局:编排命令(git push + taskkill watchdog + Start-Process restart-now)在 taskkill 之后被 harness 中断(会话解码 interrupted @ 2026-09-05T15:11:21Z turn81)→ watchdog 死、restart-now 未启动(日志 23:1x 零记录)、宿主随后 down → 双死无人拉起,浏览器 502 持续 585 分钟,只能人工重启", "evidence": "会话解码 interrupted-tool-result @ 2026-09-05T15:11:21Z;git 443426f @23:11:21 已推(命令跑到 git 完成)但编排未完成;dsh-web-watchdog.log 无 23:1x 行(restart-now 从未运行);用户浏览器 502/connection lost 持续 + health-check 无法连接", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [ "L-2026-0905-028", "L-2026-0905-032" ], "recordedAt": "2026-09-06T02:00:00.000Z" }, { "id": "L-2026-0907-034", "title": "批量并发审核必须先拆层:并发只 obtain 结论(不落盘不改状态),再串行 apply/原子打回统一落盘——共享 state 文件并发 writeStepState 会乱序覆盖丢结果", "category": "engineering-action", "layer": "L2", "trigger": "重构 /steps/auto-review 的 batchStepIds 为并发、或任何对同一 steps.json 状态的并发审核/写入场景(v9_3 并发批)时", "decision": "审核判定拆两层:obtainReviewVerdict(只走通道拿 verdict,零写盘零改状态,可安全并发)→ applyReviewOutcome(状态/notes/轨迹/webhook/落盘,串行)。批量 = 预检(串行,not_found/非 review/锁冲突直接记)→ mapLimit 受控并发 obtain(默认并发 2,env DSH_RELAY_BATCH_CONCURRENCY)→ 判定 anyRejected → 原子打回统一一次 writeStepState 或逐项串行 apply。严禁直接并发调 reviewOneStep(内部 writeStepState 同文件异步写 → 后写覆盖先写丢步审核结果)", "rationale": "v4.9/v2.0 实施:初版方案并发调 reviewOneStep 发现竞态——reviewOneStep 每步落盘同一 state 文件,JS 单线程 await 交错下两协程快照互相覆盖;拆层后并发只省审核请求时间(主耗时),落盘串行毫秒级,无竞态且原子打回语义保持(任一 rejected → 全批 rejected 含刚 approved)", "evidence": "lib/index.js reviewOneStep 拆层 commit(v4.9.0);全量 218/218 含 cc-channel 21 + alternatives 9;dialog-fallback 源码标记测试同步 v2.0 链断言(旧串『v3.0.1: Gemini API 失败 → web-gemini 网页通道(免配额)』已随链注释移除)", "confidence": "high", "status": "approved", "suggested": "in-protocol", "crossRef": [ "L-2026-0905-031", "L-2026-0906-033" ], "recordedAt": "2026-09-07T00:40:00.000Z" }, { "id": "L-2026-0907-035", "title": "审核上下文长文截断必须尾部优先(tailClip):三方轨迹时间正序、最新证据在末尾,头截 4000 会丢打回意见/重提证据/仲裁记录 → 审方基于旧状态误拒", "category": "engineering-action", "layer": "L2", "trigger": "长 trace / 长 notes / 多次打回重提的 expr 审核(buildReviewPrompt trace 段、cc 派发 notesText/traceText、alternatives trace)——凡给审方的上下文含时间序列文本且需截断时", "decision": "时间正序的上下文(三方轨迹、notes 追加日志)一律尾部优先截断:tailClip(s,n) 取末尾 n 字符 + 前置省略说明;头部截断只适用于固定短文本(任务记录、record)。审核 prompt 内注明「若超长已省略前部,只显示末尾最新」。lib/index.js 三处(buildReviewPrompt trace、runCcReviewTask cc 派发 notesText/traceText/artifactsSummary、alternatives trace)v4.9.1 已修", "rationale": "expr-2026-09-06_16-32-52 v9_4 事故:验证收口步证据全在 trace/notes 尾部(watchdog 重启实证、218/218、understand 报告、外部 AI 仲裁 approved),但审核上下文 clip(trace,4000) 取头——trace 已超长时尾部全丢 → Swarm Refactoring-Architect 三次空意见拒 + Claude 通道两次基于「未见仲裁/未见 approved 记录」旧状态误拒(它引用的上下文停在第三次重提前)。修复 tailClip 后审方才能看到仲裁与完整证据", "evidence": "commit 7578cc5(v4.9.1-fix);五轮误拒记录:Swarm×3(空 findings)+ cc×2(引用旧状态);外部 AI 仲裁 expr-2026-09-06_17-50-14 approved(channel=dialog-fallback)", "confidence": "high", "status": "approved", "suggested": "in-protocol", "crossRef": [ "L-2026-0907-034", "L-2026-0906-033" ], "recordedAt": "2026-09-07T02:10:00.000Z" }, { "id": "L-2026-0907-036", "title": "树杀宿主重启 = 断 harness 会话通道,续跑只对插件状态机留痕、不自动续接 agent 回合——编排后不能承诺\"自动续接\",必须明示需用户/goal 轮触发", "category": "engineering-action", "layer": "L2", "trigger": "把宿主重启(kill-host/restart-now/taskkill 树杀)编排为回合最后动作,且期待后续自动推进(bootResumeScan resumed、goal 自动轮、job 通知)时", "decision": "区分两层续跑:① 插件状态机续跑(bootResumeScan:resumed+restartCount+bootId 打戳)——宿主重启后**必然自动发生**(已三次实证 resumed=1);② agent 回合续接(新回合注入)——依赖 goal 自动轮或用户输入,**两者都可能缺席**(2026-09-07 事故:重启实证成功后 6 小时无任何自动轮)。铁律:把\"验证重启结果/收口\"放在重启**前**完成或拆到独立回合;必须编排重启收尾时,明确告知用户\"重启后需发任意消息(或等 goal 轮)触发续接\",不得承诺自动续接;若重启后无下文,新回合第一件事是核对 /health-check resumed 与目标状态,不假设任何工作已自动完成", "rationale": "2026-09-07 02:58 kill-host 重启实证成功(resumed=1 expr 被自动检测、restartCount 0→1、bootId 打戳),我最后消息承诺\"由机制自动续接\"——但此后 6 小时无 goal 轮无通知,用户提问才恢复。根因:harness 会话与 dsh 宿主是两套通道,宿主重启只保证插件状态机续跑;agent 回合续接无 sessionId 注入渠道(本 expr sessionId=null)+ goal 自动轮未在该窗口触发", "evidence": "resume-demo.log:02:56 编排 → 02:57 kill-host PID 20084 → 02:58 宿主 up bootId=mtq6bk7n-4b442b91 resumed=1 exprs=expr-2026-09-07_05-00-00;用户 6 小时后提问才恢复回合", "confidence": "high", "status": "approved", "suggested": "in-skill", "crossRef": [ "L-2026-0906-033", "L-2026-0907-035" ], "recordedAt": "2026-09-07T09:00:00.000Z" }, { "id": "L-2026-0907-037", "title": "等待/假死问题的机制级解:①杀宿主只能后台 job(同步杀被 harness 中断,3 实证)②job 不 wait 句柄,改同消息同步 probe 查盘 ③无介入续跑靠 expr sessionId 落盘(DSH_SESSION_ID)自动注入唤醒 ④回合结束前自检未竟工作禁预告式结尾", "category": "engineering-action", "layer": "L1", "trigger": "需要重启宿主(kill-host/restart-sync)、启动后台 job 后续接、宿主重启后期待自动续跑、回合结束前(是否还有未竟工作)", "decision": "四步铁律:① harness 内杀宿主**只能用 run_in_background 后台 job**(同步 taskkill/kill-host 必被中断——工具调用与 3080 有连接,kill 即断;3 次同步 kill 全中断、3 次后台 kill 全成功实证);② job 启动后**同一消息内不要 wait job 句柄**(跨回合必失效 + UI 悬挂 running)——改为同消息发同步 probe(Start-Sleep 65 + /status 或 /health-check)查盘上结果(uptime 归零/新 bootId = kill 已执行、watchdog 自愈拉起);③ 无介入续跑:expr 落盘 sessionId=DSH_SESSION_ID(harness 环境变量)→ 宿主重启后 bootResumeScan 自动 wakeMainAgent 注入「宿主自愈重启·自动续跑」消息(零用户输入;实证 2 次:唤醒消息自动到达);心跳(机制 b,15min 周期)兜底无重启场景的待办提醒;④ 输出最终文字前自问「还有未竟工作吗」——有就同一回合继续工具调用,禁止「接下来做 X」预告式结尾", "rationale": "2026-09-07 一天内 6+ 次「停在后台 job / 空等 6h / UI 悬挂」复盘:根因是 run_in_background 句柄跨回合失效 + 回合以预告式文字结束(幻想自动续接)+ 无 sessionId 注入渠道。机制级解而非自律:kill 后台化(环境约束)、验证盘上化(绕开句柄)、续跑注入化(sessionId 落盘 + 宿主自动唤醒)、回合自检(行为约束兜底)", "evidence": "3 次同步 kill 中断 + 3 次后台 kill 成功(watchdog.log);唤醒消息自动到达 2 次(expr-2026-09-07_07-00-00、expr-2026-09-07_13-30-00,用户零输入);037 结构模式演示 kill 后台 job + 唤醒自动到达;skill §5 已固化(后台 job 铁律/回合自检铁律/037 结构模式)", "confidence": "high", "status": "approved", "suggested": "in-runbook", "crossRef": [ "L-2026-0907-036", "L-2026-0906-033" ], "recordedAt": "2026-09-07T14:00:00.000Z" }, { "id": "L-2026-0909-038", "title": "cc 任务 outputDir 契约根因:buildReviewTask 曾硬编码绝对路径(/mnt/d/cc-tasks/tasks//out)致每个 claude-code 审核任务被 v2 门控 REJECT(.invalid/ 13 例实证)——schema 要求任务根内相对子路径", "category": "engineering-action", "layer": "L2", "trigger": "派发 cc 任务(buildReviewTask/cc-channel)、cc-watchdog 日志出现 REJECT invalid task、.invalid/ 堆积、claude-code 审核通道实际从未成功", "decision": "outputDir 字段必须相对(\"out\");绝对落盘路径(/mnt/d/...)只放 prompt 提示 Claude,不进 task.json 字段。REJECT 后人工复检闭环:CLI invalid list/revalidate/approve/recover/clean(未 approved 拒删)。", "rationale": "v2 schema validateTask 对 outputDir 做越界硬校验(禁绝对路径/..),绝对路径产物必然 REJECT——顶层校验器与底层派发必须同构,字段语义按 schema 而不是按\"给 Claude 看\"设计", "evidence": "expr-2026-09-09_12-30-09 s2v5_3(commit e7a992b);D:/cc-tasks/queue/.invalid/ 13 例;docs/task-schema-v2.md §10", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-09T15:42:48.261Z" }, { "id": "L-2026-0909-039", "title": "宿主重启后延迟投递噪声:/ask agentWoken 落盘的 aux expr(评审快照,isTest=false/status=open)会被 bootResumeScan/心跳当活跃任务反复唤醒;heartbeat 陈旧信号按阶段分批延迟送达(V3 批→V4 批→V5 批递减序列)", "category": "engineering-action", "layer": "L1", "trigger": "宿主重启后连续收到手动 handoff(expr-*.md 指向 aux 快照)与心跳自查消息,报主 expr 有已 approved 步骤待审", "decision": "① aux 评审快照(/ask 落盘、步骤已 restructure 并入主 expr)执行逻辑归档:置 isTest=true + status=done + finalized=true + archivedNote(v4.7.0 isScanEligible 语义,等效逻辑归档——插件 fs 无删除 API);② 收尾期收到此类延迟消息:实盘核对 steps.json(allApproved/finalized)+ /health-check heartbeat.found 判定为陈旧投递,trace 留痕即可,不重复动作;③ 别为每条噪声重启宿主清队列(会引入新一轮延迟投递)。", "rationale": "wake queue 是投递即删还是保留待投递取决于宿主实现;实测证明重启会重放历史 wake。归档标记(isTest+done)是停止扫描唤醒的机制级解,比逐条人工确认可扩展", "evidence": "expr-2026-09-09_12-49-39/13-03-17/13-57-25/14-37-38/14-46-35/14-53-11 归档 + 15:0x-16:0x 连续 10+ 条延迟 handoff/心跳;planResumes 复扫 0 计划", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-09T15:42:48.261Z" }, { "id": "L-2026-0909-040", "title": "三副本同步纪律:开发源 lib 改动 ≠ 宿主生效——运行副本(profiles\\web\\node_modules\\dsh-web-relay)与工作副本(web-relay\\dsh-web-relay 镜像)需显式同步;宿主跑的是运行副本旧代码直到 restart-now", "category": "engineering-action", "layer": "L2", "trigger": "改 lib/index.js 等运行时代码后调新端点/新行为发现不存在(如 POST /steps/v2-check 404)、v2 校验器在宿主侧不生效", "decision": "① 改运行时代码后:同步 dev→run copy(lib/bin/cordis.patch.yml/package.json,certutil hash 验证 IDENTICAL)→ watchdog restart-now 重启宿主(bootResumeScan 自动续跑唤醒);② 收口时同步 dev→work copy(lib/scripts/test/docs/skills/bin 全量镜像 + 删 stale);③ scripts/docs/test 不进运行副本(files 白名单),cc-watchdog 直接引用开发源 scripts 路径——运行副本只需 lib/bin/cordis/package.json。", "rationale": "三副本架构(源→运行→工作镜像)职责不同:运行副本是宿主加载实例需重启生效,工作副本是发布镜像需全量同步;V1-V5 曾全部只在开发源、宿主跑旧代码未被发现(审核走 dialog 降级链未触新端点)", "evidence": "expr-2026-09-09_12-30-09 s2v5_5(run copy 缺 task-schema-v2.mjs/index.js 旧版 hash DIFF → 同步后 IDENTICAL + restart 实弹 v2-check)", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-09T15:42:48.261Z" }, { "id": "L-2026-0910-041", "title": "静默失败三态链:content 层 resolve 空值(return '' 而非 throw)+ 消费者层只认成功分支(只打日志不上报终态)= 服务端任务永久 processing,上游白等满超时(web-gemini 16/16 实测)", "category": "engineering-action", "layer": "L2", "trigger": "跨进程/跨组件的异步任务链路(桥接/队列/扩展↔服务端)出现「任务开始执行但永不结束」;上游只能等满超时;排查时 /stats 缺失败计数只能靠 total-其余 反推", "decision": "① 每层收敛为**两态不变式**:产出层「要么 resolve 非空有效值、要么 throw 带诊断」(禁止 return 空值);消费层「要么上报成功、要么上报失败」(禁止只打日志的第三态);② 服务端状态机自带超时兜底终态(不依赖消费者行为);③ /stats 等可观测端点必须覆盖全部状态枚举(含 failed);④ 上游判定必须识别 failed/停滞并早退,不白等满超时。", "rationale": "「resolve 空值」在 JS 语义上不是错误(无 exception),但业务上等于失败——消费层的 `if (resp.answer)` 真值判断会把空串与「没响应」一起吞进无操作分支,形成无日志、无终态、无告警的静默泄漏;单测/类型系统都难以捕获,只有端到端探活 + 反向推算统计才能发现", "evidence": "expr 相关:cc 任务 wg-gap-analysis(Claude 分析 248 行 + 主 agent 交叉验证一致);dsh-web-gemini-ext background.js/content.js/bridge-server.mjs 三处修复;lib/bridge-poll.js + 11 测试;docs/web-gemini-diagnostics-2026-09-10.md;实测 /stats total=16 全非成功态、探活任务 190s+ processing", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T12:02:32.380Z" }, { "id": "L-2026-0910-042", "title": "混合架构「默认化」的正确形态:三方协作可默认、cc 派发须条件默认——判定规则要固化成可调用纯函数(硬边界优先于收益),并配降级率审计 + 双通道交叉校验两道治理门", "category": "collaboration-rhythm", "layer": "L3", "trigger": "考虑把\"混合架构 + 三方协作\"从按需启用升级为默认运行方式;或判断某类任务该不该派给 Claude Code;或收口时怀疑\"审核是否被静默降级\"", "decision": "① 三方协作机制(Step List 状态机 + 审核降级链 + 轨迹)适合默认;混合架构(cc 派发)条件默认——硬边界(需 GUI/浏览器、需跨会话状态、需主 agent 专属工具、预估>900s)优先阻断,豁免(≤1 文件小改、纯配置探路),启用(≥3 文件实现、静态诊断、代码评审、iterations>1、alternatives>1);规则固化为 lib/triage-route.js 纯函数 + POST /route/decide 入口,禁止凭感觉派发;② 配降级率审计(lib/review-audit.js:dialog 占比>30% 告警,超阈值且外部通道 0 成功=risk);③ 配双通道交叉校验(lib/cross-check.js:仅 importance=high 且显式开启,冲突升级人工、通道不可用与结论冲突严格区分)。", "rationale": "两方独立征询(外部 AI web-gemini + Claude Code 本体)均判 conditional——分歧点不在\"该不该用\",而在\"什么条件下用\"。cc 通道的 4-8 分钟固定开销与四条硬边界(900s/无--resume/白名单三工具/无 GUI)决定了它不该是全局默认;而默认化最大的风险不是能力不足,而是\"外部通道连续不可用时静默降级到无工具内部模型,形成形式上 approved 但实质审查强度下降\"——必须靠审计与交叉校验把它变成可见信号", "evidence": "expr-2026-09-10_12-18-24(P0-1/P0-2/P1-1/P2-1 四步全 approved + finalize);cc 任务 hybrid-default-advisory(Claude 建议书 24KB/verdict.json);外部 AI 征询 expr-2026-09-10_12-18-24(web-gemini,27s 无降级);lib/triage-route.js(16 测试)+ lib/review-audit.js(11 测试)+ lib/cross-check.js(11 测试);docs/CC-HYBRID.md §7-§9;实证数据:AutoIteration dialog 13/23=57%、cc 审核修复后 90s 成功(rev-1a08b460fc2)", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T12:34:22.524Z" }, { "id": "L-2026-0910-043", "title": "宿主重启导致主 agent 回合卡死的根因:工具进程是宿主的直接子进程 + watchdog 用 taskkill /T 杀整棵树 → 执行重启的命令自身被杀、结果永不返回。修复=信号解耦(写文件后立即返回,由独立进程树的 watchdog 执行)", "category": "engineering-action", "layer": "L1", "trigger": "需要重启宿主;或出现「工具调用被中断/回合卡住、必须用户发消息才能继续」的现象", "decision": "重启宿主**一律用 `node bin/watchdog.mjs request-restart [delaySecs]`**:该命令只写 bin/restart.request.json 后立即返回(工具调用正常落地),由常驻 watchdog(Task Scheduler 独立进程树)在后续 tick 执行 prepare→taskkill /T→拉起新宿主。**禁用**直接同步调用 restart-now / kill-host / restart-sync 作为首选(它们会在树杀时把正在执行命令的工具进程一并杀死 → 回合卡死)。命令返回后本回合即可正常收尾,重启后由 bootResumeScan(activeSteps + bootId 变化 → wakeMainAgent)自动唤醒续跑(前提:expr 已落盘 sessionId)。", "rationale": "taskkill /T = 杀指定进程及其整棵子树;主 agent 的工具进程由宿主 spawn,故属该子树。这不是\"任务停下来\",而是\"重启动作与执行者同树被杀\"的设计缺陷——只要把\"发起重启\"与\"执行重启\"解耦到不同进程树即可根治,无需改 harness", "evidence": "进程父链取证(powershell→宿主→watchdog→cmd→wscript→svchost/Task Scheduler);bin/watchdog.mjs request-restart + checkRestartRequest;端到端实测 2026-09-10 19:02-19:03Z:返回 exit=0 → 4s 后宿主 5764→16860 → resumeQueuedAt=19:03:04Z 自动唤醒 → watchdog 7592 存活;expr-2026-09-10_18-42-03 stab1_1", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:04:39.135Z" }, { "id": "L-2026-0910-044", "title": "web-gemini 稳定性三层兜底与\"取任务/回传\"可观测:扩展无 Gemini 标签页时不取任务(任务永久 pending);sendMessage 可能既不 resolve 也不 reject;区分轮询心跳与任务认领才能定位", "category": "engineering-action", "layer": "L2", "trigger": "web-gemini 通道 /ask 或审核长时间无响应;需判断是\"扩展没在轮询\"还是\"取了任务不回传\"", "decision": "① 每一层都要有兜底终态:宿主侧待认领早退(pendingSince 超 60s → unavailable)、服务端 sweep(pending 60s / processing 120s → failed 带诊断)、扩展侧 sendMessage 超时(70s → NO_RESPONSE 上报)。② 可观测要区分两个指标:lastPollAt/pollCount(轮询心跳,反映 SW+标签页是否活着)与 lastClaimAt/claimCount(真正认领任务,反映扩展是否在工作);只统计后者时\"每秒轮询\"会被误读成\"高频认领\"。③ 记住外部依赖:Chrome 无 gemini.google.com 标签页时,扩展的 pollOnce 直接 return,通道静默不可用——这是首要排查项。", "rationale": "该通道的三次事故各不相同(扩展不响应 / sendMessage 挂起 / 无标签页不取任务),但共同点是\"上游只能靠超时感知\",且初版可观测指标语义错误掩盖了真因。逐层兜底 + 双指标可观测把它们从\"白等 300s\"变为\"秒级诊断 + 提前降级\"", "evidence": "expr-2026-09-10_18-42-03 stab1_2;实测:桥接强杀 5s 自愈、t0001 132s 被服务端 sweep 判 failed、poll=0 正确指示无 Gemini 标签页、claimCount 299 暴露轮询/认领语义混淆;lib/bridge-poll.js(pending/processing 双阈值)+ bridge-server.mjs(sweep 双状态 + 双指标)+ background.js(sendMessage 70s 超时)", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:04:39.135Z" }, { "id": "L-2026-0910-045", "title": "cc 任务范围控制:900s 超时白耗配额的教训 + 范围评估器(校准要靠实测反推,且要防\"描述性编号列表\"假阳性)", "category": "engineering-action", "layer": "L2", "trigger": "派发 Claude Code 任务前;或 cc 任务超时失败(exit=124)无产物", "decision": "派发前用 `lib/task-scope.mjs` 的 assessTaskScope 预检:>600s 判 too-big(必须拆分,用 recommendSplit 出拆分块)、>420s 判 risky。经验阈值:单任务 ≤2 产物 + ≤5 个实质问题 + 不要求\"核实源码/多方案评估/出补丁\"这类深度动作。**校准必须用实测反推**(v2 校验器实现 222s / 架构征询 258s / 代码诊断 480s / RCA 超时 900s),且要防两类假阳性——编号列表(验收点清单)被当成深度评估工作量(已加 MAX_EVAL_ITEMS_COUNTED 封顶)、以及\"问题多但回答简短\"被高估。", "rationale": "RCA 任务(5 问 + 4 方案 + 3 产物 + 要求核实源码给补丁)900s 超时且零产物 = 白耗 15 分钟 Pro 配额;而范围适中的任务全部成功。范围是 cc 通道唯一的硬约束(900s 不可协商、无 --resume 不能续),必须在派发前判定而不是失败后补救", "evidence": "cc 任务 restart-interrupt-rca(exit=124 超时无产物)vs stab1-3-scope-assessor(4.8min 成功)/ stab1-4-gate-regression(1.6min 成功);lib/task-scope.mjs + test/task-scope.test.mjs(16 用例,二次校准 4/4 实测基准通过);docs/CC-HYBRID.md §7", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:04:39.135Z" }, { "id": "L-2026-0910-046", "title": "用 shell 字符串拼接构造 JSON 必错:runner.sh 覆写 result.json 时把含双引号的 JSON 输出未转义塞进 reason → 产物非法 JSON → 宿主 JSON.parse 失败继续轮询白等超时(门控\"坏结果不逃逸\"的意图被自身缺陷抵消)", "category": "engineering-action", "layer": "L1", "trigger": "任何在 bash 里生成 JSON 的场景(写 result.json / 配置 / 上报负载);或门控写了\"失败结果\"但下游没反应", "decision": "**禁止**用 `echo '{\"k\":\"'\"$VAR\"'\"}'` 这类 shell 拼接生成 JSON。一律用可用运行时安全序列化:`node -e 'require(\"fs\").writeFileSync(process.argv[1], JSON.stringify({...}, null, 2))' `(或 jq -n --arg)。校验环节必须包含\"产物本身能否被 JSON.parse\"这一断言,而不只是\"字段名出现\"。", "rationale": "门控写出的失败结果若本身是坏 JSON,下游(宿主 pollTaskResult)解析失败会 continue 继续等待 → 表现成\"门控失效 + 白等超时\",比不写结果更糟(掩盖了真因)。该缺陷由测试脚本(cc 任务 stab2-2-bash-gates)在复现 bash 胶水时发现,此前长期潜伏(触发条件=④门控真的拦截到不合格产物)", "evidence": "cc 任务 stab2-2-bash-gates 的 KNOWN-ISSUE;runner.sh 原 L40 vs 修后 L44(node 安全序列化);对照实验确认旧方式非法 JSON(Expected ',' or '}' after property value);scripts/verify-cc-gates.sh 重跑 PASS=16 FAIL=0 KNOWN_ISSUE=0;docs/cc-gates-checklist.md 附录", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:18:08.012Z" }, { "id": "L-2026-0910-047", "title": "归档 expr 要连步骤状态一起处理,否则心跳反复报\"待办\":归档测试快照只改 expr 级 status/finalized/isTest,步骤残留 rejected/review → 每轮心跳刷出已归档 expr(实测一轮 4 个)", "category": "engineering-action", "layer": "L2", "trigger": "批量归档测试/历史 expr;或心跳自查反复报同一批\"待办\"而实盘核对发现早已收口", "decision": "双保险:① 扫描层过滤——scanExprSignals 对 `isTest+done/finalized`(或任何 finalized)的 expr 直接不产生信号(与 resume-scan 的 isScanEligible 语义对齐);② 归档动作层——归档时同时把步骤状态处理干净(或明确保留为历史,并依赖①的过滤)。发现心跳噪音时的正确排查顺序:先实盘核对 steps.json 的真实状态,再判断是\"状态残留\"还是\"真待办\"。", "rationale": "心跳的价值在于\"不漏真待办\",但它若把已归档内容反复报出,主 agent 会消耗回合做重复核对(实测:一轮报 4 个已归档测试 expr + 1 个陈旧快照),长期会训练出\"忽略心跳\"的坏习惯,反而降低真待办的响应率", "evidence": "expr-2026-09-10_18-42-03 心跳消息(报 4 个已归档 expr);lib/heartbeat-scan.js 增归档过滤;test/heartbeat-scan.test.js +3 用例;实测回归 390/390", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:18:08.012Z" }, { "id": "L-2026-0910-048", "title": "续跑熔断阈值会误伤\"有意的多次重启\":restartCount≥2 即 paused,而回归验证/加载新代码常需连续重启(实测本 expr 被熔断,需手动 resume)", "category": "engineering-action", "layer": "L2", "trigger": "同一 expr 在短时间内连续跨重启(如:验证重启机制 + 加载新代码各一次)后,任务突然 paused 且 stopReason 为续跑熔断", "decision": "① 熔断是防死循环的正确设计(保留);② 有意的连续重启场景,主 agent 应在重启后主动检查 `restartCount`,若接近阈值则在收口前 resume(/steps/update action=resume)或换新 expr 做后续验证;③ 长期改进方向(未实施,记为候选):区分\"未完成续跑再次中断\"(真异常,应计数)与\"续跑后正常推进再重启\"(可重置计数)——例如 wake 成功后若 expr 有新的步骤推进则重置 restartCount。", "rationale": "熔断的语义是\"同一断点反复中断且无进展\",但当前实现只按\"跨 bootId 次数\"计数,未区分是否真有进展;在主动做重启类验证时会被误伤,表现为\"任务莫名 paused\"(排查成本高:需读 stopReason 才知道)", "evidence": "expr-2026-09-10_18-42-03:restartCount=2 → paused(两次重启分别为 stab1_1 机制验证与 stab2_3 加载聚合代码),已 resume 恢复;/health-check restartStats 可直接看到该计数", "confidence": "medium", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T19:18:08.012Z" }, { "id": "L-2026-0911-049", "title": "部署形态交付(Chrome 扩展/计划任务/服务):实施目录 ≠ 运行时加载目录,须以运行时注册表确认加载路径,多副本哈希一致才算完成", "category": "engineering-action", "layer": "L2", "trigger": "交付物要被某个运行时\"加载/执行\"时(Chrome 未打包扩展、Windows 计划任务、常驻服务/守护进程),尤其是方案里给了 targetWorkspace / 安装目录,而机器上存在多个同名副本(开发目录 / 工作副本 / 历史目录)", "decision": "① 实施前先确认\"运行时实际加载哪一份\":Chrome 未打包扩展查 `%LOCALAPPDATA%\\Google\\Chrome\\User Data\\\\Secure Preferences` 的 `extensions.settings[].path`(location=4 即 LOAD_UNPACKED)与 `Preferences` 里 `file_data[\"manifest.json\"]` 记录的已加载 manifest 原文;计划任务查 `schtasks /query /v`;常驻进程查命令行 + cwd(`Get-CimInstance Win32_Process`)。② 产物落到运行时加载目录,并让 开发/运行/工作副本 三份 SHA256 全同;覆盖前逐文件 `.bak-presync-`。③ 收口核验必须包含\"重载后生效\"这一步:不能因为语法检查/单测通过就宣布完成。④ 通道/服务类组件注意\"两侧版本错配\"——bridge 生效不代表扩展生效(各自独立的加载来源)。", "rationale": "未打包扩展的加载路径是 Chrome 侧持久化注册表值,与方案里写的 targetWorkspace、与\"最近被编辑的目录\"都可能不同;一旦错配,代码正确但运行时行为零变化,表现为\"改完没效果、重载也没效果\"的静默落差,排查成本极高(现象像功能没实现,其实是没加载)", "evidence": "expr-2026-09-10_20-29-05 收口核验:Step1/Step2 加固落 `D:\\DSH\\dsh-web-gemini-ext`(v0.4.0),而 Chrome 加载 `D:\\dsh relay test\\dsh-web-gemini-ext`(Preferences.file_data 记录 version 0.3.0);Bridge 侧因由 `D:\\DSH` 下 watchdog 拉起而生效,形成两侧版本错配;已同步三副本(备份 `*.bak-presync-20260911-051101`,SHA256 全同)并在三方轨迹追加核验条目", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-10T21:12:09.422Z" }, { "id": "L-2026-0911-050", "title": "同一能力的多条调用路径必须传\"同源开关\":新增/新接入的请求级参数在单步、批量、降级等每条路径都要同步,否则该路径上的安全网静默失效(漏参型缺陷)", "category": "engineering-action", "layer": "L2", "trigger": "给某能力加请求级开关或新增第 N 个参数时;该能力存在多条调用路径(单步 / 批量并发 / 降级分支 / 后台定时触发);实测只覆盖了其中一条路径", "decision": "① 加参数时立即 grep 该被调函数的**所有调用点**(本例 `obtainReviewVerdict` / `reviewOneStep`),逐点确认传参;② 写**源码级接线回归测试**(括号配对提取每个调用点的实参文本,断言都含该开关,且由同一请求级表达式驱动),因为纯函数单测无法发现\"调用点漏参\";③ 做**反向验证**(人为去掉该参数跑一次,确认用例 FAIL)——否则用例可能恒真;④ 文档承诺(如\"请求级开关\")必须与实际每条路径一致,不一致即静默放行", "rationale": "漏参型缺陷的表现为\"功能没生效但无任何报错\":被调函数收到 undefined 后按默认关闭处理,单步路径实测正常会让人误判功能已生效;本例丢掉的偏偏是\"两条通道结论冲突 → 升级人工\"的安全网,属于静默放行风险最大的那类", "evidence": "claude-code 审核 rev-1a08b804718(Step xc3)REJECT:`lib/index.js:3547` 批量分支(batchStepIds)只传 6 参、遗漏 `enableCrossCheck`,而单步路径 `lib/index.js:3622` 正确传第 7 参 → `shouldCrossCheck(step, undefined)` 恒 false;该缺陷在上一轮收口时被误记为\"已修\"(git log -S 证实从未提交修复,工作树实证:批量调用点仍缺该参)。2026-09-11 修复:批量路径补传 + `test/cross-check.test.js` 两条源码级接线用例(反向验证为 13 pass/1 fail),全量 419/419 绿", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T00:29:04.729Z" }, { "id": "L-2026-0911-051", "title": "端到端实测会写入用户的真实环境:派发前必须先声明,且被测系统自身要有\"不覆盖用户数据\"的设计;审核通过 ≠ 真实可用", "category": "engineering-action", "layer": "L1", "trigger": "为了验证通道/集成\"真的能跑\",直接向用户真实环境(浏览器页面、生产账号、真实会话)派发测试任务时", "decision": "① 实测前在同一回合明确声明\"将向你的 X 写入测试数据\"再执行,不做静默写入;② 被测链路必须具备占用检测(如输入框非空即拒绝),并保证失败后清理自己写入的残留;③ 验收标准要分\"代码级/单元级\"与\"实盘级\"两档,只有实盘级才算通道可用;④ 代码/审核通过 ≠ 真实可用——本 expr 收口时三步全 approved,实盘一跑就暴露发送链路失效。", "rationale": "三方协作的审核看的是代码与自述证据,无法覆盖\"真实 UI 是否配合\";若把审核通过当成通道可用,就会在真正需要降级通道时才发现不可用。而向用户环境写入测试数据不声明,会直接破坏用户正在进行的工作(本次实测覆盖了用户正在输入的内容)", "evidence": "2026-09-11 实盘实测(bridge /create-task):扩展 v0.4.0 生效(authState=OK/isReady=true)、1.0s 认领、选择器命中,但 SEND_FAIL(发送按钮未识别、Enter 无效),78s 判 failed;同时 content script 覆盖了用户 Gemini 输入框中的内容(用户反馈\"我的输入没了\")。修复:v0.4.1 加 INPUT_BUSY 占用保护 + 失败清理残留 + 发送候选链/Enter/requestSubmit 三路 + 失败 DOM 自诊断 + SEND_FAIL 触发 reload 自愈", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T01:32:26.521Z" }, { "id": "L-2026-0911-052", "title": "Chrome 扩展重载后,已打开页面里的旧 content script 会变成孤儿:新后台 sendMessage 收不到响应(NO_RESPONSE),需刷新标签页或依赖自愈重试", "category": "engineering-action", "layer": "L1", "trigger": "重载未打包扩展(chrome://extensions → 刷新)后立刻派发任务;或扩展更新后已有标签页仍持有旧版 content script", "decision": "① 重载扩展后**先刷新相关页面标签**(或让自愈机制触发一次 reload)再验收;② 派发侧对 NO_RESPONSE 要做\"重载标签页 + 重试一次\"的自愈(已有实现,节流 60s),不要把它当\"通道不可用\";③ 判据上区分三类失败:SEND_FAIL(页面在、按钮找不到)/ NO_RESPONSE(content script 不在)/ EMPTY_REPLY(发送成功但没抓到回复)——三者处置不同。", "rationale": "MV3 扩展重载会重建 Service Worker 与扩展上下文,但已注入页面的 content script 不会自动更新/复活,此时后台的 tabs.sendMessage 直接失败;若把它误判为通道故障,会在\"扩展刚升级\"这一最常见的验收时刻得到错误结论", "evidence": "2026-09-11 复测:重载 v0.4.1 后第一次派发 t0001 → 6s 内 NO_RESPONSE(Tab 1635932287 content script 未注入);自愈 reload 后第二次 t0002 → done/answer=\"收到\"(约 50-60s),web-gemini 通道首次端到端成功", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T02:09:47.049Z" }, { "id": "L-2026-0911-053", "title": "用户视角 ≠ 系统实况:pinned 后台标签页\"看不见\"会导致误判功能失败;判定要靠系统自报的可观测字段,并主动补埋点而不是靠肉眼", "category": "engineering-action", "layer": "L1", "trigger": "用户报\"某自动化动作没发生\",但系统侧指标(此处 isReady=true)显示正常;或某能力只有\"外部可见的副作用\"可作判据(标签页、窗口、通知、文件)", "decision": "① 先别改代码,先造**系统自报字段**(本例 tc=标签页数 / tinfo=pinned+active+windowId+url / ens=补建结果)把黑盒变白盒;② 判定以字段为准,不以\"用户肉眼没看到\"为准(pinned 后台标签页、最小化窗口、第二窗口都会被忽略);③ 无副作用分支也必须**照常上报**(原实现无标签页时直接 return 不轮询 → 外部连\"它在跑\"都看不到);④ 粘性状态字段(缺省不更新)必须配合\"明确上报缺失态\",否则\"没有\"与\"没上报\"无法区分", "rationale": "自动化动作经常在\"用户看不见的地方\"发生(后台标签页/最小化窗口)。用外部可观察副作用当唯一判据,会把\"没看见\"误判成\"没发生\",进而去修一个本来就是好的功能——本例中补建其实已稳定工作 4 小时", "evidence": "2026-09-11 实测:用户报\"无标签页、无补建\",但 bridge 收到 isReady=true;加 v0.4.2 埋点后 /stats.worker 显示 tabCount=1、tabInfo=p-w1635932285/app/f8e98a8f44d5836a(pinned 后台,存活 4h);用户关闭后 tabCount=0→25s 后 ens=created 补建成功", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T13:06:25.002Z" }, { "id": "L-2026-0911-054", "title": "自研健康探针 + 敏感阈值 = 自杀式重启风暴:探针端点必须 O(1),阈值必须容忍冷启动与机器繁忙", "category": "engineering-action", "layer": "L2", "trigger": "自研 watchdog/守护进程按\"探针失败 N 次 → 重启进程\"策略守护宿主/服务;探针端点自身需要读盘/聚合/发起 HTTP", "decision": "① 探针端点必须**幂等且 O(1)**:聚合类结果加 TTL 缓存 + 后台刷新,绝不在探针路径上做全量扫描;② 阈值要给足余量(本例探针超时 2000→4000ms、连续失联 3→6 次≈30s),并区分\"进程真的没了\"与\"只是响应慢\";③ 启动日志必须打印生效参数(本例 \"miss ≥ 6 重启\"),否则\"参数改了没生效\"无从验证;④ 出现重启风暴时先停守护进程止血,再修根因(停守护不影响宿主)", "rationale": "探针失败 → 杀进程 → 进程重启后冷启动更慢 → 探针更易失败,这个正反馈会把偶发抖动放大成持续风暴(本例单日拉起宿主 18 次、判定重启 37 次,主 agent 回合被反复打断,用户观感是\"Agent 老是掉线\")", "evidence": "2026-09-11 实测:/dsh-web-relay/health-check 稳定耗时 ~1520ms(全量 expr 扫描 + 两次 bridge HTTP)vs 探针超时 2000ms;watchdog 日志 \"miss 1/3→2/3→连续 miss 3 次→重启宿主(err=timeout)\" 每 10-20s 一次;修复后 health-check 加 15s TTL 缓存、阈值 6/4000ms,日志变为 \"miss 1/6 → 宿主恢复\",零重启", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T13:06:25.004Z" }, { "id": "L-2026-0911-055", "title": "定位\"某服务偶发卡顿\"先用同机第二服务做对照(一次定性到进程级),再谈机器级原因;排除类改动必须前后测量,无收益即回滚", "category": "engineering-action", "layer": "L1", "trigger": "某本地服务/进程响应偶发变慢(探针超时、请求 1-3s),但 CPU/磁盘/内存计数器看起来正常,怀疑杀软/磁盘/其他软件时", "decision": "① 同一时刻并行采样\"可疑服务\"与\"同机另一个独立进程的轻端点\",比较延迟分布——对照服务始终毫秒级 ⇒ 问题是**该进程内部**的,与机器/磁盘/杀软无关,省掉一轮轮无效猜测;② 再做\"是否与某个周期性任务相关\"的相关性检验(如把请求延迟与缓存年龄/定时器周期对齐);③ 采样要覆盖\"我活跃\"与\"我空闲\"两种状态(本次空闲窗口 40 次全快、活跃窗口 23% 慢,差异就是线索);④ 任何\"降低安全/权限\"的排除类改动,改前测一次、改后测一次,没改善就立刻回滚并说明。", "rationale": "偶发卡顿最容易让人去打\"杀软/磁盘/网络\"这些常见嫌疑,但计数器往往看不出问题,于是改动一个接一个、每次都要重启验证,成本极高。对照法是几分钟内出结论的判别实验:把\"机器级\"和\"进程级\"一刀切开。另外把带安全成本的改动当\"顺手优化\"是负收益——实测 Defender 排除对本例 0 改善。", "evidence": "2026-09-11 实测:宿主 3080 P90=1908ms(23% 慢)而 bridge 8899 P90=4ms(0 慢),慢样本中宿主 2503ms 时 bridge 11ms → 判定宿主进程内停顿;Defender 排除前后 8.5%→9%(无改善)已回滚;空闲窗口 40 次采样全部 23-90ms,指向回合处理期内部工作(会话落盘/GC)", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-11T17:56:43.404Z" }, { "id": "L-2026-0912-056", "title": "测量工具用错会造出假 bug:复算/诊断必须使用运行时的真实参数与真实渲染路径,否则会把自己的参数错误当成系统缺陷(同一天内犯两次)", "category": "engineering-action", "layer": "L1", "trigger": "用离线脚本复算某个纯函数、或用 dump/诊断命令推断\"配置是否生效\"时;结论与运行时观测不一致(如宿主报 0、复算报 4)", "decision": "① 复算前先从源码/运行态抄出**真实参数**(本例心跳实际是 staleMs=20min、maxAgeMs=48h,我用了默认值/自设的 6h+30d → 凭空多出 4 条\"待办\");② 用 dump 类命令判断\"用户配置是否生效\"前,先做**对照实验**(给一个已知项设值,看它是否出现在 dump 里)——本例 `dsh --dump-config` 根本不渲染用户层 config,我却据此宣布\"anthropic 配置未生效\",结论作废;③ 结论与观测冲突时,先怀疑测量方式,再怀疑系统;④ 宣布 bug 前把\"参数/命令 + 期望 + 实际\"三件套贴出来自检。", "rationale": "假 bug 的代价是双向的:既浪费一轮排查与改动(去修一个本来没坏的东西),又可能据此做出错误决策(例如因\"配置不生效\"而重写配置)。而\"用错工具\"的迹象很明确——结论与运行时不一致,只要不急着下判断就能发现。", "evidence": "2026-09-12 同一天两例:① 用 `--dump-config` 未出现 anthropic 段 → 断言\"patch 配置未生效\";对照实验(给 session-title 设 fallbackMaxWords: 9,dump 仍显示 5)证明 dump 不渲染用户层,结论撤回;② 用默认参数复算心跳 → 得 4 条待办,与宿主 found=0 冲突;改用运行时真实参数(20min/48h)复算 → 0 条,宿主行为正确", "confidence": "high", "status": "proposed", "suggested": "proposed", "recordedAt": "2026-09-12T12:35:09.579Z" }, { "id": "L-2026-0913-057", "title": "宿主换线/换版后浏览器白屏≠网络故障:SPA 外壳无 cache-control 被启发式缓存,旧外壳指向新线不存在的资源寻址(手机尤其明显,清站点数据即恢复)", "category": "tooling-trap", "layer": "L2", "trigger": "切换 dsh 版本线/端口/authority 后某个浏览器打开 GUI 一片空白(手机尤甚),而 curl 探针、token 303、静态资源都正常时", "decision": "① 先用「隐私标签 或 清该站点数据」做 30 秒判别——本次即以此定位;② 排查顺序固定为:传输与鉴权(303 Location:/ + dsh-auth cookie authority=目标域名)→ 带 cookie 首页 200 → 静态资源经 ts.net 与本机字节数一致 → 无 serviceWorker → 最后才怀疑缓存;③ 根因在 harness:dsh-host-frontend-static/lib/index.js L73 服务 SPA 外壳只写 content-type(无 cache-control/etag/last-modified),而 dsh-client-modules/lib/index.js L122/L866 给插件包带 `?rev=` + `public, max-age=31536000, immutable`——「外壳可被启发式缓存 + 资源按 rev 寻址」组合,换版后旧外壳的引用必然失配 → 白屏;④ 换线/换版后的验收清单里加一条「清目标浏览器站点数据」;长期修法是给 HTML 加 no-store(上游 issue)。", "rationale": "白屏的第一直觉是网络/端口/鉴权,但本次三者都已实测排除:tailscale 直连 7ms 且 tx 17.9MB(11.27MB 插件包确实下完)、serve 映射 443→3080 正确、宿主带 --trusted-host。同 origin 此前跑的是 rc.7 宿主,其产物布局与新线(/plugins/??…&rev=)不同,缓存旧外壳是唯一与「电脑正常、手机白屏」相符的解释,且清站点数据一次即验证。", "evidence": "2026-09-13:手机 https://win10-dt.taile618c2.ts.net/?token=… 白屏;实测 303 Location:/ + set-cookie dsh-auth-…(authority=win10-dt.taile618c2.ts.net)、带 cookie 首页 200/28,218B、插件包 11,268,207B 与 modules 包 18,683B 经 ts.net 与本机字节数完全一致、HTML 内 serviceWorker/127.0.0.1 计数为 0;清站点数据后恢复(用户确认)。服务端落点:dsh-host-frontend-static/lib/index.js L73(仅 content-type)、dsh-client-modules/lib/index.js L122/L866(immutable + rev)。", "confidence": "high", "status": "proposed", "suggested": "proposed", "crossRef": [ "L-2026-0911-049" ], "recordedAt": "2026-09-13T12:53:43.880Z" }, { "id": "L-2026-0914-058", "title": "新线 typert Remote 方法的尾参 signal 由 gateway 补:绕过 gateway 直连服务(如 apiProxy 兼容层)必须自己补,否则 undefined.throwIfAborted() → TypeError", "category": "tooling-trap", "layer": "L2", "trigger": "在 harness 0.1.5+(新线)上以 cordis 服务方式直连调用 TypertRemoteService 的 Remote 方法(自研 shim / 宿主内插件互调),或运行时出现 \"Cannot read properties of undefined (reading 'throwIfAborted')\"", "decision": "① 调用前用 `fn.length` 判声明元数:尾参为 signal(length>=2)时必须显式传一个永不 abort 的 AbortSignal(`new AbortController().signal`),与 gateway 行为对齐(dsh-api-gateway lib/index.js L397 NEVER_ABORTED_SIGNAL + L746 args.push(request.signal ?? NEVER_ABORTED_SIGNAL));② 错误信封回填诊断(声明元数 + 栈帧前几行),因为这类失败是普通 TypeError、没有 .code,现场只能靠 message 定位;③ 单测补一条『两参 controller 必收 signal』用例(源码级接线断言 + 行为断言)。", "rationale": "该报错看起来像框架缺陷,实为调用契约差异:typert gateway 按源码签名(descriptor.cancellation,即最后一个形参是否叫 signal)决定是否补参,而服务实现里的 `signal.throwIfAborted()` 是无条件执行的;绕过 gateway 直连就没有这层包装,signal 为 undefined 立即抛错。二者行为必须由调用方主动对齐。", "evidence": "2026-09-13 面板「唤醒主 Agent」失败:`gateway/internal: Cannot read properties of undefined (reading 'throwIfAborted')`。根因:dsh-api-session-controller/lib/index.js L2920-2923(SessionController,即 sessionController 服务)`prompt(request, signal) { signal.throwIfAborted(); return this.commands.prompt(request); }`——注意 L736 的 `async prompt(request)` 是另一个内部类 SessionCommandController,易误读为单参。修复:shim/dsh-apiproxy-shim v0.2.0 自适应补参 + selftest 10/10 + test/apiproxy-shim.test.js;修复后 handoff 唤醒成功且携带【主 agent 装配】与 lesson Top-K。", "confidence": "high", "status": "proposed", "suggested": "proposed", "crossRef": [ "L-2026-0911-050" ], "recordedAt": "2026-09-14T00:30:00.000Z" }, { "id": "L-2026-0914-059", "title": "收口状态以 steps.json 为权威:/steps/finalize 只回写 steps.json + 追加 trace,不更新记录 md 的 frontmatter status(人读 md 会误判未收口)", "category": "repository-knowledge", "layer": "L2", "trigger": "收口后核对「任务记录状态」,或有人阅读 web-relay/experiments/dsh-web-relay-.md 判断任务是否结束/是否需再次收口时", "decision": "① 判断任务终态一律读 `expr-.steps.json` 的 status/finalized/finalizedAt/finalSummary(面板同源,是唯一权威);② 记录 md 的 frontmatter `status` 可能长期停在 pending(现状实现缺陷,不代表任务未收口),不得据此重复触发收口或判定故障;③ 主 agent 的收口动作 = 追加 `## [主 agent]` 三方轨迹条目(可经 POST /dsh-web-relay/trace 落盘),不擅自手改插件状态文件;④ 若要修,应作为插件能力待办(让 finalize 回写 record frontmatter),而不是手工同步两份状态。", "rationale": "同一任务的状态有两份副本(steps.json 与 record md frontmatter),由不同代码路径维护:finalize 只覆盖 steps.json 并追加 trace。副本不同步会让人读 md 得出「未收口」的错误结论,从而重复收口或误报缺陷——这类『状态副本漂移』的排查成本远高于读取权威文件。", "evidence": "2026-09-13/14 expr-2026-09-13_16-14-12:面板收口后 steps.json = status done / finalized true / finalizedAt 2026-09-13T16:21:39.411Z / finalSummary【dsh-web-relay 任务收口】,而 D:\\ws2\\web-relay\\experiments\\dsh-web-relay-2026-09-13_16-14-12.md 的 frontmatter 仍为 `status: pending`;finalize 处理器实现见 lib/index.js L3906-3954(仅 writeStepState + appendTrace + 可选 wake)。", "confidence": "high", "status": "proposed", "suggested": "proposed", "crossRef": [ "L-2026-0903-002" ], "recordedAt": "2026-09-14T00:30:00.000Z" } ] }