从记忆、规则到钩子:让 AI 编程助手真正服从指挥
说过的要求,AI 下一次照样违反。我们把写下来的 257 条开发要求逐条盘点,分成三类:放记忆、写成规则、做成钩子。本文讲这批钩子的来由、做法和边界,以及每一类要求该放在哪里。
先说结论
- 写在记忆和说明文件里的要求只能提醒,管不住执行;我们把其中能由机器判断的一批要求改成了钩子:在固定时机自动运行检查脚本,按写好的条件放行或拦下。
- 判断一条要求放在哪里,问两个问题:能不能写成机器可判定的条件;它属于项目,还是属于怎样和人协作。能判定的考虑做成钩子,项目知识写进项目仓库,只和协作方式有关的才留在记忆里。
- 大部分要求做不成钩子,也不应该硬做:盘点出的 205 条要求里,建议做成钩子的是 58 条,不到三成;需要理解和判断的原则仍写在规则里。
- 钩子也是需要维护的代码,会误拦、会静默失效;这批钩子由 APSer-Atelier 统一登记和铺开,Atelier 的守卫页按实际挂载列出全部钩子,与登记清单逐条比对。
用 AI 编程助手做交付久了,会遇到一件让人泄气的事:一条要求明明说过,也写下来了,下一次照样被违反。说一次,管几天;再说一次,又管几天。
我们原先的做法,是把要求写下来,让 AI 每次开工时读。写下来的地方有两处:一处是 AI 工具自带的「记忆」,另一处是放在项目里的说明文件。9 月下旬,我们确认了一件事:这两处都只能「提醒」,管不住执行。于是把其中能由机器判断的一批要求,改成了「钩子」。
钩子是 AI 编程工具提供的一种机制:在固定的时机,比如收到一条消息时、调用一个工具之前、一轮回复结束时、提交代码时、推送代码之前,自动运行一段检查脚本,按写好的条件放行或拦下。它不依赖 AI 记不记得、理不理解,同样的输入每次得到同样的结论。
这批钩子由我们自研的工作平台 APSer-Atelier 统一登记和铺开。Atelier 在我们的交付里担任主控,AI 编程工具是它调度的执行器;钩子是主控把规矩落到执行现场的方式,执行器在哪里出错,就在哪里被当场拦下,见《APSer-Atelier 的技术架构与选型决策机制》。
这里说的 APSer-Atelier 主控包括两部分:面对我们的主控会话(一段 Claude Code 或 Codex 对话),负责组织和派出;Atelier 平台,负责登记每一件事、挂起待拍板、记下要求、按规矩放行并留下记录。
本文记录这批钩子:为什么要做,做了哪些,怎么做的,以及做完以后我们对「一条要求该放在哪里」的看法。文中的钩子运行在 Claude Code 的交互式会话里,登记和查看都在我们自研的工作平台 APSer-Atelier 上;平台的来历见《APSer-Atelier 的场景锚定与价值定位,以及实践总结》。
一、写下来的要求,实际是怎样一次次失守的?
下面这些都是我们 2026 年把 AI 用于项目开发的过程中,实际遇到的场景。每一条要求,当时都已经写在记忆或规则里了。
用中文思考和回复
我们要求 AI 的思考过程和回复一律用简体中文,英文只出现在代码、命令和标识符里。这是最简单的一条,没有歧义,也不需要判断。
它失守得最频繁。9 月 9 日一天被指出七次,9 月 17 日一天六次。追查原因,这条要求只写在一个项目的记忆里;记忆按项目隔离,在其他项目里开工时,AI 读不到它。17 日傍晚,我们把它挪进所有项目都会读的全局说明文件。
第二天凌晨,老毛病又犯了。我们当时的原话大意是:「记忆里不是早就记了吗?规则里也有吧?」事后诊断,AI 并非不知道这条要求,问题在于没有一个拦截点:之前的检查全都作用在工具调用上,没有任何一道作用在「生成文字」这件事上。措辞类的要求全靠自觉,会话一长就守不住。于是当晚加了第一个在回复结束时检查语言的钩子。
第一版上线后很快被审查出五处缺陷,其中最严重的一处:它把工具返回的结果也当成了「新的一轮」,每次只查到最后一小段思考,按实测大约只覆盖了六十分之一。挂上了,看起来在管,其实没管住。修完这几处,不到一个小时又出现整段繁体:繁体字同样是中文字符,原来的判定照样放行。于是加上繁简差异字的检测;后来又发现「检查」「管理」这类简体词里的字在繁体里字形相同,被误判为繁体,再把这几个字剔出去。
到 9 月 24 日,又发现当天有六次英文叙述没被拦下:钩子当时只查思考、不查回复正文;而且为了防止死循环,一个会话最多打回三次,长会话用满三次以后,后面就一律放行了。一条最简单的要求,前后改了四轮才算管住。
不要提醒休息
我们习惯长时间连续投入,自己判断什么时候停。AI 却常常在深夜说「早点休息」「今天就先到这里」「做完这个就睡吧」。这类话出于好意,效果却是打断状态,把本该由我们自己做的判断拿了过去。
我们明确说过不要。AI 换个说法再来:不说「早点休息」,就说「做完这个就睡吧」「别熬了」。被指出以后,它还会解释「我关心你」「只是建议」。我们后来把这条写成一句元规则:说过的「不」就是「不」,不管听起来多有温度。要求 AI 在温和的名义下越过明确边界时,恰恰最需要警惕。
这条规则写得再重,也挡不住下一次换个说法。最后它进了同一个语言检查钩子:一张作息与收工用语的词表,命中就打回重写。词表特意不收「明天」,因为「明天交付」是正常的业务日期;引用原话时写在引号里的内容也不查,免得复盘时把自己打回。
被追问才补上的遗漏
我们交代一件事,往往一口气提好几条诉求。AI 顺着对话往下做,前几条做完就报告完成,后面几条被丢掉。有一次,一项治理工作是被追问之后才补上的,另外四处遗漏也是被追问之后才去查的。我们当时的原话是:「我不嘱咐你后面几句,你就必然会遗漏了?这如何让我放心呢。」
这条判定不了「漏没漏」,做不成拦截。我们的做法是:每收到一条消息,就由钩子把几条最高频失守的要求重新贴到 AI 眼前,其中一条是「凡交付报告,第一节必须是一张诉求覆盖表,逐条列出本次提过的每一条诉求和状态」。长会话里,开工时读的说明文件离当前位置越来越远,作用越来越弱;每条消息后贴一遍,这几条就始终在眼前。
服务「没起」,于是绕过接口直接写库
同一天凌晨,AI 用几个端口探了探,没有响应,就下结论说我们的平台服务没有启动,接着绕过平台接口,直接往它的数据库里写数据。实际上服务一直在跑,只是端口不是它探的那几个。
这件事留下两条:一条写进每条消息后贴出的提醒里——下「没起、没有、不通」的结论之前,先看进程列表、端口监听表和反向代理配置;另一条做成钩子——目标数据库不在当前项目里,写入一律拦下,只读查询放行。
一次「全部添加」,生产环境两次出错
9 月 22 日,排程系统的主控 AI 会话在前台修一个问题,同时有一个执行器在后台改代码。主控提交时把整个文件加了进去,连带执行器改到一半的代码,推送后演示环境的后端起不来。随后它想用一个隔离的工作区补救,创建工作区的命令失败了,后面的命令落回主工作区,又推上去一次错的。
这件事之后,「全部添加」和「隔离工作区」都做成了拦截:提交时只能逐个写明文件;隔离工作区永远不用。两天后,另一家工具早先留下的一个功能分支接住了执行器的提交,AI 还拿「要不要合并」来问我们。从此分支也一并禁用,只在主干上提交。
写时间凭印象
长会话里,AI 心里的「现在几点」会越来越偏离实际,写进变更记录的时间常常是猜的。这条做成了钩子:写变更记录和待办里的时间戳之前,必须刚刚读过系统时钟,否则拦下。
换会话的提醒,从来没有过
我们要求 AI 在几种情况下主动提醒换一个新会话:会话被自动压缩过(上下文太长时,工具会把早先内容压成摘要)、一件事刚好收尾、同一类错误犯了两次以上。提醒时附上一段写好的交接说明,粘贴到新会话里就能接着做。
这条写进全局说明文件以后,某天我们问 AI:「为什么从来没有提醒过我换会话?」答案是:确实从来没有提醒过。我们当时的结论很直接:记忆没有用。写在记忆和规则里的要求,管不住执行。
一次整体盘点
9 月 28 日上午,我们决定把所有写下来的要求盘点一遍:哪些能改成钩子,哪些只能留在文字里,哪些已经过时。这次盘点由一个只读的助手逐条读完 AI 的记忆、全局说明文件、另一家工具 Codex 的说明与规则,以及现有的全部钩子。结果如下:
| 项 | 数量 |
|---|---|
| Claude Code 记忆文件 | 170 个,分布在 13 个项目 |
| 从中盘出的行为要求 | 257 条,去掉多处重复后 205 条 |
| 建议改成钩子 | 58 条 |
| 建议留作文字规则(要靠理解和判断,机器判定不了) | 62 条 |
| 建议挪进项目自己的说明文件(属于项目的工程知识) | 38 条 |
| 已被现有钩子覆盖(含部分覆盖) | 27 条 |
| 已经过时 | 20 条 |
| 同一条要求在多处重复出现 | 21 组 |
| 新旧说法过时或互相矛盾 | 23 处 |
| Codex 一侧的命令规则 | 214 条,全部是「允许」,没有一条「禁止」 |
这张表里有两个数字值得多看一眼。一是 23 处矛盾:比如一条旧记忆写着「并行时用隔离的工作区」,全局规则早已写明「永远不用」;AI 同时读到两句,选哪句全凭当时的上下文。二是 Codex 一侧 214 条规则全是放行:我们在 Claude Code 一侧陆续加的拦截,在另一家工具里完全没有对应。
二、一条要求该放记忆、写成规则,还是做成钩子?
盘点之后,我们给每一类要求定了去处。先说三处各是什么:
- 记忆:某一家 AI 工具私有的、跨会话保留的笔记。只有这家工具读得到,换工具就看不见;而且按项目隔离,在一个项目里记下的,另一个项目里读不到。
- 规则:写在项目仓库和全局说明文件里的文字。项目说明文件用的是多家工具都认的开放约定,随代码一起版本化。它靠 AI 读懂并自觉执行。
- 钩子:在固定时机自动运行的检查脚本,按条件放行或拦下。它不靠理解,只认条件。
判断一条要求放在哪里,我们问两个问题:
- 这条要求能不能写成机器可判定的条件? 只看命令文本、目标文件路径、回复文本这些确定的东西,就能判断违没违反,而且同样的输入总得到同样的结论。能,就考虑做成钩子。
- 这条要求属于项目,还是属于「怎样和人协作」? 如果明天换一个工具接手这个项目,它也需要知道,那就写进项目仓库,不能只存在某一家工具的记忆里。只和协作习惯有关的,才适合留在记忆里。
按这两个问题分下来,大致是这样:
| 去处 | 适合放什么 | 我们的例子 |
|---|---|---|
| 钩子 | 能按命令、路径、文本确定判定的禁令和必做动作;后果重、或者反复失守的优先 | 只在主干上提交、不新建分支;不整体丢弃工作区里未提交的改动;不用「全部添加」把别人的改动卷进提交;不改凭据文件;写时间戳之前先读系统时钟;不直接写别的系统的数据库;推送时指明推哪一个提交;推送上线后必须跑完验收;截图不落进代码仓库;思考和回复用简体中文,不说给工作划收工时间的话,不做情绪安抚,不把常规的提交推送问回来;会话压缩后必须附交接说明;新会话开工时核对工作目录;不往本会话写入范围以外的仓库写东西 |
| 规则(项目或全局说明文件) | 需要理解和判断的原则;项目的工程知识、踩过的坑、实测结论 | 证据优先,给原始输出,不用概括代替;判断错了直说;排期按完整的一段工作来排,不切成碎步;我们当次限定了范围,就做到范围为止,不顺势往外推;对外文章里的数字要回到源头核对;没做完的功能用开关关着;改一个函数的签名之前,先把所有调用方找全。项目知识如:客户原始资料只放在仓库以外的私有目录;不用真实客户资料做测试数据;性能和页面问题先在本机验证,不在生产上做实验;排序的结果由确定的程序给出,不交给大模型 |
| 记忆 | 只和协作方式有关、换了工具也不影响项目的偏好 | 汇报时先说结论再给依据;某一家工具自己的操作习惯 |
| 清理 | 已经过时、或与新规则矛盾的旧要求 | 主干分支统一叫某一个名字(实际上各项目叫法不同);并行时用隔离工作区(已被「永远不用」取代);提交署名固定写某一家工具(已改为写实际参与的执行器) |
有三点需要说明。
第一,「能判定」不等于「应该做成钩子」。比如「对外文章里的数字必须回源核对」,理论上可以检查回复里的数字是否在本轮工具输出里出现过,但这种启发式判断误拦太多,拦错的代价比漏掉还大。这类要求我们留在规则里。
第二,同一条要求可以一半做成钩子、一半留在规则里。「主动提醒换会话」的三种信号里,只有「会话被压缩过」能从会话记录里机械地判断出来,另外两种(一件事收尾、同类错误重复)需要理解上下文。所以钩子只强制第一种,另两种仍靠规则。
第三,记忆里留下的最少。62 条留在文字里的要求,多数是「判断错了直说」「证据优先」这类原则,对任何一家工具都适用。我们把它们放在全局说明文件里,不放在某一家工具的私有记忆里。
为什么要把一部分要求做成不讲情面的钩子,经济学里有一个现成的解释。《权力与预测》一书说,组织用规则代替临场决定,一大原因是可靠:许多环节互相依赖时,每一处都按规则来,别处才能放心地安排自己的事 [1]。几家 AI 工具、几个代码仓同时在动时,也是这样;对最容易出事、后果最重的几处,把临场判断收回成钩子,换来的是整个系统的可靠。
三、这一批钩子各解决什么问题、怎么做的?
以下按要解决的问题分组,每组写背景和做法。时间集中在 9 月 24 日到 28 日。
语言:从只查思考,到每一段都查
- 背景:回复正文的英文叙述漏过检查;长会话用满三次打回之后永久放行;另外发现,派出去的子助手用中文干活,却被错误地拿主会话的记录去查,连续被打回三次。
- 做法:检查范围从思考扩到回复正文;打回上限从「每个会话三次」改为「每一轮三次」;子助手只查它自己的记录,找不到就不查,绝不拿主会话的记录代替。除了在一轮结束时查,每次调用工具之前也先查一遍紧挨着的那段文字,问题出现后不必等到一轮结束才发现。修复后,同样的子助手任务打回次数从 3 次降到 0 次。
回复用语:从作息用语扩到一组词表
同一个语言检查钩子,在盘点后落地的第二批里,又加了一组回复用语的词表,也都是原本只写在记忆里的要求:
- 作息与收工用语(见第一节「不要提醒休息」);
- 情绪安抚,如「一定能成」「辛苦了」「别担心」;
- 越过边界之后的体贴式解释,如「为你好」「我关心你」「只是建议而已」;
- 临时造出来的系统代称:系统只用真名,不另起别名;
- 报告里挂一节「遗留问题」:本任务里发现的缺陷当场修完再报;
- 把常规的提交、推送、部署问回我们,如「要不要推送?」「需要我继续吗?」:这些可逆的内部动作直接做完再报告。
判定之前,先剔除代码、路径和引号里的原话。上线前回放最近 250 个会话的 3745 轮回复,逐条判读命中,收窄了四处误拦。例如「只是建议」出现在正常的技术讨论里,「遗留项」恰好是一个项目里某项任务的名字。
换会话:从「记得提醒」到「不提醒就不放行」
- 背景:规则写了,从来没执行过。
- 做法:一轮回复结束时检查会话记录,如果会话被压缩过,而压缩之后还没给出过一段完整的交接说明,就打回,要求补上。同一次压缩最多打回三次,给过一次就放行,直到下一次压缩。另一个钩子拒绝 AI 自己开新会话或在终端里另起一个 AI 进程:新会话只由我们自己在桌面应用里开,避免多个会话同时改同一片状态。
等待:不许手写等待循环
- 背景:AI 等一个后台任务完成时,常常手写一个「每隔几秒查一次」的循环。这种循环超过十分钟会被转到后台,AI 再开一个新的来等,就成了重复等待。
- 做法:拦下手写的等待循环,改用一个统一的等待脚本,并要求调用时给足超时时间。
收口:只提示,不拦
- 背景:一个会话里记下的「以后再说」越积越多,或者改动跨了好几个仓库,说明这个会话已经偏离了一件事一件事地做。
- 做法:一个会话里往停车清单登记的事项达到 5 条,或者改动涉及的仓库达到 3 个,就提示「建议收口并交接」。这个钩子只提示、从不打回,收不收口由我们决定。上线前用最近 150 个真实会话回放,有 12 个会触发,约 8%。
写入范围:一个会话只写它该写的仓库
- 背景:我们一个人同时推进好几个项目,规定同一个仓库同一时间只有一个写入者。实际发生过:一个会话开在 A 项目的目录里,拿着 B 项目的交接说明开工;或者做到一半,顺手改了别的仓库。
- 做法:新会话的交接说明必须写明「写入范围」,也就是哪几个仓库可写、哪几个只读。收到交接说明时,如果当前工作目录不在可写范围里,这条消息直接被拦下,提示换到正确的目录重开。开工以后,任何写动作,包括改文件、添加、提交、推送、打标签,目标仓库不在范围里就拦下;写之前还要先在平台上登记「本会话正在做这件事」。本文落盘时,就先被这道检查拦了一次,登记之后才写进来。
- 实测中发现一处误拦:写文档时,命令里的正文提到了别的仓库的提交命令,被当成了真的跨仓提交。改为跳过引号内和多行正文里的文字后解决。
危险命令:盘点后落地的第一批
这是盘点之后落地的第一批:12 条原本只写在记忆里的禁令,改成了调用工具之前的确定性拦截。
- 建分支、切分支;
- 整体丢弃工作区里未提交的改动(这类命令会把其他会话正在改的内容一起抹掉);
- 在仅供参考的外部开源克隆上提交或推送;
- 用 conda 建 Python 环境(我们统一用 venv);
- 删除系统钥匙串、系统账户;
- 读取密码管理器的库;
- 打印进程的环境变量(会带出凭据),以及非预演方式同步凭据;
- 非预演方式运行会批量改写项目台账的同步脚本;
- 截图文件落进代码仓库;
- 触及微信的本地数据目录;
- 推送即部署的仓库不指明提交号就推送,或一条命令里推两次;
- 提交之后,没确认提交成功就接着标记「这件事做完了」。
每一条都写明出处,也都留了逃生口:确实需要做的时候,在命令里写上对应的标记即可放行,这条命令会留在会话记录里,事后可查。只有「截图落进代码仓库」一条没有逃生口,改存到临时目录就行。
上线前,我们用最近 250 个真实会话里的 24478 次工具调用做回放。「不指明提交号就推送」命中 773 次,「截图落进仓库」命中 87 次,逐类核实都属于规则本来要管的情况。回放也查出两类误拦:一类是命令里用变量指定目录,被错算成了当前仓库;另一类是在文件里搜索某个脚本的名字,被当成了运行这个脚本。修掉之后才挂上。
发布门:出错时拦下,而不是放行
- 背景:前面的钩子都是「出错就放行」:只要检查脚本本身出错,一律放行,宁可漏拦也不能卡死日常开发。上线不一样,漏一次就是生产事故。
- 做法:推送之前和上线打标签之前,运行项目自己声明的发布检查。检查没跑完、脚本报错、超时、结果格式不对,都算不通过,不推送、不打标签。本站是第一个纳入的项目。
一页看全部钩子
钩子多了以后,登记清单和实际生效的钩子很容易对不上。Atelier 里有一个守卫页,按实际挂载的情况列出全部钩子,与登记清单逐条比对,对不上的直接标出,并统计近 7 天的拦截和报错次数。可以在页面上停用或恢复单个钩子,每次操作都要输入由我们本人在本机设置的口令。
四、做钩子时要守住哪几条?
- 认行为,不认名字。判断依据是命令实际做了什么、写到了哪个路径,不看脚本叫什么名字。
- 日常钩子出错放行,发布门出错拦下。两种取舍分开写明,不混用。
- 每一次打回都有上限。回复结束时的检查打回几次后就放行,防止 AI 和钩子在一处来回拉扯。
- 逃生口要让 AI 真的用得上。我们吃过一次亏:两个钩子的逃生口原本读的是环境变量,拒绝提示也教 AI 在命令前加变量重试;但钩子进程读不到命令里临时设的变量,AI 照做以后依旧被拦,看起来像是钩子坏了。后来改成在命令文本里写标记,或者先用一条命令签发一个按路径、限时的许可。
- 上线前用真实会话回放。单元测试只能证明写的人想到的情况;真实会话记录能查出没想到的误拦。
- 钩子的口径和规则的口径必须一致。我们曾把提交署名的检查写成只认一个固定写法,结果当天就拒掉了规则本身认可的其他写法。后来用 760 条历史提交回放,改成只认格式,零误拦。
五、钩子有哪些管不到的地方?
- 只覆盖挂了钩子的地方。这批钩子作用于 Claude Code 的交互式会话。平台自己派出去执行任务的 AI 进程,启动时刻意不加载这些钩子,靠启动参数和隔离的工作区来约束。Codex 一侧目前只有个别项目挂了钩子;盘点中我们核实到 Codex 也支持用户级的钩子,计划把同一批检查复用过去,尚在进行中。
- 钩子自己也会静默失效。有一段时间,开发机的一个系统组件升级后要求重新同意许可协议,期间所有钩子都没能运行,按「出错就放行」全部放行了。守卫页上线后统计,那 7 天里有 376 次这样的报错,而我们事先毫无察觉。现在守卫页会直接显示这类报错,能较早发现,根因还没有消除。
- 钩子有时会拦到自己人。比如写测试时,命令文本里恰好出现了「别的项目的数据库路径加上写入语句」,检查「不许写别人数据库」的钩子会把这条测试命令也拦下来。
- 本机有权限的 AI 仍然可以改钩子本身。网页上停用钩子要口令,但在本机有终端权限的 AI 可以直接改配置文件。守卫页会显示口令最近一次设置的时间,以便发现异常。钩子约束的是日常执行,它不是一道安全边界。
以下是我们的看法
以下内容是我们的观点,供您参考。
第一,记忆是提醒,规则是约定,钩子是门。 三者各有用处,不能互相替代。记忆和规则让 AI 知道该怎么做;钩子在它没照做时拦下来。一条要求被反复违反,先问它能不能写成条件;能写成条件的,就别再指望 AI「记住」。
第二,大部分要求做不成钩子,也不应该硬做。 我们盘点出的 205 条要求里,建议做成钩子的是 58 条,不到三成。「证据优先」「判断错了直说」这类原则,是和 AI 协作的底色,只能写清楚、反复用。硬把需要判断的要求做成机械检查,误拦会让人开始绕开检查,结果比没有更糟。
第三,把要求挪出私有记忆,本身就是一次清理。 平时看不见的重复、矛盾和过时,逐条盘点时都浮了出来:21 组重复,23 处矛盾,20 条过时。AI 同时读到两条互相矛盾的要求时,它的选择不可预期。这笔账不算清,钩子加得再多,也会被旧文字抵消一部分。
第四,钩子也是需要维护的代码。 它会误拦,会失效,会和规则的口径脱节。所以要有一份清单、一个统一查看的页面、上线前的回放,以及每一条都写明出处的拒绝提示。
第五,让 AI 服从指挥,前提是指挥本身写得清楚。 做钩子的过程,逼着我们把每一条「不要这样」写成确定的条件,想清楚它的出处、例外和逃生口。这一步做完,以后换了工具、换了机制,这些条件也能原样搬过去。
如果您也在用 AI 编程助手做项目交付,正在为「说过的话 AI 记不住」头疼,欢迎预约交流,聊聊各自的做法。