第 8 章:消息、提示与上下文工程
模型看到的“上下文”不是磁盘聊天记录的直接拼接。它是系统提示、首轮环境、项目规则、技能目录、消息历史、工具结果、记忆、提醒和压缩产物经过多阶段构建后的快照。
8.1 两层消息模型
和 pi-agent 一样,Grok Build 也需要区分:
- 会话/产品消息:包含 UI、synthetic reason、工具进度、session event 等丰富语义;
- 模型 conversation item:能够稳定转换到不同模型 API 的规范语义。
除此之外,还有第三层:
- 持久化 update:用于恢复客户端和重建派生聊天状态。
三层不能合并:
| 层 | 主要消费者 | 是否包含 UI/恢复语义 |
|---|---|---|
| ACP/session update | Client、replay reducer | 是 |
| ConversationItem | ChatState、Sampler | 只含模型语义 |
| Provider payload | 具体 API | 供应商专属 |
8.2 Prompt 的两个头部
Grok Build 把初始上下文分为:
- system prompt;
- first-user prefix。
system prompt 定义长期行为、工具使用规范和角色;first-user prefix 提供会话环境,例如:
- user/platform 信息;
- cwd/workspace;
- git status;
- 本地日期;
- 项目规则;
- 可用 skills;
- MCP metadata。
分离的好处:
- 供应商对 system 的缓存/优先语义可被利用;
- 环境信息可以在 compaction 后刷新;
- 规则与真实用户 query 不必混成不可区分的字符串;
- subagent 可以使用不同 system prompt/audience。
8.3 PromptContext
Prompt 不是在 SessionActor 中到处字符串拼接。PromptContext 保存可序列化、可检查的渲染输入,ToolBridge/TemplateRenderer 根据 agent definition、工具集合和环境生成最终内容。
PromptAudience 至少区分 Primary 与 Subagent。两者可能有不同:
- 基础 prompt;
- 可用工具;
- 工作范围与汇报方式;
- goal/workflow 提示;
- token budget。
模板源码使用 XOR 混淆、按需解密并 zeroize。源码明确这不是安全加密,目的更接近资源封装/避免明文散落,不能当作秘密保护边界。
8.4 项目规则发现
规则发现不仅查一个 AGENTS.md。它会考虑:
- 用户 home 范围;
- 当前 cwd 向上直到 repo root;
AGENTS.md、Claude 兼容文件;.grok、.claude、.cursor中的规则目录。
通常越接近当前工作目录的规则越具体。规则文本在嵌入 harness framing 前会处理以 < 开头的内容,避免意外破坏 XML/HTML 风格包装边界。
一个通用的上下文安全原则是:
从工作区读取的指令是数据;即使把它放进 prompt,也要防止它破坏外层 framing。
8.5 Skill 发现与优先级
技能可能来自:
- 当前目录或中间层目录;
- repo;
- user;
- config/server;
- bundled;
- plugin。
更高优先级来源覆盖较低来源。原生裸名优先于插件冲突名,插件可保留 plugin:skill 资格名,避免完全不可达。
技能发现还要处理:
- ignore config;
- known roots;
- watcher 刷新;
- prompt 中只暴露元数据还是完整正文;
- 模型按需读取技能内容。
已知技能根目录不会简单照搬 gitignore,因为用户明确安装的技能不应因项目忽略规则而消失。
8.6 从会话状态到模型请求
xai-chat-state 是对话状态的单一 actor。ChatStateSnapshot 包含:
- conversation;
- sampling config;
- prompt index;
- token counts/估算;
- edited paths;
- prompt texts;
- timing;
- last compaction;
- credentials 等运行信息。
构建请求前的流程可概括为:
8.7 为什么 ChatState 也是 actor
SessionActor 已经串行,为什么 conversation 还要独立 actor?
因为 ChatState 同时协调:
- 内存中的 conversation;
- 持久化确认;
- token usage 与估算;
- request snapshot;
- compaction replace;
- rewind truncate;
- model switch 的 system head 更新。
某些写入需要区分:
- actor 已接收;
- persistence 已提交;
- storage reply 丢失,结果不可判定;
- 内存是否可以安全收敛到磁盘结果。
独立 actor 和 ack API 使这些状态转换显式化。
8.8 Dangling Tool Call 修复
合法对话要求 assistant tool call 与后续 tool result 成对。崩溃、取消或旧数据可能留下 dangling call。如果直接发送,某些供应商会拒绝整个请求。
ChatState 在 build request 前修复此类结构,注入合成结果或清理不一致项,使 provider payload 满足协议不变量。
这是一种“发送前规范化”,与持久化事实源并不冲突:
- 原始 update 仍可保留发生了什么;
- 模型请求使用修复后的有效视图。
8.9 Tool Result 修剪
工具结果往往是上下文最大来源。默认 PruningConfig 的策略包括:
- 保留最近约 3 个 turn;
- 对超过约 4,000 字符的旧结果做 soft trim;
- soft trim 保留约 1,500 字符头部与尾部;
- 足够老(约 10 turns)的结果可 hard clear。
触发请求构建时,只有上下文使用超过一定比例(约 50%)才需要更积极修剪。
为什么保留头尾
工具输出的开头常有文件名、命令或摘要;尾部常有错误、测试结论或最终状态。只保留头部容易丢最重要的失败原因,只保留尾部又失去来源。
Hard clear 的恢复性
Hard clear 会改变 actor/派生聊天中的 tool result,但 durable updates 仍可支持 replay/reconstruction。也就是说,模型上下文视图可以有损,事实记录不必同步有损。
8.10 图片预算
图片以 base64/二进制 content 进入上下文时可能迅速膨胀。请求构建前会在接近约 50 MiB 的预算时驱逐旧图片到低水位。
图片裁剪与文本 token 裁剪是不同维度:
- 图片受 payload byte 与后端限制影响;
- 文本主要受 token/context window 影响;
- 后端 413 还可触发 sampler 层 strip-images 恢复。
8.11 Token 账本
只按字符数估 token 会忽略供应商 framing、tool schema、图片和 response overhead。ChatState 同时维护:
- 上次 provider 返回的精确 usage;
- 自上次响应后新增内容的估算 delta;
- compaction 后按保留内容比例继承的 provider overhead;
- model context window 与阈值。
这种“精确锚点 + 增量估算”比每次重新 tokenize 全历史更便宜,也比纯字符估算稳定。
8.12 动态 Reminder
系统提醒来自运行时状态,而非最初 prompt。例如:
- LSP diagnostics;
- 后台命令/monitor 完成;
- subagent completion;
- task 尚未结束;
- skill discovery;
- MCP 初始化状态;
- goal 下一步;
- 计划/工作目录切换;
- stop hook feedback。
它们通常包装成带明确 tag 的 synthetic message。提醒注入要满足两项约束:
- 不应每轮无限重复同一内容;
- 必须在模型作下一次决定前出现。
因此 SessionActor 在采样前 drain,而不是让后台 task 直接修改当前 HTTP stream。
8.13 上下文的时间分层
可以把模型输入分为四个时间尺度:
| 时间尺度 | 内容 |
|---|---|
| 进程/agent 稳定 | base system prompt、工具行为协议 |
| session 稳定 | cwd、agent persona、初始规则/技能 |
| turn 动态 | git 状态、可用工具、memory、model config |
| iteration 动态 | tool result、interjection、completion reminder、goal nudge |
更新频率不同的内容分层,可减少无意义的 prompt 变化,也更容易利用 provider cache。
8.14 Compaction 后为什么要刷新头部
压缩不是简单把旧消息替换为 summary。Grok Build 会使用 compact system prompt,并重新加入:
- 当前 user-info/context;
- 当前 AGENTS/rules reminder;
- 最近 user query;
- 近期关键消息;
- compaction summary;
- 必要 system reminder。
原因是 cwd、git、skills、MCP 和工具集合可能已经变化。若 summary 固化了旧环境,长会话会带着过时控制信息继续运行。
8.15 消息来源表
| 来源 | 在模型中常见形态 | 是否持久化 |
|---|---|---|
| 真人 prompt | User item | 是 |
| system template | System item | 可重建/快照 |
| AGENTS/规则 | 首轮前缀或 reminder | 状态与更新视实现 |
| 工具输出 | ToolResult | 是,派生视图可修剪 |
| interjection | synthetic User | 是 |
| subagent 完成 | synthetic User/reminder | 是 |
| memory | reminder/context | 注入状态受控 |
| compaction summary | User/System 组合中的 summary | checkpoint + 派生历史 |
| UI-only progress | ACP update | 不一定进入模型 |
8.16 源码阅读卡
| 目的 | 文件 |
|---|---|
| Conversation item/reason | sampling-types/conversation.rs |
| ChatState snapshot | chat-state/src/types.rs |
| ChatState actor | xai-chat-state/src/actor/* |
| pruning/compaction utils | compaction_utils.rs |
| AgentBuilder | xai-grok-agent/src/builder.rs |
| PromptContext | prompt/context.rs |
| reminder drain | shell/session/acp_session_impl/reminders.rs、notification_drain.rs |
| skill/plugin 发现 | shell/src/extensions/* |
8.17 本章结论
上下文不是聊天记录,而是一个按需构建的模型视图:
稳定 prompt 定义行为,环境前缀定义现场,Conversation 保存语义历史,ChatState 修复与裁剪视图,SessionActor 在每次采样前注入刚发生的世界状态。
下一章沿反方向看输出:模型和 SessionActor 的事件如何穿过 ACP,最终变成一个可交互、可恢复的 TUI。