Session Forensics · 2026-09-16

会话复盘:AI 为什么停下来问,而不是按决策方法自己解决

会话 ddea70b7-fe75-48d1-aadc-ef1a5f5d4814 ·「确认guest用户数据迁移到106服务器」
生命周期 09-15 23:07:40 → 09-16 06:59:32(7 小时 52 分)|用户发言 7 条 | AI 消息 160 条 | 工具调用 265 次
判定
AI 不是"不会自决"——它这一轮把 D1/D2 从改码、编译、部署到端到端验收全程自己做完了,还自己发现并修掉两起事故。问题出在"该不该问"这一步的判据上:它手上的规则自相矛盾,遇到冲突默认选了保守侧(问用户)。
关键事实
用户 U6 明确说了「中间有问题参考决策方法」,但整个会话 AI 一次都没有加载那个技能(Skill 调用计数 = 0),只用常驻规则在推。
根因
① 规则打架没有裁决顺序(R7-边界② 说 systemd 单元"只报告不动手",§1/R8 说"直接做");②「决策方法」这个判据被放在了"需要时才看"的指针表里,而它其实是会致违规的实体规则。

01上抛发生在哪:三处,只有一处是显式的

用户说的是"停下来问我"。取证后可以精确定位——正文里显式上抛只有 1 处,另有 2 处"变相上抛"(用"遗留 / 你说了算"把决定权交回去,形态不是问句,效果相同)。

时间形态内容与用户反应
00:21:50
A idx=562
变相上抛 「五、遗留(含回头条件)」挂起 3 条:① 106 缺 ffmpeg(说要 40 分钟从中转搬)② 106 没有账号 provisioner("我可以接着把 provisioner 铺到 106")③ 存量用户依赖未共享化。
2 分钟后 用户 U3 逐条打掉:「这些106直接去网上下载不就行了」(AI 一测内网源 43 MB/s,几十秒装完)·「该谁处理就开发对应功能让谁处理,不要做这种零时方案」·「啥意思没看懂,什么存量用户」。
00:28:23
A idx=651
显式上抛 「## 四、待你拍板」两问:
1. 106 上那套旧控制面要不要停(A 停用并禁用 / B 维持现状,格式完全符合规范,还写了优缺点和"我倾向 A")
2. D1–D6 先做哪些("先 D1+D2" / "先 D5",同样写了优缺点和"我倾向先 D1+D2")
45 秒后 用户 U5:「还是删除把 A. 停用并禁用 删除数据」 58 秒后 用户 U6:「D1–D6 先做哪些 按照你的规划执行,中间有问题参考决策方法」
00:40:26
A idx=888
变相上抛 「四、遗留」第 2 条:代码尚未 commit:…都在代码仓工作树里(按纪律没擅自提交,你说了算),且 A idx=915 又重复了一次。
注:这条本身合规(§4 提交边界 明文"未明确要求不 commit"),但"你说了算"这个形态等于把球踢回去。

用户的两条反馈,暴露出 AI 这两问都问错了

U5(对 Q1):「还是删除把 A. 停用并禁用 删除数据」
U6(对 Q2):「D1–D6 先做哪些 按照你的规划执行,中间有问题参考决策方法」

02取证:三条硬事实

事实 1

Skill 调用 = 0 次

用户点名"参考决策方法"之后,AI 依然没有加载 dsh-decision-method。整个会话 265 次工具调用里,Skill 出现 0 次,也没有 Read 过任何 SKILL.md。

⇒ 那份方法论从未进入上下文。AI 全程只靠常驻的 CODEBUDDY.md §1 判据在推。

事实 2

AskUserQuestion = 0 次

全程没有用提问工具,问题全写在正文里。

⇒ 印证 §1 里已有的那条判断:「提问闸门」hook 只拦得到工具,拦不到正文里的征询。这条设计上的漏洞这次真实发生了。

事实 3

AI 是"想过判据"的——只是用错了判据

