# system prompt 怎么组装 · 一个会随工具集变形的模板

> grok 的 system prompt 不是一段死字符串，而是每次会话按「有哪些工具、是否交互、什么受众」现场渲染出来的。这一章拆它的模板引擎、按 kind 间接引用工具、以及 AGENTS.md 为什么不进 system prompt 而是当用户消息注入。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/system-prompt
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# system prompt 怎么组装 · 一个会随工具集变形的模板

模型的「人设与规矩」全在 system prompt 里。grok 的 system prompt 有意思在：它**不是死字符串**，而是每次会话现场渲染。

## 三个模板，还被混淆了

模板在 `xai-grok-agent/src/prompt/template.rs`。有三份：`base`(主)、`apply-patch`(codex 风格编辑)、`subagent`(子代理精简版)，外加一个压缩后用的极短 `COMPACT_SYSTEM_PROMPT`。

一个反常的细节：明文模板放在 crate 的 `templates/` 目录(`prompt.md`、`apply_patch_prompt.md`、`subagent_prompt.md`)，但编进二进制的是 `xai-grok-agent/scripts/encrypt_templates.py` 把它们 **XOR 混淆**后生成的 `prompt_encrypted.rs` 字节数组(有测试盯着两边不走样)，运行时按需解密成 `Zeroizing<String>`(drop 时抹掉内存明文)。作者自己在注释里写明「这是混淆不是安全措施——种子就在仓库里」，目的只是别让 prompt 在 `strings` 输出里一眼可见。想看真身：`grok prompt --section template`，或者直接读 `templates/prompt.md`。

## 模板引擎：按 kind 间接引用工具

渲染由 `xai-grok-tools` 里的 `TemplateRenderer` 完成(基于 MiniJinja，定界符换成 `${{ }}` / `${% %}`，免得和工具描述里字面的 `{{ }}` 撞车)，两样东西是关键：

- **工具按 kind 引用**：模板里写 `${{ tools.by_kind.read }}`，渲染时才替换成实际工具名(`read_file`)。所以你[改工具名](https://daiw.org/manual/grok-build/tool-registry)、禁用某工具，prompt 会跟着变。
- **按条件裁剪段落**：`${%- if tools.by_kind.execute or tools.by_kind.monitor %}…${%- endif %}`——没有 execute / monitor 工具就不渲染 `<background_tasks>` 段；没有 task 工具，「用户要你派 subagent 就得真派」那条规矩也不会出现。system prompt 因此**只描述当前真实可用的能力**，不会承诺不存在的工具。

还有一批占位符：`system_prompt_label`(开头「You are … released by xAI」里的身份名，默认 `Grok`)、`os_name`、`shell_path`、`working_directory`、`current_date`、`is_non_interactive`……其中 `is_non_interactive` 很关键：headless/SDK 会话没人盯着 TUI，于是 `<user_guide>` 之类的段落自动消失，身份也从「interactive CLI tool」切成「autonomous agent」。按开关出现的还有 v2 记忆的 `<memory>` 段、plan agent 用的 `<browser_verification>` 段。

<Callout type="info">
  base 模板有个 **16 KB 软上限**测试(`PROMPT_SIZE_SOFT_CEILING_BYTES`)，还有个静态守卫测试(`test_template_vars_are_always_guarded`)确保每个 `${{ tools.by_kind.X }}` 都被要求 X 的 `if` 包着(否则可能渲染成空)。眼下 `templates/prompt.md` 还不到 10 KB——grok 的 prompt 是**刻意精简**的，这本身就是一个值得学的取向：prompt 越短越省 token、越不容易「指令过载」。
</Callout>

## `PromptContext`：所有输入的载体

真正驱动渲染的是 `prompt/context.rs` 的 `PromptContext`，它把所有输入收在一处并可序列化(便于 dump 检查)。几个维度：

- `PromptAudience`:`Primary`(全量 base 模板)vs `Subagent`(精简模板、隐藏 personas)。
- `PromptMode`:`Extend`(base 模板 + 可选追加正文)vs `Full`(正文即整个 prompt)。
- `TemplateOverride`:`None`(内置)/ `Codex`(apply-patch)/ `Custom`(自带)。

`render(&tool_bridge)` 就是把选中的模板过一遍 renderer，填占位符、解析工具名，产出最终 system prompt，由 shell 装到历史第 0 项 `System`。

## AGENTS.md 不进 system prompt

一个反直觉但很聪明的决定：**项目规则(AGENTS.md / CLAUDE.md / 规则文件)不拼进 system prompt**，而是当一条**独立的合成用户消息**注入(打标 `ProjectInstructions`)。发现顺序：`~/.grok/` → 兼容目录(`~/.claude/`、`~/.cursor/`，可在配置里分别关掉)→ 从 git 根走到 cwd(越深越优先；各级 `.grok/rules/`、`.claude/rules/`、`.cursor/rules/` 下的 `*.md` 规则也算)，`.gitignore` 过滤；工作目录没被信任时，项目级文件一律不读。

为什么这么做？system prompt 要**字节稳定**才能命中 KV 缓存；而 AGENTS.md 每个项目不同、还可能中途变。把它拆成用户消息，system prompt 就能保持稳定复用，项目指令另走一路(源码注释也强调：这条消息一旦放下就不再替换，否则会打穿 KV 缓存前缀)。还有个 `<user_info>` + `<git_status>` + 规则/技能/MCP 的**首条用户消息前缀**，同理独立于 system prompt。

## 和《从 LLM 到 Coding Agent》对照

对照 [context-injection](https://daiw.org/manual/llm-to-agent/context-injection)：最小实现里，system prompt 常是一个模板字符串 `f"...{tools}..."`。grok 的增量是——它把「prompt 随工具集/交互态/受众变形」做成了一个带条件、带守卫、带缓存友好切分的**渲染系统**，还顺手把项目指令从 system prompt 里拆出去以保缓存。

---

上一篇：[会话状态 · chat-state](https://daiw.org/manual/grok-build/chat-state) · 下一篇：[上下文压缩](https://daiw.org/manual/grok-build/compaction)
