Prompt Engineering → Context Engineering → Harness Engineering被称作是现代AI系统的三大关键阶段,分别聚焦于“如何说”、“让AI看什么不看什么”以及“构建怎样的运行环境”,三者层层递进,共同致力于提升大模型在复杂任务中的可靠性与可控性。
Prompt Engineering:静态与动态信息的组装
在Claude Code这样成熟的Agent系统实践中,Prompt Engineering它不再仅仅是针对单次任务撰写一段固定的System Prompt,而是一套复杂的、动态的Prompt组装机制。
很多人认为写个漂亮的提示词就是做好了提示词工程,但在实际生产环境中,提示词应该根据身份人设、系统行为、安全守则、任务要求、工具规范、Skill要求、约束条件等动态信息进行实时拼接和组装的,以适应更加复杂多变的任务场景。这也正是为什么越来越多人开始将关注点从单纯的“提示词如何写好”转向更宏观的“提示词如何组装”的原因所在。
System Prompt的动态组装过程
用户输入后,Claude Code 不会只把这句话发给 Claude。它会并行地去拿:
1.defaultSystemPrompt、2.systemContext、3.userContext。
拿到这三部分内容之后,claude code按照优先级选取最终要使用的System prompt
优先级从高到低:
1. overrideSystemPrompt — 强制覆盖(如循环模式下使用)→ 直接返回,忽略一切
2. Coordinator prompt — 协调器模式激活时的专用 prompt
3. Agent prompt — 用户定义的 Agent 的 prompt
4. customSystemPrompt — 通过 --system-prompt 参数传入的自定义prompt
5. defaultSystemPrompt — 默认 prompt
选择完System prompt之后,之前拿到的systemContext追加到System Prompt末尾,之前拿到的userContext作为一条特殊的<system-reminder>消息,插入到用户消息列表的最前面,也就是说,systemContext 和 userContext 不参与优先级竞争。
defaultSystemPrompt——Claude Code 默认行为规则
defaultSystemPrompt分成静态部分和动态部分。
静态部分内容很稳定,不会因为项目、用户、目录变化而频繁变化。
动态部分是每个会话、每个用户、每个项目可能都不同的内容,因为这些内容会变,所以不能和静态部分混在一起缓存。
.
├── 静态部分
│ ├── 身份介绍
│ ├── 系统行为规则
│ ├── 任务执行指南
│ ├── 操作安全守则
│ ├── 工具使用指南
│ ├── 语气和风格
│ └── 输出效率要求
├── 动态边界——边界标记
└── 动态部分
├── 会话特定指导
├── 自动记忆
├── 内部模型覆盖
├── 环境信息
├── 语言偏好
├── 输出风格
├── MCP 服务器指令
├── 临时文件目录
├── 函数结果清理
├── 工具结果总结提示
├── 长度锚点
├── Token 预算
└── KAIROS 简报
systemContext——当前系统/项目环境
systemContext是git状态信息。
让模型知道自己在哪个repo里、能用什么工具、哪些操作需要审批、哪些目录可以写。
userContext——用户和项目上下文
userContext是“这个项目/用户有哪些额外约定”。例如CLAUDE.md、日期、项目记忆
Context Engineering:引导、压缩和记忆
CLAUDE.md文档的使用
在userContext中的CLAUDE.md被claude code注入为对话的第一条消息,内容通常为用户编辑的:
这个项目是一个基于何种语言的项目,采用什么样的管理模式,后端 API 在哪里实现。
在编码规范上,使用那种函数或组件,变量命名遵循什么样的风格,选用哪种测试框架。
行为约束、常用命令,以及一些描述项目特殊约定等等。
CLAUDE.md有四种存放路径,不同的路径存放的内容且功能也不同:
- 个人通用偏好类:通常位于 ~/.claude/CLAUDE.md。它的特点是跨项目生效,属于用户维度的静态配置,适合定义开发者个人的全局设置。比如“始终用中文回复”、“简洁的代码风格”等。
- 项目共享规范:通常放置在项目根目录下的CLAUDE.md。它的核心价值在于“标准化”,确保团队内所有成员对项目的理解是一致的,避免因信息不对称导致的幻觉或错误实现,因此必须提交到 Git版本管理中。这里可以包含项目架构说明、统一的编码规范、构建命令等公共知识。
- 个人私有指令:对应 CLAUDE.local.md文件。它用于存储那些“不该公开”但又是当前开发者必需的上下文,例如“我负责XXX模块”、“我的测试账号是xxx”等敏感或个性化信息。由于涉及隐私或特定环境配置,这类文件明确不应提交到 Git,从而在享受个性化定制的同时,保障了代码仓库的安全性。
- 按文件类型分类的规则:通过 .claude/rules/*.md目录来实现。随着项目复杂度不断提升,通用的项目规范可能无法覆盖所有场景,这时就需要按文件类型或业务领域进行拆分。例如,我们可以分别定义前端规则、后端规则、测试规则等,甚至利用Frontmatter来限定某些规则仅在特定文件路径下生效。这种模块化的管理方式,让claude code在处理具体任务时动态加载最精准的上下文,既避免了上下文窗口的浪费,又极大提升了指令执行的准确度。
Claude Code的上下文管理
claude code采用了一套按照推理成本递增的三层渐进式压缩体系:
1.MicroCompact、2.Session Memory Compact、3.Full LLM Compact
在此基础上,官方Cookbook文档中我们可以看到官方明确要求模型在输出最终摘要前需要先在<think>标签内进行全面分析。除此之外,Cookbook中提供的示例中在压缩进行中传递消息时并未给模型传递任何tools,让摘要器自然成为一个只读、纯文本的压缩步骤。
Cookbook提供的提示词:
SESSION_MEMORY_PROMPT = """
将会话压缩为一份结构化摘要,
保留继续工作所需的全部信息。请优先优化助手继续执行任务的能力,
而不是让摘要便于人类阅读。
<analysis-instructions>
在生成摘要之前,请先在 <think>...</think> 标签中分析会话记录:
1. 用户最初请求了什么?(保留准确原话)
2. 哪些操作成功了?哪些失败了?失败原因是什么?
3. 用户是否曾在任何时候纠正、调整或重新引导助手?
4. 会话结束时,正在积极进行什么工作?
5. 哪些任务尚未完成或仍处于待处理状态?
6. 哪些具体细节(ID、路径、数值、名称)必须在压缩后保留下来?
</analysis-instructions>
<summary-format>
## 用户意图
用户最初的请求,以及后续的任何补充、修订或细化。对于关键要求请使用直接引语。
若用户目标在对话过程中发生演变,请记录这一变化过程。
## 已完成工作
已成功完成的操作。请具体说明:
- 创建、修改或删除了什么
- 准确标识符(文件路径、记录 ID、URL、名称)
- 已应用的具体数值、配置或设置
## 错误与纠正
- 遇到的问题,以及问题如何解决
- 已失败的方法或方案(避免后续重复尝试)
- 用户纠正,例如:“不要做 X”、“其实我的意思是 Y”、“这不对,因为……”
请逐字保留用户的纠正内容——这些代表已经学习到的偏好。
## 当前工作
会话结束时正在进行的事项。包括:
- 正在执行的具体任务
- 能准确说明工作停留位置的直接引语
- 任何部分结果或中间状态
## 待处理任务
用户提出但尚未开始的剩余事项。
请区分“用户明确要求”与“根据上下文推断 / 假定的事项”。
## 关键引用
继续工作所需的重要细节:
- 标识符:ID、路径、URL、名称、密钥
- 数值:数字、日期、配置、凭据(已脱敏)
- 上下文:相关背景信息、约束条件、偏好
- 引用:会话中提及或使用过的信息来源
</summary-format>
<preserve-rules>
出现以下内容时,务必保留:
- 准确标识符(ID、路径、URL、密钥、名称)
- 原始错误信息
- 用户纠正和负面反馈
- 具体数值、公式或配置
- 已发现的技术约束或需求
- 任何进行中工作的准确状态
</preserve-rules>
<compression-rules>
- 更重视最近的消息——对话末尾通常代表当前的活跃上下文
- 删除客套、致谢和填充内容(如“当然!”、“这是个好问题”)
- 删除会被单独重新注入的系统上下文
- 每个栏目控制在 500 词以内;压缩较早内容,为较新的内容留出空间
- 若必须删减细节,请按以下优先级保留:
用户纠正 > 错误 > 当前工作 > 已完成工作
</compression-rules>
"""
MicroCompact——纯规则驱动,轻量化压缩
claude code定义了一个可压缩工具白名单,仅针对如 Bash、Read、Grep、Glob 等产生大量标准输出的工具进行压缩处理;而对于 Edit、Write 等涉及核心状态变更的操作,其输出则被完整保留,以确保后续决策的准确性。 在处理多模态内容时,会把图像按固定规则转换为可计量的visual token,查阅官方文档可知图片按 28×28 像素块划分,每个块就是一个visual token,因此,一张图像的成本为⌈width / 28⌉ × ⌈height / 28⌉个visual token。如果收到的图像过大,图片会先先被缩放,再按缩放后的尺寸计算visual token。每张图像的最大尺寸为 8000x8000 像素,如果在一个API请求中提交超过20张图像,此限制将降低至 2000x2000 像素。
Session Memory Compact——基于已有会话记忆进行会话记忆压缩,无推理成本
当MicroCompact难以缓解上下文压力时,claude code进入第二层压缩,它会直接利用在之前的交互中可能已经生成过高质量的会话记忆替换冗长的原始历史消息,此过程无需调用LLM进行新的总结。 传统对话压缩通常是:
不断聊天、读文件、运行命令
↓
上下文快满了
↓
暂停当前工作
↓
把整段历史交给模型总结
↓
用总结替代旧历史
↓
继续工作
这种做法的问题在于当上下文满的时候需要临时发送一次“请总结整段会话”的模型请求,用户会感觉到一次额外的等待。 而claude code把“写总结”这件事提前了:
会话刚开始
↓
正常聊天、读文件、调用工具
↓
达到较早的软阈值
↓
后台生成 Session Memory
↓
后续每隔一段时间,后台更新 Session Memory
↓
真正接近上下文上限
↓
直接用现成的 Session Memory 替换旧历史
因此当上下文快满触发了Session Memory Compact后claude code的历史就变成了:
[Session Memory 摘要]
[最近还没有被总结的几轮消息]
[当前用户的新请求]
所以更准确地说:claude code是完全不做总结,也不是完全不调用模型;而是把总结计算提前到后台完成。
Full LLM Compact——LLM参与的全量压缩
如果前两层压缩依然无法将上下文控制在安全范围内,或者任务场景极其复杂,claude code会调用 LLM 进行全量压缩,这一步并非简单的“请帮我总结”,它强制模型遵循一套严格的9 段式结构化模板:
1. Primary Request and Intent——用户最初想做什么,以及需求后来有没有变化
2. Key Technical Concepts——本次任务涉及的重要技术点、约束、术语或方案
3. Files and Code Sections——读过、改过或需要继续关注的文件、路径、函数、代码位置
4. Errors and fixes——遇到过什么错误、原因是什么、如何修复,避免以后重复踩坑
5. Problem Solving——已经尝试过哪些分析或解决步骤,得到什么结论
6. All user messages——保留用户所有关键指令,防止压缩后遗漏需求或偏好
7. Pending Tasks——用户明确要求但尚未完成的事项
8. Current Work——压缩发生时,模型正在做什么,进展停在哪里
9. Optional Next Step——下一步最合理的动作(可继续做,但未必是用户明确要求)
Memdir结构化记忆系统
随着交互轮次的不断增加,项目可能会进入长周期时间,Claude Code 是如何做到能够记住项目的目标、要求和已经开发过哪些内容呢? Claude Code 设计了一套名为 Memdir 的结构化记忆机制。为什么强调“结构化”?因为非结构化的记忆虽然灵活,但在实际工程中极易导致上下文膨胀和检索噪 声。这套机制将记忆明确拆解为四种核心类型,每种类型承载不同的业务语义:
- User(用户级):记录用户的个人偏好、操作习惯及特定指令风格,让 Claude Code 越用越懂你;
- Feedback(反馈级):存储模型行为的修正记录和历史纠错案例,形成“避坑指南”,防止同类错误复发;
- Project(项目级):固化项目层面的技术选型、架构决策和约束条件,确保多轮对话中技术立场的一致性;
- Reference(参考级):沉淀通用的文档片段和代码模式,作为高频调用的知识底座。
有了分类,接下来的挑战是如何高效地加载这些记忆而不拖慢响应速度。 Claude Code在
memdir/memdir.ts中实现了loadMemoryPrompt作为记忆加载的主入口。这个函数并非简单的文件读取,而是一个精密的“过滤器”:它首先扫描记忆目录,将分散的记忆条目按上述四种类型进行归类整理;紧接着,它会严格应用预算限制,根据当前任务的上下文窗口大小,动态裁剪记忆内容;最后,生成格式化后的记忆提示词注入到 Prompt 中。 这一步至关重要,它确保了进入 LLM 上下文的每一字节都是高价值的,避免了因记忆过载导致的“注意力分散”。当然,仅仅依靠规则过滤在面对海量记忆时依然显得力不从心。当记忆库规模扩大,如何从成千上万条记录中精准捞出当前最需要的几条?Claude Code引入了 LLM-in-the-loop 的检索策略。在 memdir/findRelevantMemories.ts 中,Claude使用的是Sonnet模型来理解语义驱动检索过程。系统不再依赖简单的关键词匹配或固定的相似度阈值,而是让大模型亲自充当“图书管理员”,对候选记忆进行语义相关性判断,并强制约束其只返回最多5条最相关的记忆。这种设计巧妙地平衡了“召回率”与“精确度”:一方面利用大模型的推理能力解决了传统检索在复杂语义下的失效问题,另一方面通过数量限制严格控制了 Token 消耗和延迟。从静态的规则组装到动态的 LLM 语义筛选,这套记忆体系让 Claude Code 不再是“用完即走”的一次性工具,而是具备了持续学习和自我修正能力的AI Coding Agent。
———阿里云开发者公众号《深度解析Claude Code在Prompt/Context/Harness的设计与实践》
Harness Engineering:环境、约束与控制
系统级强提醒引导Agent
在多轮对话的用户消息流中,模型极易混淆“用户输入”与“系统指令”。针对此问题,claude code的做法是将所有需要注入系统的元信息(如配置文件内容、日期、工具执行结果等)统一包裹在<system-reminder>...</system-reminder>标签中。通过这种显式的标签隔离,系统能够向模型清晰地传达:“这部分内容是系统注入的元信息,而非用户的自然语言输入”,从而有效避免了模型对上下文的误解或指令跟随的偏移。 <system-reminder>几乎贯穿了claude code交互的全生命周期:
- 用户上下文初始化:在第一条用户消息发送前,系统会自动注入CLAUDE.md的项目规范、当前日期等基础信息,为 Agent 设定初始认知框架。
- 工具结果反馈:当 Agent 调用工具完成后,工具的输出(如文件读取内容、记忆片段)会被包裹进该标签追加到对话历史中,确保模型能基于最新的执行结果进行推理。
- Hook反馈:在复杂的自动化流程中,Hook 的执行结果同样通过此机制注入,让模型实时感知流程状态。
- 周期性任务与能力描述:无论是待办任务的状态提醒,还是会话级别的技能列表(Skill List)、可用代理类型(Agent List),都通过这种标准化的方式动态挂载到上下文中。这种多维度的注入策略,保证了 Agent 在任何时刻拥有的上下文都是完整、即时且结构清晰的。
claude code系统内置的Agent
不同任务不应该都交给同一个、全权限、昂贵模型去做。更好的做法是把任务拆成不同角色:
搜索代码 → 快速、只读、便宜的 Agent
做方案设计 → 更擅长推理、只读的 Agent
改代码 → 有写权限的 Agent
验证结果 → 专门找问题、尽量不改代码的 Agent
回答产品用法 → 文档导向的 Agent
配置状态栏 → 小范围、固定任务的 Agent
Explore subagent
一个快速的、只读的代理,针对搜索和分析代码库进行了优化。它不负责改代码,因此通常只给只读工具。它的调查结果留在独立上下文中,只返回最终结果,避免主会话被大量搜索输出淹没。
Plan subagent
继承主会话模型,在真正改代码前,先用只读方式理解现有架构、类似实现、关键依赖和风险。 工作流程:
理解需求
↓
深入探索代码库(找已有模式、相似功能)
↓
设计解决方案(考虑权衡和架构决策)
↓
详细规划(步骤、依赖、风险)
General-purpose subagent
继承主会话模型,拥有全部工具。适合跨文件分析,多步骤操作需要读、写、跑命令的任务,依赖前一步结果的复杂流程。
Verification subagent
专门找问题、不轻易 PASS 的代理。
设计哲学一:红蓝对抗
它的开场白就奠定了基调:“You are a verification specialist. Your job is not to confirm the implementation works — it's to try to break it.”,翻译一下就是:你是验证专家。你的工作不是确认代码能跑——而是想办法把它搞崩。这是经典的红蓝对抗思维,就是专门给代码挑刺的,让Agent自己发现问题所在。设计哲学二:不要随便给PASS
Verification的System Prompt里,毫不留情地指出了它在做验证时需要避免的两个“典型问题”:验证逃避:“面对一个检查项,你会找各种理由不去真的运行它——你读读代码,叙述一下你‘会’测试什么,写上 PASS,然后就溜了。”被前80%迷惑:“你看到一个漂亮的 UI 或者通过的测试套件,就倾向于给 PASS,而没注意到一半按钮其实什么都不做,状态刷新后就消失,或者后端在遇到坏输入时直接崩溃。前 80% 是容易的部分。你的全部价值在于找到最后那 20%。”设计哲学三:严格的权限控制
它只能看,不能改。唯一的例外是可以往 /tmp 写临时测试脚本(用Bash重定向),用完要自己清理。它在对话过程中会被反复注入提醒:“CRITICAL: This is a VERIFICATION-ONLY task. You CANNOT edit, write, or create files IN THE PROJECT DIRECTORY.” 它不被允许调用各种工具,比如:不能再生成子Agent、不能退出计划模式、不能编辑文件、不能写文件、不能编辑笔记本等等。设计哲学四:按变更类型分类的验证策略
在System Prompt里为十几种变更类型定义了专门的验证策略,主要有下面这些变更类型:
前端变更:启动开发服务器 → 浏览器自动化 → 检查子资源加载
后端/API:启动服务 → curl 测试端点 → 验证响应结构 → 测试错误处理
CLI/脚本:用代表性输入运行 → 验证 stdout/stderr/退出码
基础设施:语法验证 → 干运行(terraform plan, kubectl --dry-run)
Bug修复:先复现 Bug → 验证修复 → 回归测试
数据库迁移:运行迁移 → 验证 schema → 测试回滚(可逆性)
重构:现有测试必须不改动地通过 → diff 公共 API
移动端:清理构建 → 模拟器安装 → dump UI 树 → 点击验证设计哲学五:反偷懒话术
在System Prompt里有一组“AI 常见的自我开脱话术”,然后逐一拆穿,列举一下:
代码看起来是对的 —— 看起来不是验证,运行它
实现者的测试已经通过了 —— 实现者也是 AI。独立验证
这大概没问题 —— “大概”不是“验证过了”,运行它
让我启动服务器然后看看代码 —— 不,启动服务器然后打断点
我没有浏览器 —— 你检查过有没有playwright MCP工具
这个太耗时了 —— 不是你说了算的
在写解释而不是运行命令 —— 停下来,运行命令
Guide subagent
当用户问Claude Code“怎么用”这类问题时,它会被唤起,然后去官方文档网站查文档,最后基于文档给出回答。
Statusline subagent
门负责帮用户配置终端状态栏
claude code安全防御体系
Permission Engine——规则的精细化权限控制
它负责在工具调用发生前进行快速的逻辑判定。其核心在于定义清晰的“三行为模型”:
- Allow(自动允许):针对低风险、高频次的操作,直接放行以保障效率。
- Deny(自动拒绝):针对明确禁止的高危操作,直接阻断。
- Ask(请求确认):针对不确定或中等风险的操作,暂停执行并提示用户介入确认。
为保证灵活性,claude code该引擎通常支持多源规则配置,并遵循严格的优先级覆盖机制:settings.json(全局配置)→ CLI 参数(启动时指定)→ 命令行规则 → session 规则(会话级动态规则)。
当 Agent 发起工具调用时,引擎会立即检索匹配规则,输出判定行为。
Sandbox Isolation——操作系统原型的沙箱隔离
claude code引入了操作系统级别的隔离机制,此机制提供了硬核的物理隔离能力:
- 文件系统隔离:通过只读挂载根目录和白名单目录机制,防止 Agent 随意篡改系统关键文件。
- 网络与进程隔离:利用独立的 Network 和 PID 命名空间,限制网络访问范围,防止进程逃逸。
- 用户权限降级:强制以非 root 用户身份运行,从源头上杜绝提权风险。
沙箱并非“一刀切”,它会检测命令特征采用“按需隔离”的策略。对于那些需要交互式终端(TTY)、特殊网络设备或不兼容沙箱环境的命令,系统会自动识别并将其排除在沙箱之外,转为直接执行(配合更严格的权限校验)。
可编程的Hook拦截机制
| 钩子名称 | 触发时机 | 所属生命周期 |
|---|---|---|
| PreToolUse | 工具调用前 | 工具生命周期 |
| PostToolUse | 工具调用后 | 工具生命周期 |
| ToolError | 工具执行出错 | 工具生命周期 |
| SessionStart | 会话开始 | 会话生命周期 |
| SessionEnd | 会话结束 | 会话生命周期 |
| SessionPause | 会话暂停 | 会话生命周期 |
| SessionResume | 会话恢复 | 会话生命周期 |
| PreSampling | 模型采样前 | 消息生命周期 |
| PostSampling | 模型采样后 | 消息生命周期 |
| UserPromptSubmit | 用户提交输入 | 消息生命周期 |
| PreFileEdit | 文件编辑前 | 文件操作 |
| PostFileEdit | 文件编辑后 | 文件操作 |
| PreFileWrite | 文件写入前 | 文件操作 |
| PostFileWrite | 文件写入后 | 文件操作 |
claude code的Hook机制的强大之处不仅在于“监听”,更在于“干预”。所有 Hook 的执行结果都支持返回结构化的 JSON 数据,从而赋予外部脚本直接修改系统行为的能力:
- 阻断执行:返回 { "blocked": true, "reason": "..." } 可直接熔断高危操作,作为安全沙箱之外的第二道软性防线。
- 动态篡改:通过 { "input": {...} } 或 { "output": {...} },Hook 可以实时修正工具的输入参数(例如自动补全缺失的路径)或清洗输出结果(例如脱敏敏感信息),而无需修改 Agent 核心代码。
- 反馈注入:利用 { "message": "..." },Hook 可以向对话流中插入系统提示或用户通知,实现人机交互的增强。
- 这种配置通常集中在
settings.json中,通过声明式的方式定义匹配规则(如 match: { "tool": "Edit" })和执行命令(如 command: "my-linter --check"),极大地降低了使用门槛,让非核心开发人员也能轻松扩展 Agent 能力。
claude code还在工程层面为Hook机制引入了严格的超时保护机制,在hooks.ts中,定义了全局常量TOOL_HOOK_EXECUTION_TIMEOUT_MS(默认10分钟)。任何Hook的执行一旦超过此时限,将被强制终止并抛出超时错误。这一设计确保了即使外部插件表现不佳,也不会拖垮主进程,保障了 Agent 整体运行的鲁棒性和可用性。