这是本次最关键的一条。reasoning 里留有完整的推理链(R idx=605 → 609 → 642):

// 它的原始思考,逐字引用
"按 R7-边界:106 是平台的服务器(我 lane 内?)—— 嗯,'平台级 / 全局 / 别人 lane'
 提到 systemd 单元属'别人 lane' ⇒ 只报告不动手?"

"按 R7-边界:systemd 单元属'平台级' ⇒ 只报告不动手。
 然后报告 + 提问(遗留控制面是否清理 —— 这是'真门禁'吗?
 停旧服务 = 影响面可能超范围 + 别人 lane ⇒ 应该问 ✓)"

"待拍板项:106 旧控制面(A 停用/B 维持)—— 这是真取舍(各有优缺)✓
 其他:D1-D6 的开发优先级 —— 这属于'先做哪个'= 业务优先级 ⇒ 边界外 ⇒ 要问"
"按 §1:'业务目标与优先级(做不做、先做哪个)'属边界外 ⇒ 必须问 ✓"

⇒ AI 不是凭直觉上抛的,它是"照章办事"推出来的。所以病症不在态度,在规则本身:它在两条打架的规则里选了保守的那条,又把有客观排序的事归到了"业务优先级"。

03根因:两层

第一层 · 规则自相矛盾,且没有裁决顺序

「106 上的 systemd 单元能不能动」这一个问题,CODEBUDDY.md 里有三处给出相反结论:

