前阵子处理一个历史仓库,差点被AI把调用关系改崩。排了半天才发现问题不在模型能力,也不在提示词写得差,而是我压根没给AI一个明确的上下文切换机制。今天想聊的就是这件事:我自己整理的一套 context-mode。
简单讲,context-mode 不是某个具体工具的隐藏参数,而是我在日常AI协作里维护的一套上下文管理模式。它把项目信息、角色设定、行为约束、输出格式打包成若干张“模式卡”,在不同任务之间显式切换,而不是让上一轮的对话残留一路带到下一轮。这篇东西适合那些经常用AI写代码、改代码、做代码审查,又总抱怨“它怎么又忘了”“它怎么管这么宽”的人。读完你基本可以照着搭一套自己的。
1. 一个差点翻车的重构,让我决定管好上下文
1.1 翻车现场的还原
那个仓库是一个内部工具库,scripts/目录下散落了三十多个Python脚本,很多脚本之间通过sys.path互相 import,还有几个被 CI 定时任务直接调用。我当时的想法很简单:让AI帮我把这些脚本整理到src/tools/下面,保持所有行为不变。
我打开AI工具,直接说了一句“帮我把 scripts 整理到 src/tools”。模型很积极,立刻开始写迁移方案。问题就出在这里——它没有问我要保持哪些调用关系,也不清楚哪些脚本会被外部入口依赖,就按自己的“合理猜测”设计了一版新目录。它还把几个脚本里的公共函数签名改掉了,理由是“更规范”。结果一跑测试,三处 import 直接崩。
从表面看,这是AI“自作主张”的问题。但站在使用者的角度复盘,根子在我:我把它丢进了一个上下文不清不楚的场景。它既不知道自己是只读审计还是动手重构,也不知道哪些文件是可以碰的,更不知道输出应该先给计划还是直接给 diff。这种情况下,模型只能从历史消息里猜,猜错就是必然。
1.2 context-mode 的核心理念
从那以后,我把所有AI协作任务按“上下文边界”重新做了切分,这就是 context-mode 的来源。你可以把它理解成操作系统的运行模式:内核态能做底层操作,用户态受限执行,不同模式拥有不同的权限和可见资源。AI协作也一样,每个任务应该有一张明确的模式卡。
一张模式卡至少回答四个问题:
- 当前角色是什么?是资深工程师、代码审查员,还是教学陪练。
- 能看到哪些文件?是只读某个目录,还是允许修改全仓库。
- 绝对不能做什么?比如禁止改接口签名、禁止加依赖、禁止动无关代码。
- 输出应该长什么样?先给影响清单、再给 diff,还是只解释思路不给代码。
后来我在多个项目里反复验证,这套东西比任何“角色扮演提示词”都稳定。因为角色扮演只是改变语气,模式卡直接改变了模型的信息输入范围和输出约束,它是一种结构性约束,不是修辞。
1.3 适用场景,别只看代码
context-mode 最适用的当然是AI辅助编程,但它的思路完全可以迁移到别处。
- 多项目并行:你电脑里同时跑着后端服务、数据脚本、前端小工具,每个项目的依赖和规范都不同。
- 多角色切换:早上写业务代码,下午做代码审查,晚上还要给新人讲架构。三种任务对AI的要求完全不一样。
- 长会话维护:一个会话从早上聊到晚上,早期内容早被挤到窗口外面,AI却仍然按旧目标干活。
- 教学解释:让AI讲清楚一段代码和让AI直接改这段代码,需要的上下文完全不同,前者不希望出现“我帮你改了”,后者不应该输出长篇原理。
我一直强调一个观点:不要把AI当聊天对象,把它当有临时工作台的操作员。context-mode 就是在每次任务开始前,明确告诉它“这次你只需要在这张台子上工作”。
2. context-mode 的核心设计:分层、打包、切换
2.1 五层上下文模型
在搭 context-mode 之前,我先把“上下文”这个概念拆成了五个层级。明确层级之后,才知道模式卡应该管哪一层、项目规则文件应该放哪一层、临时指令又应该放在哪一层。
| 层级 | 内容 | 生命周期 | 典型示例 |
|---|---|---|---|
| 系统级 | 通用角色、价值观、回答风格 | 整个对话 | “你是一位严谨的Python工程师” |
| 项目级 | 仓库结构、命名规范、技术栈 | 整个项目周期 | “本项目使用src布局,测试放在tests” |
| 模式级 | 当前任务的边界、禁忌、输出格式 | 当前任务 | “本次只做只读审计,不改任何文件” |
| 会话级 | 本次对话的具体目标、临时变量 | 单个会话 | “本次要梳理scripts到src的迁移顺序” |
| 消息级 | 单条指令的补充信息 | 单轮对话 | “先只看utils.py,其他文件先不读” |
之前我所有上下文都混在同一段提示词里,模型需要自己判断哪些信息是这次任务该激活的。结果就是系统级和项目级还能记住,模式级经常被后面的对话覆盖,会话级和消息级又经常互相冲突。
2.2 模式卡的基本结构
模式卡是 mode-level 上下文的载体。我用的最小结构是 YAML,因为可读性好,也能方便被脚本处理。下面是一张实际在用的重构模式卡:
id: refactor name: 重构模式 description: 用于小范围重构,保持外部行为完全不变 role: 高级Python工程师 scopes: - src/**/*.py - tests/**/*.py forbidden: - 修改公开函数签名 - 新增第三方依赖 - 重排无关的import - 改动注释风格 format: - 先输出本次改动的文件列表和影响范围 - 再输出 diff - 最后输出测试建议 budget: summary这里有三个关键点,值得展开。
第一,scopes一定要用具体目录,不要写“整个仓库”。AI是严格按照字面路径理解范围的,你把全仓库暴露给它,它就会在全仓库里“找活干”。
第二,forbidden比“希望你保持接口稳定”这种正向描述有效得多。正向描述是一种意图,负向描述是一种校验规则。模型对“不要改签名”的执行率远远高于“尽量保持兼容”。
第三,budget字段表示这次上下文预算策略。full是注入完整文件,summary是只注入摘要,retrieval是按需检索。预算字段控制的是“把多少资料塞进模式卡”,防止模式卡本身成为新的负担。
2.3 三种上下文预算策略
有了模式卡,紧接的问题是:文件那么多,全塞给AI吗?当然不行,即使窗口能装下,信噪比也会降低。我一般按三种策略分配。
第一种是完整注入。只适用于核心小文件,比如单个脚本、一个类文件。完整注入的好处是模型看到的和真实情况完全一致,不会出现“它以为函数长这样”的幻觉。
第二种是摘要注入。适合大文件或整目录。做法是让AI先读一遍生成结构化摘要,摘要里保留函数签名、关键常量、对外接口,然后拿摘要作为后续工作的上下文。这个策略省token,但有失真风险,摘要里漏掉的细节模型会自己脑补。
第三种是按需检索。模式卡只告诉AI“相关信息在 docs/ARCHITECTURE.md 和 src/utils.py 里,按需打开”,然后在具体执行时让AI自己判断需不需要读。这个策略需要模型具备较高的工具调用自觉性,碰上强模型很好用,碰上弱模型容易偷懒。
选择策略的标准就一条:改动的风险越高,越要靠近完整注入;改动越外围,越可以用摘要或按需检索。比如纯排版调整用摘要就够了,但改核心服务接口就必须完整注入。
3. 从零落地一套 context-mode:目录、命令与模板
3.1 存储结构与命令脚本
我建议把模式卡放在~/.context-modes/目录里,按项目分子目录。整体结构如下:
~/.context-modes/ global.yaml legacy-tool/ audit.yaml refactor.yaml webapp/ review.yaml debug.yamlglobal.yaml放所有项目通用的约束,比如“禁止编造不存在的API”“不确定的时候先提问”。各项目目录下放针对具体仓库的模式卡。
然后写一个简单的命令行脚本,我仍然叫它context-mode。不用太复杂,把模式卡列出、查看、复制到剪贴板就够了。
#!/usr/bin/env bash # context-mode:查看和切换模式卡 set -euo pipefail MODE_DIR="$HOME/.context-modes" ACTION="${1:-list}" MODE="${2:-}" case "$ACTION" in list) find "$MODE_DIR" -name "*.yaml" | sed "s|$MODE_DIR/||" | sed 's/\.yaml$//' ;; show) cat "$MODE_DIR/$MODE.yaml" ;; use) if [ -z "$MODE" ]; then echo "usage: context-mode use <mode>" >&2 exit 1 fi echo "=== 当前模式: $MODE ===" cat "$MODE_DIR/$MODE.yaml" ;; *) echo "unknown action: $ACTION" >&2 exit 2 ;; esaccm use refactor会把模式卡内容直接打印到终端,然后我把它粘贴到AI会话的开头。可能有人觉得手动粘贴多此一举,但实测下来,粘贴这个动作会强迫我确认一次当前任务边界,比直接对着聊天窗口打字更清醒。
3.2 在会话里注入模式卡的正确姿势
模式卡不是贴一次就完事。长对话里,上下文会持续滚动,模型对模式的“记忆强度”会越来越弱。我的做法是给模式做一个“会话锚点”。
具体格式是这样:
[当前模式] refactor [会话目标] 将 scripts 目录下的工具脚本迁移到 src/tools,保持所有调用关系不变 [会话锚点] 已经确认的调用关系见上方审计结果,本次不允许再调整公共函数签名锚点本身是一段短文本,放在每轮提问前面。尤其当我连续追问很多细节时,锚点能让模型重新聚焦到当前模式。主动发锚点还有一个好处,就是让你自己形成习惯:每次输入前,先想清楚这次是在哪个会话状态下。
很多AI工具支持自定义指令或者项目规则文件,比如把项目级信息放进去。我的经验是:项目规则文件放系统级和项目级上下文,模式卡对应的 mode-level 信息还是放在会话开头最靠谱,因为模式切换的频次远高于项目规则变更。
3.3 真实案例:一次归档重构的完整走查
回到最初那个差点翻车的仓库,我用 context-mode 重新走了一遍。
第一步,建立legacy-tool/audit.yaml:
id: audit name: 只读审计 description: 摸清调用关系,不修改任何文件 role: 代码考古学家 scopes: - scripts/**/*.py - src/**/*.py forbidden: - 不要给出重构后的具体代码 - 不要假设未验证的调用关系 - 不要修改任何文件 format: - 输出脚本之间的调用关系表 - 输出被外部入口依赖的高风险点 - 输出建议迁移顺序然后我把 audit.yaml 内容发给AI,附上仓库目录树。它花了很少的 token 就产出了一份调用关系清单,列出了 5 个被 CI 直接调用的脚本、12 个彼此 import 的脚本对,以及 3 处循环 import 的高风险点。
第二步,AI产出审计结果后,我切换到 refactor.yaml,并且把审计结果作为会话锚点附在后面。refactor.yaml 里明确禁止修改函数签名,这样模型在看代码时就不会再自作主张“规范化”接口。
这次的迁移很顺利,最重要的一行指令其实是我在切换时发的锚点:“本次只允许移动文件和调整 import 路径,任何函数签名改动都必须先单独提出来确认。”
3.4 模式卡模板速查
为了方便你直接抄,我把日常常用的几类模式整理成一张模板速查表:
| 模式 | 角色设定 | scopes 举例 | forbidden 要点 | 输出格式 |
|---|---|---|---|---|
| audit | 代码考古学家 | 全仓,但只读 | 禁止给重构代码 | 影响清单、风险点 |
| refactor | 高级工程师 | src 与 tests | 禁止改签名、加依赖 | diff + 测试建议 |
| review | 审查员 | 本次 MR 涉及文件 | 禁止直接改代码 | 问题列表 + 严重级别 |
| debug | 排查工程师 | 相关模块 | 禁止大面积重写 | 原因推断 + 验证步骤 |
| explain | 讲解老师 | 用户指定文件 | 禁止贴大段代码 | 生活类比 + 关键行解释 |
4. 常见问题与排查技巧实录
4.1 模式串味:上一轮的任务污染了新一轮对话
症状是切换模式后,AI仍沿用上一个目标来理解当前问题。比如刚从 audit 切到 refactor,我让它“移动文件”,它却还是在分析调用关系,甚至输出了一堆“建议”。
原因几乎总是旧上下文残留。很多AI工具不会因为你在对话里粘贴了一条新模式卡就清空历史消息,前面的内容仍然占着上下文窗口,权重还不低。
解决办法很简单:切换模式前先重开一个会话,再粘贴模式卡。千万不要图省事在同一会话里切换模式。如果没法重开会话,至少先发一句“忽略之前所有与本次重构无关的目标”,但说实话效果不如重开会话。我在这一点上吃过好几次亏,现在已经当成铁律:模式切换 = 会话重建。
4.2 模式卡本身太长,把上下文预算吃光了
模式卡写得越长,AI越容易忽略中间的部分,而且长上下文会压缩真正代码的占比。
我的经验是每张模式卡控制在 30 行以内。超过 30 行,就要考虑是不是这个模式覆盖了太多职责。解决办法是把一个“大杂烩模式”拆成“审计”“重构”“验证”三个阶段模式,而不是让一个全能模式干完所有事。
如果模式卡里确实需要放一份很重要的架构说明,不要把完整说明写进模式卡,写一行“完整架构说明见 docs/ARCHITECTURE.md,按需阅读”就够了。
4.3 摘要失真导致的代码幻觉
使用 summary 预算策略时,AI经常会基于一份不完整的摘要开始改代码,改出来的内容是它“觉得应该有”,而不是仓库里“实际存在”的。
这种问题排查起来特别费劲,因为表面上AI回得有理有据。我后来强制自己加一条校验规则:凡是对核心文件的改动,模式卡里必须写明“先引用当前文件关键代码,再开始改动”。让模型先把要改的函数原文贴出来,相当于要求它展示证据。它引用不出来,自己就会意识到还没读文件。
4.4 多项目并行时模式卡互相污染
同时维护五六个项目时,很容易把 webapp 的规范粘到 legacy-tool 里。我在目录命名上加了项目前缀,并且每次cm use时,命令脚本会打印当前模式和对应的项目路径。这样做表面上是多打几个字,实际上可以避免“把A项目的规范用到了B项目”这种低级错误。
检查顺序我通常是这样:先cm list看全局有哪些模式,再回忆当前会话所属项目,最后才cm use。看起来麻烦,但稳定执行两周后就会形成肌肉记忆。
4.5 排查速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| AI仍按旧任务处理新问题 | 同一会话切换模式 | 重开会话再注入模式卡 |
| 关键代码被“规范”改坏 | scopes 太宽 / forbidden 没写签名约束 | 收窄 scopes,加 forbidden |
| 输出完全跑题 | 会话锚点缺失 | 在问题前补充模式锚点 |
| 摘要和实际代码不一致 | summary 策略导致失真 | 切到 full,要求先引用原文 |
| 模式卡被忽略 | 模式卡太长 | 压缩到30行内,拆模式 |
5. 一些补充,以及我自己的使用习惯
5.1 负向约束永远比正向期望好用
这是我在 context-mode 里收获最大的一点。同样一句话,“保持和谐”不如“禁止动其他文件”执行得彻底。原因在于负向约束是可以被逐字校验的,模型在生成前能快速比对是否触犯。我自己写模式卡时,会专门留出三分之一的篇幅写“不要做什么”。
举个例子,重构模式里写“不要修改函数签名”,AI在执行时看到新函数签名会立刻意识到冲突,而“注意兼容性”这种话它往往一带而过。
5.2 别把所有事情都塞给 context-mode
也有不适合用这套机制的场景。比如只问一个API用法,或者让AI帮我把一段文案改得更通顺,这种一次性任务加模式卡反而累赘。context-mode 适合的是“有稳定边界、可以重复执行、风险偏高”的任务,纯闲聊和一句话需求不需要。
还有一个心态建议:模式卡不是写得越详细越好。它应该是你和AI之间的一份“任务委托书”,而不是一本操作手册。写得太细,说明你自己还没想清楚边界。
5.3 我的最终使用习惯
现在我的日常流程已经固定成四步:进入任务前先判断风险等级,中低风险直接干活,中高风险一律套模式卡;写模式卡时先列禁止项,再列输出格式;切换模式一律重开会话;关键任务在每轮提问前发一条会话锚点。
跑了两个月,最大的变化不是AI的输出质量提升了多少,而是我自己的思维被训练出来了。每次准备给AI发指令,我都会先问自己:当前的上下文边界是什么?哪些变量在这个任务里是稳定的?哪些是绝对不能碰的?
如果以后有人问我为什么愿意多花几分钟维护这个叫 context-mode 的目录,我的回答是:它逼着我在每个任务开始前,先把自己脑子里的上下文也理清楚。