# 诊断指标定义与阈值 > 状态:**22 项已实现、0 项排期、0 项尚未实现**(第二轮评估后 12→15;第三轮按「待优化报告」 > Phase 1 采入 DSH 实测数据源后 15→19;2026-10-15 决策 D4 实现 K1/K2 → 21; > 同日接入本地 MCP 服务器后实现 A1 → 22)。 > 本文件是**代码的事实源镜像** —— > 与 `src/diagnostics/index.mjs` 的 `REGISTRY` 不一致时以代码为准;冒烟测试里有 > "文档集合 == REGISTRY 集合"的断言(见 `scripts/smoke.mjs` 第 15 段)。 > 表中所有阈值都是代码内默认值,可被 `opts.*` 覆盖。 --- ## 分类学(5 大类 22 项) ``` C 成本类 token 过快 / 上下文膨胀 / 长会话疲劳 / 压缩可观测性 / 工具面成本 / 上下文注入量 E 执行类 死循环 / 反复失败 / 结果过大 / 计划脱节 / 冗余重复调用(实测新增) K 知识记忆类 跨会话失忆 / 已解决重新排查 N 环境安全类 命令反复失败 / 高风险操作 / 审批摩擦 / 重试频繁 A 资产闲置类 MCP 闲置 / skill 闲置 / 高频手动序列 / 子代理开销 / 辅助 LLM 开销 ``` **已移除:I 交互类**(I1 无用思考 / I2 需求反复澄清 / I3 输出不合偏好) 理由:中文启发式误报率高,且无对应的可执行处方,与"可信证据"定位冲突。 --- ## 实现状态 | 状态 | 指标 | |---|---| | **已实现(22)** | C1 C2 C3 C5 C6 C7 · E1 E2 E3 E4 E5 · K1 K2 · N1 N2 N3 N4 · A1 A2 A3 A4 A5 | | 排期中(0) | 无 —— A1 于 2026-10-15 实现(见下) | | 尚未实现(0,附原因) | 无 | **2026-10-15:A1(MCP 闲置)从"排期中"变为已实现 —— 配置驱动指标** 它此前无法判定的原因是**缺数据源**:判定要两个输入 ①配置了哪些 MCP 服务器、②窗口内调用次数, 而本机 `~/.dsh` 下没有任何 MCP 配置。用户接入本地 filesystem MCP server 后两个输入都有了, 于是实现并**用真实配置验收**(不是合成数据): | 输入 | 来源 | |---|---| | 服务器清单 | profile patch 里 `name: '@deepseek-ai/dsh-mcp-client'`(或 `-resources`)且未 disabled 的行 → `config.serverName` | | 调用次数 | 会话里工具名匹配 `mcp____` 的次数(DSH 的固定命名,见该插件 README「工具命名与共存」) | | 观察窗口 | 与 A2 同口径:`今天 − 首次会话时间`;不足 30 天只报"还差多久",**不下闲置结论** | | 证据 | **配置文件的行号**(`#L<行>`)—— `show` 能展开非 zstd 文件,所以照样可复核 | 测试/CI 用 `AGENT_CLINIC_PROFILE_PATCH` 指向语料自带的 patch(干净 runner 上没有 profile 目录)。 **决策 D4(2026-10-15):K1/K2 从"尚未实现"变为已实现** 用户选了较强的一档:把**规范化文本指纹(加盐 sha256)写入本地快照** `~/.agent-clinic/text-fingerprints.json`。隐私边界写在 `src/privacy/fingerprints.mjs` 文件头: 正文只在解析时**当场**哈希、不落盘不进报告;每台机器一份随机盐;短于 12 字符的文本不生成指纹; `agent-clinic fingerprints` 可查看、`--clear` 可删除、`AGENT_CLINIC_NO_FINGERPRINT_STORE=1` 可整体关闭 (关闭或快照损坏时,K1/K2 会带说明出现在"未检测到异常"里写明**未采集**,不假装没问题)。 | 指标 | 判定口径 | 数据源 | |---|---|---| | K1 跨会话失忆 | 同一指纹出现在 **≥3 个**不同会话(两次可能是正常追问,三次才是"没记住") | 本地指纹快照 + 本次扫描 | | K2 已解决重新排查 | 同一指纹在 ≥2 个会话出现,且**较晚**的会话在该消息之后有失败/重试信号,而**最早**那次之后没有 —— 软信号,只给"低"+措辞"疑似" | 同上 + `tool/result.is_error` / `llm/retry` | > K2 为什么是代理判据:日志里**没有**"这件事已解决"的显式信号,能观测到的最接近事实是 > "同一问题再次被提起、且又走进了失败/重试"。因此它永远只给低严重度、措辞必须保留"疑似"。 > 实测(合成语料):未加"最早那次之后不能有失败"这层收紧时,K2 一次报 8 条; > 收紧后是 1 条量级 —— 这是把"一直在排查"(E2/N4 已在报)与"当时看着好了、后来又翻出来"区分开。 **第三轮(待优化报告 Phase 1)采入的四类 DSH 实测数据源**: | 指标 | 数据源 | 为什么它比原先的估算可信 | |---|---|---| | C5 压缩可观测性 | `compaction/summary` / `compaction/prune` 的 `shadowedTokenCount` | C1/C2 是**推断**浪费,C5 是 DSH **自报**的实测释放量(本机实测 638,171 token) | | C6 工具面成本 | `request/header` 的 `tools` 数组长度与字节数 | 工具 schema 每个请求都要重发,此前从未被计量(本机实测 29,779 字符 ≈ 7,445 token/请求) | | C7 上下文注入量 | `agent/inbox/spliced` 的 `inserted` 体积 | 注入内容会留在历史里被重复计费(本机实测 158,393 字符) | | A5 辅助 LLM 开销 | `web/*-llm-request`、`session/title-llm-request` | 这些调用**没有 usage 记录**,此前完全不可见(本机实测 96 次) | > C5/C6/C7/A5 全部**零隐私代价**:只记数字(token 数、字节数、次数)。 > C6 是唯一记录工具名的(工具名是指令标识符,不是用户内容),用于回答 > "注册了哪些工具、哪些窗口内 0 次调用"。 **关于"尚未实现"的定性(第二轮评估 F1 更正)**: `K1`/`K2` **不是**"DSH 没有这个数据",而是本工具按隐私约束只记 `user/message` 的 `text_len`、不留正文。要做跨会话"你是否又在问同一个问题",需要改为记录**规范化文本指纹** (hash,可复原性为零)—— 方案未定,未排期。报告中的措辞已从"数据源缺失/不可实现" 改为"尚未实现(原因见下,都不是平台做不到)"。 `E3`、`N3`、`N4` 曾一度被列在这个"不可实现"名单里,第二轮评估用原始日志逐条证伪后实现: | 指标 | 旧理由 | 实测事实 | |---|---|---| | E3 工具结果过大 | "tool/result 只归一化 is_error,不含 content 长度" | 原文带 `data.message.content[].text`,**1116 条有正文、57 条 >8192 字符**;适配层已补记 `text_len`(只记长度) | | N3 权限审批摩擦 | "DSH 只有 approval/policy" | 实测 `approval/asked` 23 次 + `approval/decided` 23 次(按 `id` 关联) | | N4 API 重试频繁 | "不存在 llm/retry 事件" | 实测 `llm/retry` 携带失败码 / 重试序号 / 退避时长 | --- ## 指标明细(已实现部分) | 指标 | 判定 | 阈值 | 防误报 / 已知边界 | |---|---|---|---| | **C1** token 消耗过快 | 会话 `output / 请求数` > **其余主会话**单位请求 output 中位数 × 2 | `c1BaselineRatio=2`、`c1MinOutputTokens=20000`、`c1MinSessions=3` | 基线**排除子代理会话与 0 产出会话**,并**逐会话排除被判会话自身**(自己不该是自己的对照;对照样本不足时退回全体主会话并在摘要标注);有绝对量下限;结论明写"产出量大不必然是浪费"(第二轮评估 F12 重写;排除自身后基线更低→倍率更高→更敏感,示例 `[100,250,2000]`:含自身 250→8.0×,排除自身 175→11.4×) | | **C2** 上下文膨胀 | 末轮 input > 首轮 input × 10 且 cacheRead 占比 < 30% | `c2GrowthRatio=10`、`c2CacheShareMin=0.3`、≥8 条 assistant | 无绝对体量下限;吃缓存红利的真膨胀会漏(代码注释已自认) | | **C3** 长会话疲劳 | 单会话 USER_MSG + ASSISTANT_MSG 计数 > 200 | `c3MaxMessages=200` | ⚠️ **仍不看 compaction**(第三轮已具备数据源与 C5,待接线);压缩过的长会话仍会报 | | **C5** 压缩可观测性 | 单会话压缩次数 ≥3,报告**实测**释放量(summary / prune **分开列**)、摘要回吐比例 | `c5MinCompactions=3` | 回吐比例的分母是**摘要自己的释放量**(旧版用 summary+prune 之和,prune 多的会话会被系统性低估);释放量为 0 时不给该比例 | | **C6** 工具面成本 | 工具 schema 中位字节 ≥20000;另有"**本窗口未观察到调用**的工具 ≥5 个" | `c6SchemaChars=20000`、`c6MinDeadTools=5` | 4 字符≈1 token 是启发式(已标注);"未调用 ≠ 不需要"—— 注册是所有会话的并集,低频工具只是窗口里没遇到对应任务,结论里写明了(置信度 0.7) | | **C7** 上下文注入量 | 注入次数 ≥20 且注入字符 ≥100000 | `c7MinSplices=20`、`c7MinChars=100000` | 注入是 DSH 的正常行为,本指标定位是"说明成本来源",故严重度封顶为低/中 | | **E1** 死循环 | 同 `tool_name` + 同 `tool_args_hash`,配对结果**均为错误**,≥3 次 | `e1MinRepeat=3` | 参数有变化的试错不计入;幂等重试可能被误判 | | **E2** 反复失败 | 会话工具错误率 > 30% 且调用数 ≥10 | `e2ErrorRate=0.3`、`e2MinCalls=10` | 不加权,一次环境性批量失败即触发 | | **E3** 工具结果过大 | 单条 `tool/result` 的 `text_len` > 8192 字符,且会话内 ≥5 条 | `e3ResultChars=8192`、`e3MinCount=5`、`e3MaxFindings=3` | 只统计长度,不留正文;只列最严重的 3 个会话 | | **E4** 计划执行脱节 | 取**最后一条** `todo/write`,`total>0 && done===0` 且其后 tool_call ≥5 | `e4GapCalls=5` | 只看最后一条计划;`todo_count` 结构不符则返回 null(静默不判) | | **E5** 冗余重复调用 | 同 `tool_name` + 同 `tool_args_hash` 重复 ≥3 次且**均无错误** | `e5MinRepeat=3` | 与 E1 的边界:有一次错误就交给 E1 | | **N1** 环境命令反复失败 | 命令类工具失败 ≥3 次(不要求同参) | `n1MinFails=3` | 与环境故障不可区分 | | **N2** 高风险操作 | 命令执行类工具命中高危模式(`rm -rf` / `--force` / `reset --hard` / `mkfs` …) | 模式表 `dsh.mjs:98-109` | 只匹配命令文本,不匹配 write/edit 内容;`node`/`python` 也在命令类里,参数含 `--force` 即命中 | | **N3** 权限审批摩擦 | `approval/asked` ≥5 次;报告审批次数、工具分布、等待中位、**被拒 / 结果无法识别 / 无对应决定记录**三者分列 | `n3MinAsks=5`、`n3SlowDecisionMs=60000` | 按 `id` 与 `decided` 关联;outcome **三分类**:`deny/denied/reject/rejected/decline/declined/refus/forbid` → 被拒,`allow/approv/accept/grant/ok/yes/permit` → 通过,**其余一律计"结果无法识别",绝不猜**(第三方评估 §4.3:旧 `!/^allow/i` 把 5 个 `approved` 报成"被拒 5 次");只记 id/工具名/结论,不记 `reason` 正文 | | **N4** API 重试频繁 | `llm/retry` ≥3 次;报告失败码分布与累计退避 | `n4MinRetries=3` | 每次重试都是新的提供方请求(可能重复计费) | | **A2** skill 闲置 | 已装 skill 在 ≥30 天**日历窗口**内 0 次触发 | `assetMinDays=30` | **窗口 = 今天 − 首次会话时间**,并报告活跃天数(第三轮修正:旧版用事件时间跨度,会把"中间 59 天没用"误判成闲置);判据是 `skill` 工具的 `arguments.name`;不足 30 天时说明还差多少天。**已装 skill 名单的目录可用 `AGENT_CLINIC_SKILL_DIRS` 覆盖**(路径分隔符分隔多个)—— 本项的输入一半来自会话、一半来自"这台机器装了什么",CI 与合成自检必须给定这份名单,否则断言等于绑在宿主机上(第三方评估 §4.2 那类病) | | **A3** 高频手动序列 | 非通用工具的两步相邻序列 ≥5 次 | `a3MinRepeat=5` | 去镜像(`a↔b` 合并)、任一端是通用工具即排除、跨会话合并(第二轮评估 F14) | | **A4** 子代理开销 | 子代理会话 token 占比 > 40% | `a4SubagentShare=0.4` | 子代理多为正常委派;分母含空会话易越线 | | **A5** 辅助 LLM 开销 | 辅助模型调用 ≥20 次;报告次数、模型与 maxTokens 上界 | `a5MinRequests=20` | 上界 ≠ 实际用量(结论里明确标注);这些调用不进报告 token 概览 | --- ## 误报防护规则(一票否决) 1. **可定位** —— 每个结论必须附 locator,不可复现的结论不出 2. **死循环豁免正常重试** —— 同参数 + 同错误 + ≥3 次 3. **软信号不给"高"** —— 措辞"疑似"的信号不用 `高` 严重度 4. **单样本不出结论** —— 跨会话类判断需要足够窗口/样本 5. **禁止硬编问题** —— 未检测到异常必须在报告中显式列出(并带原因) 6. **处方附置信度** —— 高/中两档 7. **处方须自证** —— 必须说明它如何解决对应信号 8. **处方须可执行** —— 必须给出 YAML 片段 / 命令 / 插件入口 / 界面路径 / 指向具体动作的建议, 并附风险、验证方式与出处;当前值已不弱于建议时撤掉该条 —— **且撤掉必须写出来** (报告有"已评估但未给出的处方"段,见 [docs/remedies.md](remedies.md)) --- ## 边界分支(语料覆盖不了的部分守在哪) 合成语料(`scripts/fixtures/synth-dsh.mjs`)保证**每个已实现指标至少命中 1 处**(`--verify` 校验, 21/21)。但"不命中分支"——指标**不该**报的那些路径 —— 语料覆盖不到,也不该硬塞进语料 (生成器会变脆、语义会混)。那些分支由 `scripts/smoke.mjs` 的**构造输入断言**守着: | 指标 | 不命中分支(语料覆盖不到) | 守在哪(smoke 断言) | |---|---|---| | C1 | 样本不足 → 退回"全体主会话"基线并在摘要标注 | D3/D4 段的构造会话(`c1Session`)与"基线为排除本会话后…"断言 | | C1 | 总量低于下限(≥2 万 output)不报 | 同上,构造低产出会话 | | N3 | 5 次 asked 中 1 次没有 decided → "无对应决定记录",且不混进"结果无法识别" | 第 20 段 N3 五条 | | N3 | `approved / accept / granted / ok / allow` 不得被算成"被拒" | 第 20 段 N3 五条 | | N3 | 未知结果(weird value)单独计数,不与"被拒"混算 | 第 20 段 N3 五条 | | A3 | 通用工具(`read`/`pwsh`)参与的序列不报 | D5 六条(含"关掉学习后仍不报"的对照) | | A3 | 自动学到的通用工具参与的序列不报;关掉学习会报(对照) | D5 六条 | | A2 | `AGENT_CLINIC_SKILL_DIRS` 给定时的名单来源;不设时退化为宿主目录 | A2 守卫断言(自带 `skill-dirs-guard` 目录) | | K1 | 指纹只在一个会话出现 → 不报 | 第 21/20 段 K 类构造观测 | | K1/K2 | 未采集指纹 / 快照损坏 → 只说明"未判定",不假装没问题 | 第 20 段 D4 两条(关闭开关 + 损坏快照) | | K2 | 最早那次之后就有失败 → 不报(那是"一直在排查",已由 E2/N4 覆盖) | 第 20 段 K2 收紧断言 | | 全指标 | 无数据时不崩:降级运行(`通过 p · 失败 f · 跳过 s`,退出码 0) | 第 2 段的 `skipCheck` 路径 + CI 的空数据步骤 | **约定**:往语料里加东西之前先问一句"这是命中分支还是边界分支" —— 命中分支进语料(可验证覆盖率),边界分支进 smoke 的构造断言(可精确控制输入)。 --- ## 阈值校准原则 - 所有阈值都在代码里给默认值,可用 `opts.*` 覆盖;改阈值需同步更新本文件与冒烟断言 - 基线只跟**用户自身**比,不做跨用户比较 - 校准记录追加到本文件下方 ### 校准记录 | 日期 | 指标 | 初值 | 实测 | 调整 | |---|---|---|---|---| | 2026-10-13 | E2 | >30% | 各会话 0–10.8% | 保持;补「样本 ≥10 次调用」前置条件 | | 2026-10-13 | C2 cacheRead | <30% | 93.9% | **需重新标定** | | 2026-10-14 | E1 | 同参数+同错误 ≥3 | 0 例命中 | 保持定义;新增 E5 覆盖「同参数+成功+重复」场景 | | 2026-10-14 | C1 | output 总量 > 全会话中位数 ×1.5 | 13 会话误报 6 个;基线被 2 个空会话从 15,760 拉到 10,329 | **重写**:改单位请求 output、基线只取主会话、加 2 万 token 下限、阈值 1.5→2 | | 2026-10-14 | A2 | `tool_name` 与技能目录名比对 | 22 个 distinct tool_name 中无一技能名(skill 只有 1 个,名字在参数里) | **改判据**:从 `skill` 调用的 `arguments.name` 取技能名 | | 2026-10-14 | A3 | 相邻工具对 ≥5 次 | 本机 18 条,含 grep→read / read→grep 镜像与 read/edit/pwsh 正规工作流 | **收窄**:去镜像 + 任一通用工具即排除 + 跨会话合并 + 严重度降为低(本机 18 → 1) | | 2026-10-14 | E3 | "不可实现" | 实测 1116 条工具结果有正文、57 条 >8192 字符 | **实现**:适配层补 `text_len` | | 2026-10-14 | N3 | "不可实现" | 实测 asked 23 / decided 23 | **实现**:适配层补 `approval_phase` 系列字段 | | 2026-10-14 | N4 | "不可实现" | 实测 `llm/retry` 存在 | **实现**:新增 `KIND.RETRY` | | 2026-10-15 | C5 | 新增 | 实测 26 次摘要 + 6 次剪枝,共释放 **638,171** token | **实现**:采 `compaction/*` 的 `shadowedTokenCount`(DSH 实测值,非估算) | | 2026-10-15 | C6 | 新增 | 工具 schema 中位 **29,779** 字符 ≈ 7,445 token/请求;31 个注册工具中 9 个窗口内 0 调用 | **实现**:采 `request/header` 的 `tools` | | 2026-10-15 | C7 | 新增 | 注入 96 次、158,393 字符 | **实现**:采 `agent/inbox/spliced` | | 2026-10-15 | A5 | 新增 | 辅助请求 96 次(联网检索 93 + 标题 3),maxTokens 上界 381,120 | **实现**:采两类 `*-llm-request` | | 2026-10-15 | A2 窗口 | 事件时间跨度 | 反例:60 天前用一次 + 昨天用一次 → 跨度 60 天过门槛,但中间 59 天没用过 | **改判据**:窗口 = 今天 − 首次会话时间;同时报告活跃天数;不足时说明"还差 N 天生效" | | 2026-10-15 | 报告 token 口径 | 未标注 | `token:input …` 让读者以为这是全部开销(辅助调用无 usage,不在其中) | **改口径**:写明"仅主对话(assistant/message 的 usage)",辅助调用单列一行给上界估算 | | 2026-10-15 | C6 措辞 | "整个窗口内 0 次调用" | 注册来自所有会话的并集,低频插件会被误读成"该卸" | **改措辞**:"本窗口未观察到调用",并写明未被调用 ≠ 不需要;置信度 0.85 → 0.7 | | 2026-10-15 | C5 回吐比例 | 分母 = summary + prune 释放量 | summary 的正文量去除以一个被 prune 撑大的分母 → 系统性低估 | **拆分**:summary / prune 释放量分开列,比例只用摘要自己的释放量 | | 2026-10-15 | N3 outcome | `!/^allow/i` 即算被拒 | 第三方评估实测:5 个 `approved` 全被报成"被拒 5 次",一旦 DSH 改用 `approved` 措辞被拒字段系统性全错 | **三分类**:显式拒绝词表计被拒、显式通过词表计通过、其余计"结果无法识别";摘要三种计数分列不混算 | | 2026-10-15 | C1 基线 | 含被判会话自身 | 第三方评估 §5.1:自己不该是自己的对照(中位数稳健使本次结论仍对,但口径不严);排除后基线更低→倍率更高→更敏感 | **改口径**:逐会话排除自身取中位;样本不足退回全体主会话并在摘要标注;`[100,250,2000]` 由 8.0× → **11.4×**(基线 250 → 175) | | 2026-10-15 | A2 skill 名单来源 | 只读宿主机 `~/.agents/skills`、`~/.workbuddy/skills` | 全新 CI runner 上必然 0 个已装 skill → A2 命中 0 → 合成自检假红;断言实际绑在"作者本机装了什么"上(同评估 §4.2 病症) | **可覆盖**:新增 `AGENT_CLINIC_SKILL_DIRS`(路径分隔符分隔);CI 与 `--verify` 指向语料自带 demo skill 目录;未设时沿用 HOME 默认 |