规则位置原文要点推出的动作
§1 第 27 行开发环境服务器 ⇒ 重启 / 停实例 / 改配额或 env / 改 nginx·nft 直接做,只需动手前一句话说明直接做
§3 R8(已修正为)"均可直接做,不必再等确认"直接做
§3 R7-边界②/var/lib/**、全局符号链接、systemd 单元、nginx·nft、别人的 profile/产物 ⇒ 一律只报告、不动手只报告
§2 第 58 行要动生产 ⇒ "先按 R8 说清…并取得确认"(与 R8 自己打架)等确认

AI 撞上这个交叉地带时,选了保守侧。但 R7-边界② 的错不在措辞严,而在于它把"平台级 / 全局"与"别人 lane"并列了——于是 AI 把自己家的 106 节点读成了"别人的东西"。事实上 AI 在更早的 R idx=79 里还判对过:「这不属于'别人 lane'(106 是我们自己的 worker 节点,属于平台)」。同一个对象,前后两次判断相反。

第二层 · 判据的位置放错了,违反了文件自己的标准

CODEBUDDY.md 开头明确写着分层判定标准:

「这条内容如果不看,会不会导致「违规」或「事故」?
会 → 必须常驻实体内容(写在本文件 / MEMORY.md 里,不许只给指针);只是"更慢、更绕" → 才可以只给指针」

而「决策方法」被放在了 §2 「什么时候去查什么」—— 动作触发的指针表 里,也就是被定性为"需要时才看、看了更准、不看也不违规"的那一类。

可事实上——"该不该上抛"判错 = 直接违反 §1 提问判据,那是被明文列为"必须实体常驻"的章节。文件在第 67 行自己也承认了这个风险:

「⚠️ 技能的加载由模型判断相关性,不能保证。所以:凡"动作前必须生效"的规则,必须写在本文件里;技能只承载"需要时去拿的方法论"。」

⇒ 结论:一个会致违规的判据,被自家标准判成了"可给指针",结果就是这次——用户点名了,指针也没被走。

补充 · 三处判断对照

AI 的判断它引用的依据按用户口径应当如何
「106 旧控制面停/留」要问 R7-边界②
"systemd 单元 ⇒ 只报告"
自决(直接删) 对象是我们自己的 106;且"旧控制面早于集群化切换、已运行 1 天 19 小时"这个不确定点完全可以自查(was 有访问日志/依赖引用),不该把"我不确定"当上抛理由。用户 U5 / U7 两次自己给出了"删除"。
「D1–D6 先做哪几个」要问 §1 "业务目标与优先级
属边界外"
自决 AI 自己已排出客观顺序(3 项 P0、D1+D2 能闭环"新用户落 106 起不来"的真实缺陷)⇒ 属明显更优,按 §1"候选只有优点 ⇒ 自己拍掉"。用户 U6:「按照你的规划执行」。
「ffmpeg 要不要传」写进遗留 R idx=159
"暂缓(问用户?或直接传)"
自决(先查再传) 该查的是"目标机自己能不能装"——AI 只测了公网和 GitHub,没测内网源。用户一句话点破后,内网源 43 MB/s 几十秒装完。

04公允地说:这一轮大部分是自决的

避免把复盘读成"AI 什么都不自己干"。同一会话里,U6 之后 AI 实际上是这样跑的:

⇒ 所以真正的病灶很窄:只在"平台级资源"和"业务优先级"这两个交叉地带判错了方向。

05已做的收口(我拍的可推翻)

文件改动
CODEBUDDY.md
§1 新增
🔀 规则冲突裁决顺序:同一对象被多条规则给出相反结论时,按 R8 → §1 边界内自决清单 → 其余红线 取首个命中项,⛔ 不再"自行取保守侧"。
⛔ 冲突 ≠ 门禁:规则打架不构成上抛理由;门禁只有两类(不可逆破坏性操作 / 边界外六类)。
🔑 "平台级" ≠ "别人的":自己的 47 / 106 / 本工作区上的 dshs*·dsh-* 单元、/var/lib/dshs/**、nginx·nft ⇒ 按 §1+R8 直接做。
📌 上抛前三问:① 是我们自己的资源吗?② 关键不确定点查证了吗?③ 第一名是否明显更优?—— 任一为"是"即自决。
CODEBUDDY.md
§2 第 58 行
删掉与 R8 矛盾的"并取得确认",改为"直接做,动手前一句话说明;只有破坏性不可逆才先出清单"。
CODEBUDDY.md
§2 第 61 行
「用户点名决策方法 ⇒ 必须立即 Skill(dsh-decision-method),不得凭记忆代替、不得只靠 §1 判据」——把指针升级为硬要求。
CODEBUDDY.md
§3 R7-边界②
明确②的适用对象 = "别人的 / 归属不明",⛔ 不含我们自己的 47/106/本工作区资源。判据看"归属",不看"是不是平台组件"。
技能
dsh-decision-method
2.7.4 → 2.7.5:§4.4 硬约束 2 修正("与红线冲突 → 红线赢"过宽,正是本次上抛诱因)+ 新增 §4.5 规则冲突裁决顺序 + 上抛前三问(含本案例);description 补触发词。
技能
dsh-feature-first
1.7.0 → 1.7.1(5 处)——这才是 AI 判定"该不该上抛"的主依据,上一轮漏了:
· §3.2 删掉「只有真会中断的(重启服务 / 停实例 / drain / 改配额·env / 改 nginx·nft)才上抛」→ 这正是本次上抛的直接依据;补上"平台级 ≠ 别人的"。
· §3.4 第 1 类加出口:方向已定 + 候选有客观排序 ⇒ 「先做哪个」属边界内自决(AI 把"D1–D6 先做哪些"归进来才上抛的)。
· §3.4 第 7 类 去掉 R8;§6 自检第 4 条改写 + 新增第 0 条;§7 加 R8 例外注。
· §5.4 新增硬约束 10「并列内容逐条分段」+ 反模式 14(你这次提的排版要求)。
.codebuddy/rules/
条件规则
按路径自动注入的规则里也有同源冲突:
· server-ops.md:表格「✅ 会,须先知会」→ 改为「直接做,动手前一句话说明,⛔ 不必等确认」。
· frontend-ui.md:补 R8 修正注 + 按 R11 说明"无收益的重启仍属劣化"。
技能
workbuddy-session-forensics
三个数据源的结论全部作废重写(本次实测发现旧版严重过时):
· workbuddy.db.sessions 表已可用且最新(67 行)→ 升为首选入口
· 转录 projects/<目录名>/<sid>.jsonl 本机完全可读(旧版"近期会话没有"是踩了目录名的坑:工作区搬迁后新会话进 e-ProgramData-AI技能-…,旧会话留在 d-AI技能-…)
· edge-sync.log 已不存在
· 新增 §2b jsonl 抽取法(⚠️ 文本元素是 input_text/output_text,不是 text —— 按 text 抽会全空且不报错)+ §2c「分析 AI 为何上抛」三段取证法

三处同步已对账一致:dsh-decision-method md5 7ae701b7…、dsh-feature-first md5 6ca922e2… ⇒ 本机 = 文档库 = 镜像 /opt/dsh/docs/skills/;新钩子 md5 9041fe37… 本机 = 镜像。对账一致数 177 → 178。
其余 3 项差异(bash-output-guard.py / stop-dialog-guard.py / DEPLOY-本部署.md)是既有差异、不属本次改动 —— 按纪律只报告、不动手。

06加固:怎样保证「点名了方法」就真的会加载技能

这是根因的机制层。技能加载是软的 —— 由模型判断相关性,CODEBUDDY.md 第 67 行自己就写着「不能保证」。所以不能把它当唯一防线。四层硬化全部落地,代价都很低,且互不排斥:

层次做什么可靠性
① 判据实体化
根本解
把"该不该上抛"的判据直接写进 CODEBUDDY.md §1(规则冲突裁决顺序 + 上抛前三问)—— 即使技能永远不加载,判据也在。 最可靠
每次会话必加载
② 机制层强制
新钩子
新建 scripts/skill-load-guard.py(UserPromptSubmit 钩子):扫用户输入,命中「决策方法 / 参考决策 / 按你的规划 / 别问我 / 自行决策」⇒ 注入一条紧邻用户消息的强制加载指令(位置比静态规则文件显著得多)。
自作用域(只在本工作区生效,其他项目一律放行);急停双闸;低频自证日志。
强
不依赖模型自觉
需完全重启才生效
③ description
零成本
给 dsh-decision-method 的 description 补触发词:「用户点名决策方法 ⇒ 必须立即加载,不得凭记忆代替」。description 每轮都在上下文里。 中
仍是提示
④ 自检清单 dsh-feature-first §6 新增第 0 条:「用户本轮点名了某个技能 / 方法吗 ⇒ 必须先加载」。 中
靠自觉

钩子实测(三情形全部正确)

输入结果
含"参考决策方法"· 本工作区✅ 输出 additionalContext,含强制加载指令 + 本案例
普通提问 · 本工作区✅ 无输出(不添乱)
含关键词 · cwd = 其他项目✅ 无输出(自作用域生效,不污染别的项目)

⚠️ 钩子和 CODEBUDDY.md 都需完全重启 WorkBuddy 才加载(关窗 ≠ 退出)。重启后想确认钩子活着,看 .workbuddy/skill-load-guard.log —— 没出现 HIT 行只说明还没命中过,不等于没装好。

顺带修掉一个真实 bug

settings.json 里 stop-dialog-guard.py(上一个同类钩子)的安装命令带着 -S -E —— 而该脚本自己的 docstring 就明确警告:

「⛔ 安装命令不要给本脚本加 -E…本机设了 PYTHONUTF8=1,而 -E 会把它全部忽略 ⇒ stdin 回退 cp936 ⇒ 含中文的 payload 解码即炸。2026-09-15 实测:-S -E 曾让本钩子"看起来从未被调用"整整一天。」

已去掉 -E(保留 -S)。即:那个钩子此前很可能一直没真正生效过 —— 与本次问题同源:机制看着装了,实际没跑。

07取证方法与文件

数据源(全部本机、只读):

取数中间件(已归档,不再散在工作区根):