学习目标
读完本章,你应当能够回答一个看似简单、实际上经常被回答错的问题:“这个 Agent 的提示词是什么?”你不会只复制一段 system 文本,而是会先询问这段文字来自哪里、属于哪一个消息角色、在什么条件下被拼进请求、会不会在下一轮被重建、哪些工具定义同时改变了模型的可行动空间,以及最终请求是否真的把它发给了模型。你还会得到一套可以迁移到新 Agent 的检查顺序:先找来源,再画组装图,接着抓运行时请求,最后用可重复的测试验证解释。
本章的核心观点是:提示词是运行时系统中的一组可观察约束,不是一句孤立的“人格描述”。一段自然语言只有在被放进特定协议、由特定代码组装、与特定工具和状态一起发送时,才对 Agent 的行为产生确定意义。这里的“确定”不是说模型输出一定确定,而是说我们可以确定哪些字节进入了请求、以什么角色进入、何时可能变化、由哪个组件负责。OpenAI 的官方 Prompt engineering 文档把 instructions、消息角色、上下文、示例、版本化和评测放在同一套工程流程里[^src-openai-prompt];Anthropic 则提醒开发者先从简单、可组合的模式开始,并把工具接口当作 Agent-computer interface 来设计[^src-anthropic-agents]。本章把这些建议与四个真实开源 Agent 的实现连接起来。
图 01 说明:外圈不是装饰,而是提示词在真实系统中的四个依赖层。图片由 imagegen 按 content/course/figures/f01.json 的图稿说明生成,最终资产需保留生成提示词和来源记录。
1. 先区分四种“提示词”
在研究 Agent 时,最容易犯的错误是用同一个词指代四个不同对象。第一种是“文案”,也就是某个 Markdown、字符串常量或模板文件里可直接阅读的自然语言。它回答的是作者写了什么。第二种是“模板”,它除了文案还包含变量、条件分支、循环或消息边界,例如只有启用工具时才出现的段落,或者根据是否非交互运行而改变的身份句。模板回答的是系统在什么条件下会写入什么。第三种是“组装结果”,它是某一次运行、某一组工具、某一个工作目录、某一个模型和某个会话状态下生成的完整消息。组装结果回答的是这次运行准备发送什么。第四种是“请求证据”,也就是通过网关、SDK mock、HTTP dump 或测试断言得到的实际请求 body。请求证据回答的是究竟发出了什么。
这四种对象可能相同,也可能相差很大。Pi 的 buildSystemPrompt() 是模板和组装器的结合体;在禁用工具、技能、项目文件和提示词模板时,它可以产生一段很短的 system prompt,但只要打开一个工具,Available tools 和指导原则就会变化。Hermes 的 agent/system_prompt.py 将 stable、context、volatile 三层拼接,而上下文文件、技能索引、记忆快照和时间戳又来自不同路径。Codex 的基础指令以独立 developer 内容进入 Responses 请求,权限和技能等 context fragment 可能在另一个消息中追加。Grok Build 用 MiniJinja 模板和条件变量生成 system prompt,并在构建时把模板按位置相关的 XOR 方式混淆进二进制。把这些都压扁成“某 Agent 的完整提示词”,会同时丢失来源、条件和版本。
研究笔记中的术语约定如下:凡是能从上游仓库固定提交直接读取的内容,标为 source;凡是包含变量和条件的内容,标为 template;凡是由代码在运行中拼出的内容,标为 assembly;凡是从捕获网关取得的请求,标为 runtime capture。课程中的解释、推断和建议使用 interpretation 标记。解释可以很有价值,但不能替代前三类证据。这种分栏方式也回应了 Weng 对 Agent 三组件的总结:规划、记忆和工具共同构成系统,不能只从“脑内文字”理解 Agent[^src-weng-agents]。
2. 身份句只是入口,不是全部行为
很多公开提示词以 “You are ...” 开头,因此读者会把身份句误认为核心。身份确实重要,它规定了模型被期待扮演的角色、语气和任务范围,但它通常只解决“你是谁”和“应该朝哪里努力”。它不自动解决“什么数据可信”“什么时候必须调用工具”“工具失败后做什么”“怎样证明完成”“哪些路径不能访问”。在四个样本中,身份句后面都有更具操作性的层:Hermes 有完成工作、工具使用和平台提示;Codex 有 AGENTS.md 作用域、计划、验证和最终答案格式;Grok 有 work policy、tool calling、background tasks 和 browser verification;Pi 有可见工具清单、指导原则、文档路径和当前工作目录。
身份句还有一个运行时陷阱:它可能根据模式变化。Grok 的模板在交互模式和非交互模式使用不同句子;Hermes 的 SOUL.md 可以替换默认身份;Codex 的基础指令按模型家族选择;Pi 可以用 custom prompt 完全替换默认主体。因而“身份句相同”不能证明 Agent 行为相同,“身份句不同”也不能证明整体行为不同。审计时应把身份当成第一段可定位证据,然后继续寻找实现完成任务的规则和工具协议。
一个实用练习是把身份段拆成三列。第一列写角色声明,例如“coding assistant”。第二列写目标,例如“complete the user request”。第三列写可验证动作,例如“read files, execute commands, edit code”。如果第三列为空,说明你看到的更像产品文案,而不是完整 Agent contract。若第三列存在,再检查这些动作是否在工具 schema 和循环代码中有对应实现。只有文本、工具和循环相互指向时,身份才落地成系统行为。
图 02 说明:同样一行“请总结文件”,放在 developer、user 或 tool result 中,模型收到的权限和可信度不同;图中用颜色标出角色,而不是把颜色误当作自然语言内容。
3. 角色是协议字段,不是排版标题
在 Markdown 里写 # Instructions 只是文本标题;在 API body 里设置 role: developer 或使用 Responses API 的 instructions,才是协议层的角色。二者经常同时出现,但作用不同。标题帮助模型在一条消息内部理解结构,角色影响消息之间的指令优先级。OpenAI 官方文档明确说明,instructions 具有比 input 更高的优先级,developer 消息优先于 user 消息;同时,instructions 只作用于当前 Responses 请求,使用 previous_response_id 时不会自动继承前一轮的 instructions[^src-openai-prompt]。这意味着“前一轮已经告诉模型的规则”不能想当然地当作本轮仍在上下文中。
角色边界还决定了如何解释不可信内容。网页、文件和工具返回经常包含看起来像指令的自然语言。它们通常属于 user 或 tool result 数据,不应因为文本中出现“system message”就升级权限。Instruction Hierarchy 论文把这一问题概括为不同权限指令冲突时的优先级问题,说明安全不能只靠模型看到的字符串形式。虽然本章暂不深入安全实现,但你现在已经可以建立一个重要习惯:在复制提示词时把 role 和 content 一起保存,在网页上把角色作为证据字段显示,而不是只显示一块混合文本。
四个 Agent 的角色设计提供了不同的对照。Codex 捕获里有独立的 instructions 和 developer 段,这种边界让模型基础指令与运行时技能/权限片段可分别追踪。Hermes 和 Pi 的默认调用往往把组装后的主体放在单个 system 消息中,但其内部仍有 stable、context、volatile 或工具、项目、技能等语义层。Grok 的二进制请求在本次配置中使用 system 消息,而模板内部用 <work_policy>、<communication> 等标签区分逻辑段。不要因为 API 只给你一个 system 字段,就假设系统只有一层;也不要因为文本有许多标题,就假设它们都是协议角色。
4. 工具把提示词变成行动空间
一个工具定义至少包含名称、描述、输入 schema、执行边界和返回格式。模型在决定下一步时,不仅读取自然语言提示词,还会读取工具列表;工具列表的存在改变了它能采取的动作。Anthropic 的工具工程附录建议让参数名、边界、示例和错误情形足够清晰,并指出工具格式的“人机界面”投入会直接影响 Agent 表现。SWE-agent 的研究也将 Agent-computer interface 作为影响软件工程性能的独立变量[^src-anthropic-agents]。因此,研究提示词时不能只下载 system prompt,还要记录同一次请求里的工具 schema 或至少记录可见工具名字。
工具结果又会回到下一轮上下文。一次完整的 tool loop 通常是:模型提出结构化调用,执行器校验参数并运行,执行器生成工具结果,框架将结果带回模型,模型再选择继续、修正、请求审批或结束。任何一环都可能改变提示词的有效语义。例如工具描述说“需要绝对路径”,但模型只看到相对路径示例;或者返回值没有区分错误与空结果,模型就可能把失败当成“没有数据”。这也是为什么工具的错误返回和调用示例应当与总提示词一起做版本控制。
图 03 说明:图中的箭头标签必须写明“validate”“execute”“append result”,这样读者知道每条边代表什么,而不是看到几个相关框就自行猜测。
工具是否可见也可能是条件化的。Pi 的 builder 只在传入 toolSnippets 时把工具写入 Available tools,并根据工具集合添加文件探索和其他指导;Hermes 根据 valid_tool_names 决定记忆、skills、工具强制和平台提示段;Codex 的权限与环境片段取决于本次运行配置;Grok 的模板变量根据工具种类决定是否包含后台任务、编辑规则和浏览器验证。一个“静态完整提示词”若不附带工具集合,就无法解释这些分支。
5. Agent loop 解释“为什么要继续”
Workflow 可以沿固定路径调用模型,Agent 则允许模型根据观察选择下一步。Anthropic 对二者的区分不是字面游戏:固定路径适合可预测任务,动态 Agent 适合步骤数和工具选择难以预先写死的开放任务。ReAct 论文以 reasoning、action、observation 交替来描述这种循环,并报告行动取得的外部信息可以减少纯推理中的幻觉和错误传播[^src-react]。在生产系统里,reasoning 是否展示给用户是另一个问题;循环本身不等于必须暴露思维链。
把 Agent loop 画成状态机比写“模型会思考”更可靠。至少应有 idle、requesting、tool-running、observed、waiting-for-approval、completed 和 failed 等状态。每一条转换都要写触发条件:收到用户请求、模型发出工具调用、工具返回成功、工具返回错误、审批被拒绝、达到最大迭代次数、最终结果通过校验。停止条件同样属于提示词系统,因为提示词可能要求“持续完成”,而运行器可能还有总回合上限、超时、token 上限或人工检查点。
图 04 说明:时序图把模型、执行器、环境和用户分成泳道,重点显示观察从工具返回并被添加到下一次请求,而不是留在执行器内部。
一个常见错误是把计划当成执行本身。Codex 的计划规则要求使用 update_plan 跟踪进度,但真正的完成仍需工具输出支持;Hermes 的 finishing guidance 也禁止在写完 stub 或运行单条命令后停下。计划是状态的可读投影,工具结果才是环境真值。对于教程读者,这意味着评价一个 Agent prompt 时,要同时检查它是否描述了计划、是否规定了反馈、是否有可观察的完成门槛,不能只数“plan”出现了几次。
6. 稳定层和易变层为何要分开
提示词中的内容有不同生命周期。身份、长期工具说明和输出格式通常在同一个会话中保持稳定;当前工作目录、模型、时间戳、用户消息、工具结果和短期记忆则可能变化。把所有内容每回合重新拼接,可能破坏缓存、浪费 token,甚至让旧内容和新内容互相矛盾。反过来,把所有内容冻结,也会让上下文陈旧,或者把本应只在当前任务有效的个人数据带入下一次任务。
Hermes 文档把系统提示词分为 stable、context、volatile 三层,并特别说明缓存稳定性是重要设计选择。Pi 在重新构建 system prompt 时把自定义主体、追加提示词、项目上下文和技能按规定顺序连接。Codex 把基础指令作为独立片段,再由权限、用户规则、skills 和应用上下文组成 developer 消息。Grok 通过模板条件和运行时 placeholders 形成最终 system prompt。四者的命名不同,但都在解决同一工程问题:哪些字节可复用,哪些字节要随请求更新,哪些内容应在消息角色中隔离。
图 05 说明:层叠顺序不是视觉优先级;它展示内容生命周期。一个易变层可以拥有较高语义优先级,但不应因此破坏稳定前缀的缓存设计。
练习时可给每段内容贴三个标签:lifetime(会话、请求、工具回合或跨会话)、trust(代码、配置、用户、外部数据)和 owner(哪个模块负责生成)。当一段文字的 owner 不明时,它就是维护风险。比如“当前目录是 /work”如果由 shell 环境注入,就不应复制到通用 source prompt;如果由 Agent builder 写入,就应该在组装测试中固定输入并断言输出。
7. 上下文窗口不是无限记忆
模型能读取多少 token 取决于模型和请求,而不是取决于 Agent 在产品文案中声称“拥有长期记忆”。上下文通常由 system/developer 指令、历史消息、工具调用、工具结果、用户输入和模型输出组成。OpenAI 文档提醒上下文窗口是有限容量,并建议把与任务相关的资料放入请求,同时为重要工作设置最终检查。Lost in the Middle 研究进一步显示,长上下文中信息的位置会影响检索表现,模型不一定能平等利用中间部分。Weng 的综述把工作记忆、长期记忆和工具使用分开讨论,并强调有限通信带宽带来的限制[^src-weng-agents]。
在真实 Agent 中,记忆可以是多个东西:本回合的消息列表、压缩摘要、外部向量检索、用户 profile、项目文件、技能正文或数据库状态。它们的可信度不同、更新时机不同、删除策略也不同。Hermes 的 MEMORY.md 快照属于 volatile 系统层,Pi 的项目上下文是一次 build 的输入,Grok 的 user info 和 memory 由不同模板插槽进入,Codex 的 memories 可能通过独立 context fragment 注入。网页需要把这些来源分组,否则读者会把“能被检索到”误解为“永远正确”。
一个可复现的上下文实验应限制变量。先固定模型、工具集合、工作目录和初始历史,再只增加一个项目文件,比较捕获请求的长度、角色和插入位置。然后只改变文件内容,观察哈希是否变化。最后触发压缩或会话恢复,确认框架是追加摘要、替换历史,还是重建系统提示词。实验结果只说明该版本、该配置的行为,不能外推为所有 Agent 的永久规则。
图 06 说明:图中把窗口画成总容量,并把 system、history、tool results、user input 分成可测量的区段;它不把窗口画成无限向右延伸的“记忆云”。
8. 版本、条件和差异才是可信度的核心
“验证过”不能只表示某个人读过仓库。至少要记录仓库 URL、提交或 tag、源码路径、行号或符号、文件 SHA-256、运行版本、命令、配置和捕获哈希。OpenAI 官方提示词文档建议把生产提示词放在代码中,用版本控制、代码审查、测试和评测管理,并提醒同一模型家族的不同 snapshot 也可能表现不同[^src-openai-prompt]。这条建议直接适用于本课程的数据模型。
Codex 的研究尤其说明版本差异为何不能隐藏:本机 0.151.0 捕获的 instructions 与 rust-v0.151.0 标签模板几乎相同,但存在两个小 diff hunk;当前 main 的模板又是另一代内容。若网页只显示“Codex prompt”,读者无法判断看到的是哪个版本。Grok 的可读模板和加密数组可以通过仓库 XOR 算法逐字节互证,但发布二进制报告的 commit 与当前 main 仍要分别记录。Hermes 和 Pi 的动态目录路径会进入请求,路径本身不是可复制到另一台机器的固定事实。
图 07 说明:左栏是 source,中央是 runtime capture,右栏是 interpretation;只有左栏和中央栏能直接支持“发出了哪些字节”,右栏必须带引用和不确定性说明。
差异分析应优先使用机器可读的方法:对源文件计算哈希,对请求中的 system/developer 内容单独计算哈希,按角色分段,对 Markdown 标题和 XML 标签建立边界,然后生成 diff。人工阅读用于解释差异,不用于替代 diff。若差异来自动态值,例如当前目录或时间戳,应在网页中用变量徽章标出,而不是把整个段落判定为“源码不一致”。
9. 一个最小的捕获实验
本项目的捕获网关提供三个功能:接受 OpenAI Responses、Chat Completions 和 Anthropic Messages 形状的 POST 请求;从 instructions、system、developer 内容中提取提示词;把认证和 token 类请求头替换为 [REDACTED]。它返回 capture complete 占位响应,不调用真实模型。这个设计的教学价值在于,Agent 仍然会执行自己的请求流程,但不会消耗推理额度或向第三方发送学习内容。
运行实验时先启动网关,再为一个 Agent 设置自定义 base URL。命令必须固定工作目录、模型、工具、技能、项目规则和会话策略。实验输出至少保存:请求时间、Agent 标识、请求路径、请求 body、消息角色、每个分段字符数、组合提示词哈希和脱敏后的 headers。不要把真实 API key 作为“验证数据”;它既不能证明提示词正确,也会增加泄露风险。
实验的判定应分成三个层次。第一层是存在性:确实收到 POST,且 prompt segments 非空。第二层是结构性:角色、条件段和关键 marker 与源码预期相符。第三层是版本性:捕获使用的运行版本与源文件提交一致,或页面明确写出差异。当前验证器对四个 Agent 都通过了前两层,对 Codex 使用 verified-with-version-diff 状态,正是因为第三层发现了版本差异。可信度不是一个绿色圆点,而是一组可检查条件。
图 08 说明:同一个 prompt 名称在两个版本中沿着不同条件分支进入请求;差异应显示在具体行和条件上,而不是只显示“版本更新”。
10. 反模式:把内容数量当成理解深度
第一种反模式是收集很多“system prompt 泄露”文本,却不记录来源和日期。文本可能来自旧版本、二次转述、用户自定义或模型输出,无法证明是代码真正发送的内容。第二种反模式是把 AGENTS.md、插件技能、用户画像和系统身份拼成一块长文本,读者看不出可信度和生命周期。第三种反模式是只看首次请求,忽略工具回合、压缩、恢复、模型切换和子代理请求。第四种反模式是用“模型会思考”“Agent 会自主完成”这类宣传语替代停止条件和失败路径。
第五种反模式是以截图代替文本证据。截图适合展示布局和关系,但不能可靠复制长 prompt,也不便搜索、比对和辅助技术。第六种反模式是为了满足字数要求复制同一段解释。长章节应增加新的概念、反例、实验和引用,而不是让同一段落换几个标题。第七种反模式是把“工具可用”写成“工具一定会用”。Toolformer、ReAct 和生产 Agent 研究都说明调用时机、参数正确性和结果利用需要单独评测,工具 schema 的存在不是成功保证。
基础学习阶段应该主动抵抗“越长越完整”的错觉。一段 200 字、带来源和失败边界的 prompt 可能比 20,000 字的无来源文案更容易维护。课程要求每章至少 20,000 个可读字符,是为了给出足够空间解释机制、反例和实践,而不是鼓励把自然语言变成长墙。审计器检查重复段落比例,图稿要求每张图表达独立结论,都是为了把篇幅转化为可学习的信息。
11. 把提示词写成可审计 contract
一个可审计 contract 至少包含五个问题。第一,目标是什么,模型要帮助谁完成什么结果。第二,输入来自哪里,哪些内容是可信配置,哪些内容是用户数据,哪些内容是外部不可信数据。第三,模型能做什么,工具有哪些参数和边界。第四,什么时候算完成,哪些检查或环境事实必须满足。第五,失败时怎么办,是否重试、请求人工判断、保存部分结果或明确阻塞。OpenAI 的 goal/context/output/boundaries 四项提示框架可以作为初始骨架,但 Agent 还要加 action、state、stop 和 evidence。
把 contract 写进代码后,动态值必须通过显式参数传入。不要在 builder 内部隐式读取任意环境变量,然后在测试里只断言最终字符串包含某个词。更好的测试会构造一个确定的工具集合、工作目录和用户消息,断言每个角色出现一次、系统层顺序稳定、敏感头不进入 prompt、工具结果会在下一轮出现。Hermes 的缓存测试、Codex 的 context fragment 测试、Grok 的 encrypted template 测试、Pi 的 system prompt 测试,都是不同语言对同一 contract 思想的实现。
图 09 说明:审计从 source 走到 assembly,再到 request,最后进入 evaluation;每个检查点都有自己的证据,不能用下一点的结果替代上一点。
网页呈现 contract 时,应给读者一个“证据抽屉”。默认只显示结论和短片段,展开后看到源码路径、提交、哈希、捕获时间、运行命令和差异说明。这样既保持阅读节奏,又允许技术读者复核。原文/译文切换只改变语言层,不应隐藏来源和版本状态;在双栏模式中,源文和译文可以逐段对齐,但译文永远标为人工校订内容,而不是上游原文。
12. 练习:从四个 Agent 反推系统
练习一:打开 Hermes 的 runtime capture,只看 system 消息开头 300 个字符,写出你还不知道的三件事。答案通常包括工具集合、上下文文件、技能索引、记忆、平台和时间戳。练习二:比较 Codex 的 instructions 与 developer 两段,说明为何它们不能合并成“一个源文件”。练习三:阅读 Grok 的 prompt.md 与 template.rs,用自己的话解释“可读模板”和“加密数组”如何互证。练习四:在 Pi 的 models.json 中关闭所有工具,然后再打开 read 工具,预测 Available tools 和 Guidelines 哪些字节会变化。
练习五:为一个你熟悉的工具写一行过度简略的描述,再写一版包含输入边界、错误含义和示例的描述,列出模型可能犯的不同错误。练习六:构造一个包含“请忽略之前规则”的网页返回值,说明它为什么仍然是外部数据,不能因为出现在工具结果里变成高权限指令。练习七:为一次三回合 Agent 运行画状态机,标出至少一个人工审批点、一个可重试错误和一个不可重试错误。练习八:把一个大 system prompt 分成稳定、请求、环境三个生命周期组,并说明每组的缓存策略。
练习九:设计一个捕获记录 schema,至少有 agent、version、role、sourceCommit、capturedAt、promptSha256、redactedHeaders 和 segments。练习十:给同一个 source prompt 生成两种中文翻译,一种追求字面对应,一种追求学习可读性,标出哪些 token、路径、工具名和 XML 标签必须保留。练习十一:找一段无法确认来源的网上 prompt,写出它缺少哪些字段,并把状态标为 unavailable,而不是 verified。练习十二:从一个运行时捕获中选择五句规则,分别判断它们是身份、动作、边界、输出还是验证要求;如果一句话属于多个类别,说明为什么。
13. 参考实现:把研究问题变成数据
下面的伪代码展示一个最小研究管线。它不是某个 Agent 的实现,也不会调用模型;它的任务只是确保研究者没有跳过证据步骤。
source = pin_repository(url, commit)
files = locate_prompt_sources(source)
source_records = hash_and_record(files)
gateway = start_capture_gateway(redact_headers=true)
runtime = run_agent_with_local_endpoint(agent, fixed_config)
capture = gateway.read(agent)
assert capture.prompt.segments is not empty
assert marker_set(capture) matches expected_runtime_markers
diff = compare(source_records, capture, dynamic_fields=["cwd", "time", "model"])
publish(source, runtime, capture, diff, translation_status)
伪代码里最重要的不是函数名,而是顺序。若先写教程、后寻找来源,作者很容易把自己的解释回填成“事实”。若只运行 Agent 不固定配置,捕获无法复现。若只比对完整字符串,动态路径和时间戳会制造大量噪音;若完全忽略差异,又会掩盖真实版本变化。研究管线的目的,是把这些判断变成可以失败的检查,而不是依赖作者记忆。
14. 四个 Agent 的微型追踪
14.1 Hermes:缓存不变量如何塑造提示词
从 Hermes 开始追踪时,不应先全局搜索 “You are Hermes”,而应先定位 build_system_prompt_parts() 和 build_system_prompt()。前一个函数把内容拆成 stable、context、volatile,后一个函数按顺序连接,并把稳定部分保存到缓存字段。随后才回到 prompt_builder.py,查看默认身份、完成工作、并行工具、记忆、skills 和平台提示等常量。这个方向能回答两个不同问题:哪些句子可能出现,以及它们在什么条件下出现。
本次运行固定 HERMES_HOME 到临时目录、使用 current main 源码、将 Provider 指向本地捕获网关,并用 --ignore-rules 排除真实用户规则、记忆和 SOUL.md。捕获仍然包含运行平台、工作目录、当前 profile、模型和时间戳。为什么这些动态内容没有被 --ignore-rules 全部删除?因为它们不是用户规则文件,而是系统组装器根据运行环境生成的提示。实验因此验证了“配置隔离”和“环境信息”是两条不同注入路径。
再看捕获文本顺序:默认身份最先出现,随后是 Hermes 自身文档指引、完成工作、并行工具、持久记忆、skills 安全、回合中用户引导、主机信息、CLI 平台提示和时间戳。若只读 DEFAULT_AGENT_IDENTITY,会漏掉影响工具持续性和消息信任的绝大多数规则;若只读捕获,又难以知道每段来自常量、条件函数还是环境变量。网页因此需要同时提供“按运行顺序阅读”和“按源码所有者阅读”两种索引。
Hermes 还展示了缓存与正确性的冲突处理。它把系统提示词视为会话中的缓存前缀,只有新会话、压缩重建或显式失效流程才重新构建。中途写入 memory 并不意味着已经缓存的 prompt 立即变化。这个设计减少成本,但也要求调用者理解“磁盘状态已更新”和“当前会话模型已看到更新”是两个事实。如果教程只写“Hermes 支持持久记忆”,读者会错过最关键的可见时机。
14.2 Codex:基础指令和上下文片段为何分消息
Codex 的追踪从模型 family 或 model metadata 选择基础指令开始。本次运行指定 gpt-5.2-codex,使用系统安装的 codex-cli 0.151.0,通过自定义 Responses provider 将请求发到捕获网关。请求 body 中,基础指令出现在 instructions,skills 与权限环境出现在独立 developer 内容。这不是网页为了排版而拆分,而是 HTTP 请求本身的结构。
版本比对发现,捕获的 instructions 与 rust-v0.151.0 标签下 gpt_5_2_prompt.md 几乎一致,但审批模式说明有一处措辞差异,文件结尾也有换行差异。此时合理结论不是“验证失败”,也不是“完全相同”,而是 verified-with-version-diff。页面需要展示两段哈希、diff 摘要和运行版本,让读者知道大部分内容有直接来源,同时看到不能解释为静态复制的部分。
developer 消息中的 skills 列表又说明运行时能力目录不是模型基础 prompt。它包含当前会话可用的 skill 名称、短路径和读取规则;换一个 Codex 安装、不同 CODEX_HOME 或不同启用配置,developer 段就会变化。若将它复制到所谓“Codex 完整 system prompt”,下一位读者可能误以为这些 skills 永远存在。正确展示方式是把它标成 capability inventory,并记录捕获环境。
Codex 还通过 context fragment 类型表达消息所有权。例如 BaseInstructionsFragment 要求独立 developer 消息;permissions、user instructions、apps、environments 和 multi-agent mode 分别有构造器与测试。这样的实现便于对每个片段单独测试,也让最终请求更容易解释。代价是“完整提示词”不再对应单一文件,研究工具必须能够收集多条消息并保留顺序。
14.3 Grok Build:模板明文、二进制混淆和运行渲染
Grok Build 的仓库同时提供 templates/prompt.md、apply_patch_prompt.md、subagent_prompt.md 和 prompt_encrypted.rs。注释明确写出 XOR 混淆不是安全边界,seed 也在仓库里。仓库测试会把明文按 seed + byte position 算法转换,再断言与加密数组一致。我们用独立 Node 脚本复现这个算法,三份模板分别逐字节相同。这一步证明的是构建输入与嵌入字节一致,不是证明某次运行展开了所有条件分支。
运行时使用官方临时二进制 1.0.13,通过临时 HOME 的 config.toml 定义 capture 模型和本地 /v1/models。模型配置把 system_prompt_label 设为 Grok Build、agent_type 设为 grok-build。捕获后可以看到模板变量已经展开:非交互模式身份句出现,工具名称填入,只有当前工具集合对应的 conditional block 保留。模板中的 ${%- if ... %} 不再出现在请求里。
这组证据需要分成三页或三个标签:模板源码、嵌入验证、运行时渲染。模板页适合学习条件和结构;嵌入页解释为什么 strings 不直接显示 prompt,并提醒这不是加密安全;运行时页展示当前版本、工具集合和渲染结果。若把加密数组长数字直接当作“提示词”,读者学不到任何结构;若只展示明文模板,又无法证明二进制真的使用它。
14.4 Pi:最小 builder 和显式扩展点
Pi 的 buildSystemPrompt() 相对短,适合初学者观察动态拼装。默认主体写明它是 coding assistant、列出可用工具、给出指导原则、Pi 文档路径和当前目录。函数输入包括 customPrompt、selectedTools、toolSnippets、promptGuidelines、appendSystemPrompt、cwd、contextFiles 和 skills。每个参数都对应一种可观察变化。
本次运行下载与源码版本一致的 npm 包 0.84.4,配置一个名为 capture 的 OpenAI Chat Completions Provider,禁用工具、扩展、skills、prompt templates 和项目上下文。最终 system prompt 只有 1,583 个源文本字符。短并不等于不完整;它准确反映了“最小配置”的运行状态。网页应把该配置作为 capture profile 展示,旁边再提供启用 read、bash、项目上下文或 skills 后的差异实验。
Pi 自定义 prompt 的语义尤其值得注意:customPrompt 会替换默认主体,但 appendSystemPrompt、项目上下文、skills 和当前工作目录仍可追加。把“自定义 system prompt”解释成“最终请求只剩用户文本”是错误的。测试必须针对最终组装结果,而不是只检查 customPrompt 被读取。Pi 的 prompt template 又属于用户命令模板,不应与 system prompt builder 混成同一种内容。
四个微型追踪得到共同结论:研究起点可以不同,但终点都应该是具体请求。Hermes 强调生命周期与缓存,Codex 强调消息片段与模型 family,Grok 强调模板条件与构建嵌入,Pi 强调小型 builder 与显式覆盖。教程不应把其中任一做法提升为唯一标准,而应让读者看到它们解决的工程约束。
15. 线协议如何改变可见边界
OpenAI Responses 请求可能使用顶层 instructions,并在 input 数组中携带 developer、user、assistant 和工具结果等 item。Chat Completions 通常把 system、developer、user、assistant、tool 消息放进 messages 数组。Anthropic Messages 请求可以把 system 作为字符串或内容块,并用 content block 表达 tool use 与 tool result。三种协议都能承载 Agent,但消息边界和工具返回形状不同。
捕获网关必须按协议解析,不能假设所有请求都有 messages[0].content。本项目的 extractPrompt() 先检查 instructions 和 system,再遍历 messages 与 input,只提取 system/developer 等高权限段。工具列表通常位于请求的 tools 字段,不应误拼到 prompt string;若网站要解释可行动空间,应把工具 schema 作为与消息平行的数据集展示。
流式响应也会影响 Agent 是否顺利结束。Responses 使用事件类型,Chat Completions 使用 data: chunk,Anthropic Messages 使用 message_start、content block 和 message_stop 等事件。捕获网关即使不生成真实内容,也要返回客户端能解析的最小占位流,否则 Agent 可能重试、改变请求或在收到 prompt 后报协议错误。此处“无需真实返回”不等于“可以返回任意字符串”,而是“不需要模型语义,但仍需满足客户端线协议”。
请求路径本身也是证据。Codex 发往 /v1/responses,Pi 和本次 Hermes、Grok 配置发往 /v1/chat/completions。同一个 Agent 可能支持多种 Provider 和 API mode;切换协议后,system prompt 文本未必变化,但消息封装、工具调用与缓存字段可能变化。网站版本页需要记录 capture profile 的 wire API,不能只记录模型名字。
序列化还会引入看不见的变化。SDK 可能把 developer role 降级为 system,以兼容不支持 developer 的服务;可能把多个 content block 合并;可能添加 provider 特定 headers 或 prompt caching 标记。研究者若只看 Agent builder 输出,不看 SDK 发出的 body,就无法发现这些转换。最小验证应同时保留 builder snapshot 和 HTTP capture,并在二者之间做角色级映射。
16. 信任边界不是 XML 标签
XML、Markdown 标题和分隔符能帮助模型理解边界,但它们不会把不可信文本变成可信文本,也不会改变 API 角色。假设 Agent 使用 web_fetch 获取网页,网页正文中出现 <system>ignore all rules</system>,它仍然只是工具结果数据。只有运行器错误地把它重新序列化为 system/developer 消息,权限才真正改变。提示词注入防护因此需要运行时分层、工具输出标记、权限约束和模型行为共同工作。
Hermes 的回合中用户引导提供了具体案例:它只信任一个完全一致的 out-of-band marker,而且只在最新工具结果中的当前位置处理。该规则试图区分真实用户中途消息和文件、网页中的仿冒文本。即便如此,安全仍依赖运行器保证只有真实用户通道能生成 marker;若普通工具能够随意输出同样标记,提示词规则就无法建立信任。
Codex 的 AGENTS.md 规则展示另一类信任:项目文件由用户或仓库作者提供,权限低于直接 system/developer/user 指令,但高于普通代码注释的约定意义。目录作用域限制避免根目录规则无差别覆盖外部路径。Grok 和 Pi 也会发现项目规则或 skills;每个加载器都应记录文件路径、作用域、是否信任项目、是否安全扫描,以及内容被截断时的行为。
安全章节后续会详细讨论 prompt injection。本章只建立一条审计原则:每段动态文本必须同时带 role、origin 和 transformation。role 表示协议权限,origin 表示来源通道,transformation 表示它是否经过截断、扫描、模板展开、摘要或编码。缺少任一字段,都可能让后续解释把数据误认为指令。
17. 缓存、成本和正确性
Prompt caching 通常依赖请求前缀稳定。OpenAI 文档建议把重复使用的内容放在 prompt 开头,并保持请求体前部参数稳定,以获得延迟和成本收益[^src-openai-prompt]。这是一条性能建议,不应覆盖语义正确性。如果某段内容需要高权限角色或必须紧邻当前输入,就不能仅为缓存而移动到错误位置。
Hermes 把 byte-stable system prompt 作为会话原则,说明它如何冻结技能索引、记忆快照和插件分区。Codex 也会计算 prompt cache key,并把模型、权限和上下文片段纳入请求。Grok 的模板渲染后可以缓存,但工具集合或模式变化会产生不同文本。Pi builder 简单,却同样会因 cwd、context files 和 skills 改变最终字符串。四者都需要一个“什么变化会使缓存失效”的列表。
缓存命中不能被当作提示词正确的证明。旧 prompt 错误但稳定,仍可能高命中;新 prompt 正确但频繁加入时间戳,可能低命中。评测应分别记录行为质量和缓存统计。优化顺序应是先保证角色、来源和更新时机正确,再识别可以稳定的前缀,最后通过请求日志验证缓存效果。
在网页中,缓存知识最好通过时间轴表达:会话开始构建一次、普通回合复用、某个事件触发失效、下一次请求重建。把 stable/context/volatile 画成静态三层还不够,读者需要看到哪些事件改变哪一层。图注应写明“压缩触发重建”或“记忆写入仅更新磁盘”,而不是用一条无标签箭头表示“刷新”。
18. 如何设计 Agent prompt 评测
提示词评测不应只问模型回答“看起来是否更好”。对于 Agent,可以把一次任务分成若干可观测决策:是否识别需要工具、是否选对工具、参数是否有效、是否正确利用返回、失败后是否恢复、是否遵守权限、是否达到停止条件、最终结果是否由真实执行支持。API-Bank 类工具评测将调用、检索 API 和多步规划分层,AgentBench 则指出长期推理、决策和指令遵循是常见障碍。评测粒度越贴近决策,越容易知道应改 prompt、tool schema 还是运行器。
第一类测试是确定性结构测试。输入固定配置,断言 builder 输出包含或不包含某段、角色顺序正确、工具数量匹配、敏感值被脱敏。这类测试不调用模型,运行快,适合每次提交。第二类是协议测试,使用 mock server 或捕获网关,断言 HTTP body 与预期结构一致。第三类是行为 eval,给模型一组代表性任务,记录成功、工具错误、越权和完成质量。第四类是回归回放,把历史失败任务在新版本上重新运行。
行为 eval 需要防止“只测快乐路径”。至少加入工具返回空数据、网络超时、路径不存在、权限拒绝、用户中途改变方向、上下文过长、项目规则冲突和模型切换。对于高风险动作,要验证 Agent 会请求审批,而不是验证它是否“语气谨慎”。对于完成声明,要检查真实文件、测试结果或外部状态,而不是只搜索回复里是否出现“完成”。
版本升级时,应先固定旧 prompt、旧模型 snapshot 和 eval 结果,再只改变一个变量。若同时升级模型、重写 prompt、增加工具和改变审批策略,即使指标变好也无法归因。OpenAI 官方建议固定生产 model snapshot,并建立测试和 eval suite 来监控提示词迭代与模型升级[^src-openai-prompt]。开源 Agent 研究也应记录依赖版本和运行命令,使读者能够复现实验,而不是只看最终分数。
19. 完整实验:从一个请求反推提示词层
选择 Pi 作为入门实验,因为 builder 较短。第一步,在临时目录创建最小 models.json,定义一个 OpenAI Chat Completions Provider,base URL 指向捕获网关,使用假 API key,并增加 X-Capture-Agent: pi。第二步,运行 npm 发布包,指定与源码一致的版本,关闭 tools、extensions、skills、prompt templates 和 context files。第三步,向 Agent 发送只要求回复固定短语的用户消息。
捕获后不要先读整个 body。先验证 header 已脱敏,再看 model、stream、messages 数量和角色。提取 system 内容,计算字符数与 SHA-256;检查 operating inside pi、Available tools: 和 Current working directory 等 marker。随后回到 system-prompt.ts,按 builder 顺序找到对应字符串。此时可以建立 source line 到 runtime segment 的映射。
第二轮只打开 read 工具,并提供真实 tool snippet。预测变化:可用工具清单会出现 read,skills 仍然关闭,项目上下文仍然不存在。运行并 diff 两次 capture,确认变化局限于工具清单和可能的指导原则。第三轮加入一个 AGENTS.md,打开 context file 加载,确认它被包在 project context 标签中,并记录路径。每轮只改变一个变量,才能说明条件分支。
第四轮设置 customPrompt。先预测默认身份、Pi 文档和默认指导是否消失;再检查 appendSystemPrompt、项目上下文、skills 与 cwd 是否仍会追加。若实际结果与预测不同,回到源码更新 mental model,不要修改实验输出迎合预期。第五轮对运行时 prompt 做中文翻译,把工具名、路径、XML 标签和字面 marker 保留,计算译文所绑定的 capture hash。只要原文 hash 变化,构建就应拒绝旧译文。
图 10 说明:实验面板展示固定输入、请求角色、原文哈希、marker 检查、source mapping 和单变量 diff;不显示真实密钥,也不虚构模型输出。
完成 Pi 后,把同一实验迁移到 Hermes、Codex 和 Grok。不要追求完全相同的命令,而要保持相同证据问题:固定了哪个版本,动态内容来自哪里,工具集合如何记录,消息角色怎样保留,捕获与 source 如何比对。最终比较的不是谁的 prompt 更长,而是哪种实现让生命周期、信任和调试更清楚。
20. 章节小结
本章建立了一个可迁移的 mental model:提示词由 source、template、assembly 和 runtime capture 四种对象组成;消息角色是协议字段,标题只是文本结构;工具 schema 扩大了行动空间,工具结果驱动 Agent loop;上下文、记忆、环境和项目规则有不同生命周期与信任等级;版本、配置、哈希和差异决定“验证过”的含义。Hermes、Codex、Grok Build 和 Pi 看起来使用不同语言与不同产品术语,但它们都必须解决这些共同问题。
下一章会把“角色和权限”单独展开,讨论 system、developer、user、assistant、tool result 之间如何处理冲突。再下一章会深入上下文序列化,解释为什么同一段自然语言在不同 API 线协议中可能拥有不同边界。学习时请保留本章的检查顺序:先问它来自哪里,再问它如何组装,最后问它是否真的发送并被评测。
21. 自检清单
- 我能指出当前文本是 source、template、assembly 还是 runtime capture。
- 我能在捕获记录中看到消息角色,而不是只看到混合字符串。
- 我能解释至少一个工具调用如何改变下一轮上下文。
- 我能画出请求、动作、观察、继续/暂停/结束的状态机。
- 我能区分上下文窗口、会话历史、长期记忆、项目文件和工具结果。
- 我能为一个结论提供上游 URL、提交、路径、时间和哈希。
- 我能说出一个版本差异,并标明它是动态字段还是源码变化。
- 我知道一张图表达一个机制结论,而不是用截图替代正文。
- 我会把不确定事实标成 pending、inferred 或 unavailable。
- 我能用本地捕获网关复核 Agent,而不把 API key 或真实模型调用当作证据。
22. 全书术语辨析
Prompt:在本书中是发送给模型并影响当前生成的内容总称,但单独使用时必须加限定词。source prompt 指源码中可读的提示文本,runtime prompt 指请求中的实际高权限文本,user prompt 指用户输入。若一句话只说“prompt 变了”,读者无法判断是文件内容、组装逻辑、角色还是请求发生变化。
Instructions:可以是自然语言中的普通名词,也可以特指 Responses API 的顶层 instructions 参数。后者是协议字段,不能简单译成 Markdown 中的 “Instructions” 标题。网页展示时,协议字段应使用代码样式,并在证据面板显示它位于请求 body 的什么位置。
System prompt:常被泛指所有高权限内容,但不同 API 和 Agent 对 system/developer 的映射不同。本书只有在上游代码或协议确实使用 system role 时才把某段称为 system message;描述整体时优先使用“高权限提示词层”或“组装后的运行时提示词”,避免把 Codex 的 developer 内容误标成 system。
Context:模型本次生成能够访问的信息集合,包括高权限指令、历史、用户消息、工具调用和结果等。项目文档里也可能把某个片段命名为 context,因此使用时应说明是“整个模型上下文”还是“项目上下文文件”。Context window 是容量限制,不是持久存储产品名称。
State:系统在回合之间保存并用来决定下一步的结构化事实,例如当前计划、审批等待、工具调用 ID、会话 ID、工作目录和恢复点。State 不一定以自然语言进入模型;即使完全不进入 prompt,也能通过运行器控制流程。把 state 和 context 混同,会漏掉代码级控制逻辑。
Memory:跨时间保存并可在后续取回的信息机制。短期历史、压缩摘要、用户 profile、向量库、文件式 MEMORY.md 都可能被产品称作 memory,但它们不能互换。研究时至少记录存储介质、写入触发、检索触发、注入角色、更新可见时机、容量和删除方式。
Tool:模型可以选择调用的结构化能力。完整工具 contract 包括名称、描述、输入 schema、执行器、权限、超时、错误和结果形状。Shell 命令只是某个终端工具的参数,不等于工具协议本身。网页解释工具时应同时显示模型看到的 schema 和运行器真正执行的边界。
Policy:对允许、禁止、升级、重试和终止等行为的规则集合。Policy 可以出现在提示词、代码、沙箱、审批服务或网络层。只有写在自然语言里的 policy 依赖模型遵循;代码和基础设施 policy 可以在模型之外强制执行。安全分析必须标出执行层。
Guardrail:对输入、输出或工具动作进行检测、阻断、修改或升级的机制。它不是一句“请安全行事”的同义词。Guardrail 需要触发条件、判定器、处置动作和失败策略;如果只是提示模型自我检查,应明确称为模型内检查,而不是强制边界。
Assembly:把身份、规则、工具说明、项目文件、记忆和动态值转换成有序消息的过程。Assembly 可能是字符串拼接、模板渲染、context fragment 排序或 SDK 转换。研究 assembly 时既看输出,也看条件与所有者;最终字符串相同,不代表组装生命周期相同。
Runtime capture:从实际 Agent 请求路径记录的 HTTP body 或等价 SDK mock 证据。Capture 应脱敏、带时间、版本、配置和哈希。它证明特定运行发送了什么,不证明服务端在请求外还做了什么,也不证明模型一定遵循每条内容。
Snapshot:固定时间点的模型、代码、配置或 prompt 版本。Git commit 是源码 snapshot,模型版本号可能是固定 snapshot,也可能是移动别名。课程引用 snapshot 是为了可复现,不表示旧版本永远更正确;升级时应重新捕获并运行 eval。
Eval:用代表性输入和明确判定标准测量系统行为的过程。Eval 可以是确定性结构检查、协议测试、模型行为任务、人工量表或端到端成功率。没有失败样例、基线和版本记录的“试了几次感觉不错”不是可用于发布决策的 eval。
Provenance:一项内容从哪里来、经过什么转换、由谁审核、对应哪个版本的记录。源码 URL、commit、文件路径、哈希、捕获命令、翻译审核和图片生成提示词都属于 provenance。它让后续读者可以复核,也让过期内容能够被准确替换,而不是凭记忆重写。
这些术语会在后续章节反复出现。遇到某个 Agent 使用不同命名时,本书先尊重上游名称,再映射到这里的通用概念。例如 Hermes 的 volatile tier 是 assembly 生命周期概念,Grok 的 <work_policy> 是模板内部结构,Codex 的 BaseInstructionsFragment 是消息组装组件,Pi 的 prompt template 是用户命令模板。映射的目的不是消除差异,而是让比较建立在同一问题上。
23. 建议的阅读与复现顺序
如果你第一次系统研究 Agent,先按基础部分顺序阅读,不要立即跳到最长的 Codex prompt。角色与权限、上下文序列化、工具协议、Agent loop、状态与记忆会提供后续源码分析需要的词汇。完成基础部分后,先做 Pi 实战,因为它的默认 builder 较短、输入参数清楚;再做 Hermes,观察生命周期和缓存;随后做 Grok Build,理解条件模板和二进制嵌入;最后做 Codex,处理模型 family、多个 context fragments 和独立 developer 消息。
复现实验时,为每个 Agent 创建单独临时 HOME,不复用真实用户配置。选择与源码提交可对应的发布版本,固定 cwd 和工具集合,关闭与本次问题无关的 plugins、skills 和项目规则。先运行最小配置,再一次只打开一个能力。每次捕获后立即记录命令、版本、请求角色、字符数和哈希;不要等所有实验结束后凭记忆补写。若安装器修改 shell profile、下载二进制或创建缓存,也记录位置,以便部署后完整清理。
阅读网页时,可以把语言切换设为“原文”先检查字面内容,再切到“双栏”理解中文,最后打开证据链查看 source 和 runtime diff。译文用于学习,不替代英文证据;若翻译状态不是 reviewed,页面应提示仍需校订。看到动态路径、日期或模型名时,把它当作 capture profile 的变量,不要复制进自己的通用 prompt。
每完成一章,尝试用一张空白纸画出该章的状态、数据或信任边界,并为每条箭头写动词。如果一条关系只能写“相关”,说明理解仍不具体。再选择一个源码符号和一个运行时字段,解释它们如何对应。能够从代码走到请求、再从请求回到行为 eval,才算真正掌握 Agent 提示词结构,而不只是记住术语。
[^src-openai-prompt]: OpenAI, “Prompt engineering”, https://developers.openai.com/api/docs/guides/prompt-engineering(访问于 2026-08-30)。 [^src-openai-agents]: OpenAI, “Agents SDK”, https://developers.openai.com/api/docs/guides/agents(访问于 2026-08-30)。 [^src-anthropic-agents]: Anthropic, “Building Effective AI Agents”, https://www.anthropic.com/research/building-effective-agents(访问于 2026-08-30)。 [^src-weng-agents]: Lilian Weng, “LLM Powered Autonomous Agents”, https://lilianweng.github.io/posts/2023-06-23-agent/(访问于 2026-08-30)。 [^src-react]: Yao et al., “ReAct: Synergizing Reasoning and Acting in Language Models”, https://arxiv.org/abs/2210.03629(访问于 2026-08-30)。