Learn-Claude-Code | 笔记 | Planning & Coordination | s10_new System Prompt
目录
写在前面
learn-claude-code 项目目前有两条教程线,一条是之前的 s12 章节的,另一条是最近更新的 s20 章节的,大家如果刚学习这个项目的话推荐直接看最新的 s20 章节的教程即可,由于博主之前学习过 s12 章节的内容,因此打算把 s20 章节中新增章节的内容给补充学习,内容重复的章节博主这边就跳过了。
下面是旧版到新版的对应关系:
| Legacy 12-lesson track | Current 20-lesson track | Topic |
|---|---|---|
| old s01 | new s01 | Agent Loop |
| old s02 | new s02 | Tool Use |
| old s03 | new s05 | TodoWrite |
| old s04 | new s06 | Subagent |
| old s05 | new s07 | Skill Loading |
| old s06 | new s08 | Context Compact |
| old s07 | new s12 | Task System |
| old s08 | new s13 | Background Tasks |
| old s09 | new s15 | Agent Teams |
| old s10 | new s16 | Team Protocols |
| old s11 | new s17 | Autonomous Agents |
| old s12 | new s18 | Worktree Isolation |
| new only | s03, s04, s09, s10, s11, s14, s19, s20 | Permission, Hooks, Memory, System Prompt, Error Recovery, Cron, MCP, Comprehensive Agent |
从上表中我们可以看出我们需要补充的内容包括 s03(已补充)、s04(已补充)、s09(已补充)、s10、s11、s14、s19 以及 s20 八个章节的内容。
新版学习路径如下:
主线:能动手 → 能做复杂任务 → 能记住和恢复 → 能长期运行 → 能协作 → 能扩展并合体

前言
在上篇文章 Learn-Claude-Code | 笔记 | Memory Management | s09_new Memory 中,我们介绍了开源项目 learn-claude-code 新版第九个章节 s09_new: Memory 的内容,这篇文章我们继续跟着教程文档来学习规划与拆解相关内容,记录下个人学习笔记,和大家一起分享交流😄
Note:本篇文章主要学习记录 新版教程 第二部分 Planning & Coordination 中 s10: System Prompt 章节的内容。
github:https://github.com/shareAI-lab/learn-claude-code
reference:https://chatgpt.com/
1. s10: System Prompt
前面从 s01 到 s09,我们已经把一个 Coding Agent 的基础能力逐步搭起来了:s01 讲 Agent Loop,s02 讲 Tool Use,s03 加入 Permission,s04 加入 Hooks,s08 引入 Context Compact,s09 又进一步加入 Memory。到这里为止,Agent 已经不再是一个只能简单调用工具的小脚本,而是开始具备 “长期记忆、上下文压缩、工具执行、运行时扩展” 的完整雏形。
但是随着能力越来越多,一个新的工程问题开始出现:system prompt 不能再继续用一整段硬编码字符串来维护了。
在早期章节中,system prompt 很简单,例如:
SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."
这在 s01、s02 里完全够用,因为当时 Agent 只有最基础的身份说明和工具说明。但到了 s09 之后,Agent 已经有了 memory、compression、skill loading、permission、hooks 等能力。如果仍然把所有内容都写进一个巨大的 SYSTEM 字符串里,后续每增加一个能力,就要不断往里面追加说明。久而久之,这个 prompt 会变成一个难以维护的大杂烩。
s10 要解决的正是这个问题:system prompt 应该是运行时根据当前状态组装出来的,而不是提前写死的一整段固定文本。
2. 问题
s10 的核心问题可以概括为一句话:
prompt 是组装出来的,不是写死的。
在真实 Agent 系统里,system prompt 本质上不是一段静态说明,而是当前运行环境的 “配置快照”。它应该反映当前 Agent 到底有哪些工具、当前工作目录在哪里、有没有可用记忆、有没有加载某些技能、是否处在某种特殊模式中。
如果继续使用硬编码 SYSTEM 字符串,会带来三个明显问题。
第一个问题是 可维护性差。当工具、记忆、技能、权限、团队协议越来越多时,所有规则都挤在一个字符串里。想修改工具描述时,可能不小心影响身份说明;想增加 memory 规则时,又可能和已有上下文规则冲突。prompt 变得越来越难拆、难查、难调试。
第二个问题是 不够动态。同一个 Agent 在不同项目、不同目录、不同工具配置、不同记忆状态下,system prompt 应该不同。比如当前没有 .memory/MEMORY.md 时,就没必要告诉模型 “这里有相关记忆”;当前没有启用某个工具时,也不应该在 prompt 里描述这个工具。硬编码 prompt 无法很好反映真实运行态。
第三个问题是 浪费 token。system prompt 每轮都会发送给模型,如果每次都把所有能力说明全部塞进去,即使当前请求根本用不到,也会造成上下文浪费。更重要的是,过多无关说明也会让模型注意力分散,降低当前任务的执行稳定性。
因此,s10 不再把 prompt 当成 “一段固定文字”,而是把它拆成多个可独立维护、可按需加载、可缓存复用的 section。
3. 解决方案
教程文档中的 system-prompt-overview.svg 正是这一节的核心架构图:

这张图可以看成 s09 Memory 之后的一次 “prompt 工程升级”。图中蓝色部分表示 s09 已经保留的能力:messages[] 经过压缩和 memory loading 后送入 LLM,LLM 再决定是否调用 TOOL_HANDLERS,工具结果继续追加回 messages[]。也就是说,原来的 Agent Loop、压缩管线、记忆加载、工具执行逻辑都还在。
s10 新增的是上方绿色部分:PROMPT_SECTIONS、assemble_system_prompt 和 get_system_prompt。
PROMPT_SECTIONS 相当于一个 section 货架,把 system prompt 拆成多个有名字的段落。比如 identity 负责说明 Agent 是谁,tools 负责说明有哪些工具,workspace 负责说明当前工作目录,memory 负责说明是否存在相关记忆。每个 section 都是独立的,某个 section 的变化不会影响其他 section。
assemble_system_prompt 负责根据当前 context 选择哪些 section 应该被拼接进去。这里的关键是 “按需”,比如 identity、tools、workspace 是始终加载的,因为它们是 Agent 每轮都需要的基础信息;而 memory 只有在当前确实存在记忆内容时才加载。
get_system_prompt 则负责缓存。它会先把当前 context 用 json.dumps(context, sort_keys=True) 序列化成一个确定性的 key。如果当前 key 和上一轮一致,说明运行状态没有变化,就直接复用上一次组装好的 system prompt;如果 key 变化,才重新调用 assemble_system_prompt 组装。
所以 s10 的整体设计可以概括为:
用
PROMPT_SECTIONS拆 prompt,用assemble_system_prompt(context)按真实状态拼 prompt,用get_system_prompt(context)缓存 prompt。
这让 system prompt 从一段写死的字符串,变成了一个可以被运行时状态驱动的配置产物。
4. Runtime Prompt Assembly 流程图分析
Web 教程中的 Runtime Prompt Assembly 用 6 张图把 s10 的运行过程讲得非常清楚。它不是从代码角度出发,而是从 “系统如何一步步把运行态变成 prompt” 的角度出发。
1. Runtime State Arrives:运行态先到达

第一张图展示的是初始状态。左侧是 Runtime context,里面包含 workspace、tools、memory、skills 等运行时信息。中间是 Section shelf + cache,也就是 section 货架和缓存。右侧是最终的 System prompt,但此时还没有构建出来。
这张图想表达的是:system prompt 的来源不是一段静态文本,而是当前 runtime context。当前工作目录是什么、启用了哪些工具、memory 是否可用、skills 是否加载,这些都属于运行态信息。
换句话说,prompt 不是先验固定的,而是从当前 Agent 所处环境中 “长出来” 的。
2. Section Shelf Selects Owners:每个子系统拥有自己的 section

第二张图展示了 section shelf 的作用。每个 section 都有自己的 owner:identity 属于 core,tools 属于 tool registry,workspace 属于 runtime,memory + skills 属于 context loader。
这点非常重要。它说明 system prompt 不应该由一个地方集中硬写,而应该由不同子系统分别贡献自己的 prompt section。
比如工具系统最清楚当前有哪些工具,因此 tools section 应该由工具注册表负责。memory 系统最清楚当前有哪些可用记忆,因此 memory section 应该由 memory/context loader 负责。workspace 是运行时环境信息,因此 workspace section 应该由 runtime 负责。
这样做的好处是调试边界清晰。如果模型对工具理解错误,我们就去看 tools section;如果模型没有遵守记忆偏好,我们就看 memory section;如果工作目录不对,就看 workspace section。每个规则都有归属,不会全部混在一段 prompt 里。
3. Context Key Checks the Cache:用 context key 检查缓存

第三张图对应代码中的 get_system_prompt(context)。
中间的 context key 使用类似:
json.dumps(context, sort_keys=True)
这样的方式,把当前 runtime state 转成一个稳定的 cache key。只要 runtime state 没变,这个 key 就不会变,system prompt 就可以复用。
图中此时显示的是 cache miss: assemble sections,说明当前上下文还没有对应的缓存结果,需要重新组装 prompt。
这一张图强调的是:缓存不是拍脑袋做的,而是基于确定性的上下文状态。相同状态得到相同 key,相同 key 复用相同 prompt。这样 prompt 既具备动态性,又具备可复现性。
4. Prompt Is Assembled:section 被拼成最终 prompt

第四张图展示了 prompt 组装完成后的状态。
中间的 section shelf 里,identity、tools、workspace、memory + skills 都已经被选中并标记为可用。右侧的 System prompt 则显示出最终拼接出来的内容:
[identity]
You are a helpful coding agent.
[tools]
Available tools: bash, read_file.
[workspace]
Current workspace: /repo.
[memory + skills]
Load memory index and code-review skill.
这说明最终送给 LLM 的 system prompt 是由多个 section 组合出来的,而不是某个地方提前写死的一整段字符串。
这种结构化组装有两个好处。第一,它让 prompt 更容易审计,我们可以清楚看到每一段来自哪里。第二,它让后续扩展更自然,新增一个能力只需要新增一个 section,不需要重写整个 system prompt。
5. Same Key Reuses the Prompt:上下文不变就复用 prompt

如果 runtime context 没有变化,那么 context key 也不会变化。此时就不需要重新 assemble,系统直接复用上一次的 system prompt。图中显示:
cache hit: reuse prompt
这对应代码中的:
if key == _last_context_key and _last_prompt:
print("[cache hit] system prompt unchanged")
return _last_prompt
这一步虽然在教学代码里只是一个简单的字符串缓存,但它引出了真实 Agent 系统里非常重要的思想:prompt 的稳定性会直接影响缓存命中率。
在 Claude Code 这样的真实系统中,prompt cache 不只是为了少拼一次字符串,而是会影响 API 层的缓存命中、延迟和 token 成本。因此 prompt section 的顺序、静态/动态边界、动态内容变化范围,都会成为工程设计的一部分。
6. LLM Sees the Built Prompt:模型看到的是运行时产物

最后一张图展示的是最终结果:LLM 接收到的不是一段旧式硬编码 prompt,而是由 runtime state 组装出来的 system prompt。
这张图的重点在右侧:sent to LLM。也就是说,前面的 runtime context、section shelf、cache key、assembled prompt,最终都服务于一件事:在调用模型时,把一个可追踪、可解释、可缓存的 system prompt 交给 LLM。
这也解释了为什么 s10 很重要。模型本身并不知道当前工具、目录、记忆和技能状态,必须由 harness 层把这些状态整理成 prompt 交给它。s10 做的就是把这件事工程化。
完整动画演示如下图所示:

5. 工作原理(代码分析)
OK,下面我们就来看看代码具体是如何实现的:
1. PROMPT_SECTIONS:把一整段 prompt 拆成可维护的 section
s10 首先定义了一个 PROMPT_SECTIONS 字典:
PROMPT_SECTIONS = {
"identity": "You are a coding agent. Act, don't explain.",
"tools": "Available tools: bash, read_file, write_file.",
"workspace": f"Working directory: {WORKDIR}",
"memory": "Relevant memories are injected below when available.",
}
这一步看起来只是把字符串拆开,但它背后的工程意义很重要。
在旧版本里,system prompt 是一个整体。只要你要改 prompt,就必须去改同一大段字符串。随着能力增加,这种写法会越来越脆弱,因为不同能力之间没有边界。工具说明、身份说明、记忆说明、工作目录说明都混在一起,后续调试时很难知道到底是哪一段影响了模型行为。
而在 s10 中,每个 section 都有明确的名字和职责:
identity表示 Agent 的身份和行为风格,它告诉模型自己是一个 coding agent,并且应该执行任务而不是长篇解释。tools表示当前可用工具,它告诉模型可以使用bash、read_file、write_file。workspace表示当前工作目录,它给模型一个明确的操作边界。memory表示记忆相关提示,它只有在存在记忆内容时才真正参与组装。
这种拆法让 prompt 变得像代码模块一样可以维护。后面如果新增 skill、MCP、团队协议、错误恢复策略,都可以继续增加新的 section,而不是把所有规则继续塞进一个大字符串。
2. assemble_system_prompt:根据真实状态按需拼接
接下来是 assemble_system_prompt:
def assemble_system_prompt(context: dict) -> str:
"""Select and join prompt sections based on current context."""
sections = []
# Always loaded — identity, tools, workspace
sections.append(PROMPT_SECTIONS["identity"])
sections.append(PROMPT_SECTIONS["tools"])
sections.append(PROMPT_SECTIONS["workspace"])
# Conditional — memory loaded when MEMORY.md exists and has content
memories = context.get("memories", "")
if memories:
sections.append(f"Relevant memories:\n{memories}")
return "\n\n".join(sections)
这个函数的作用是 “选 section,然后拼成最终 system prompt”。
这里最关键的点是:section 是否加载不是根据用户问题里的关键词判断,而是根据 context 中的真实运行状态判断。
例如用户没有说 “memory” 这个词,但如果 .memory/MEMORY.md 存在且有内容,那么 memory section 就应该加载。反过来,即使用户提到了 “记忆”,如果当前系统里没有真实记忆内容,也不应该硬塞一个空的 memory section。
这和 s09 的设计是一脉相承的:Memory 是从 .memory/MEMORY.md 这样的真实文件系统状态中加载出来的,而不是靠模型自己猜测。s10 把这种思想推广到了 system prompt 组装上:prompt 不再由静态文本决定,而是由当前运行态决定。
在这个教学版本中,始终加载的 section 有三个:identity、tools、workspace。这三个是 Agent 每轮必须知道的基础信息。条件加载的 section 是 memory,只有当 context["memories"] 非空时才加入。
最后用 "\n\n".join(sections) 拼接,保证不同 section 之间有清晰分隔,方便阅读和调试。
3. get_system_prompt:用 context key 做缓存
s10 的第二个关键函数是 get_system_prompt:
_last_context_key = None
_last_prompt = None
def get_system_prompt(context: dict) -> str:
global _last_context_key, _last_prompt
key = json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
if key == _last_context_key and _last_prompt:
print(" \033[90m[cache hit] system prompt unchanged\033[0m")
return _last_prompt
_last_context_key = key
_last_prompt = assemble_system_prompt(context)
loaded = ["identity", "tools", "workspace"]
if context.get("memories"):
loaded.append("memory")
print(f" \033[32m[assembled] sections: {', '.join(loaded)}\033[0m")
return _last_prompt
这个函数解决的是 “重复组装” 的问题。
如果当前 context 没有变化,那么最终拼出来的 system prompt 也不会变化。此时没有必要每轮都重新拼接字符串。因此代码用 _last_context_key 和 _last_prompt 保存上一次的上下文 key 和 prompt 结果。
这里有一个细节:代码中使用的是:
json.dumps(context, sort_keys=True, ensure_ascii=False, default=str)
而不是 Python 内置的 hash()。
原因是 json.dumps(..., sort_keys=True) 可以把 dict 转换成稳定、确定性的字符串。只要 context 内容相同,生成的 key 就相同。而 Python 的 hash() 不适合这里:一方面 Python 的 hash 有进程随机化,另一方面 dict/list 这类嵌套结构本身也不能直接 hash。
当 key 命中时,代码打印:
[cache hit] system prompt unchanged
这说明当前 system prompt 没有重新组装,而是直接复用了缓存结果。
当 key 不命中时,代码重新调用 assemble_system_prompt(context),并打印当前加载了哪些 section:
[assembled] sections: identity, tools, workspace
如果 memory 存在,则会变成:
[assembled] sections: identity, tools, workspace, memory
这也让 prompt 组装过程变得可观测。我们不再只能猜测模型看到了什么,而是可以通过日志明确知道哪些 section 被送进了 system prompt。
需要注意的是,教学版里的 cache 只是 Python 进程内的字符串缓存,它只是避免重复拼接 prompt。它和 Claude Code 真正的 API prompt cache 不是一回事。Claude Code 源码里还有更复杂的静态/动态 section 边界、global cache scope、section cache 等机制。教学版先把最核心的思想讲清楚:运行时状态不变,prompt 就可以复用;运行时状态变化,prompt 就重新组装。
4. update_context:用真实状态生成 prompt 输入
接下来是 update_context:
def update_context(context: dict, messages: list) -> dict:
"""Derive context from real state: which tools exist, whether memory files exist."""
memories = ""
if MEMORY_INDEX.exists():
content = MEMORY_INDEX.read_text().strip()
if content:
memories = content
return {
"enabled_tools": list(TOOL_HANDLERS.keys()),
"workspace": str(WORKDIR),
"memories": memories,
}
这个函数负责把当前真实环境整理成 context。
它做了三件事。
第一,它读取当前实际注册的工具:
"enabled_tools": list(TOOL_HANDLERS.keys())
也就是说,prompt 中描述的工具应该来自真实工具注册表,而不是手写猜测。
第二,它记录当前工作目录:
"workspace": str(WORKDIR)
这样 system prompt 可以知道当前操作边界。
第三,它检查 .memory/MEMORY.md 是否存在:
if MEMORY_INDEX.exists():
content = MEMORY_INDEX.read_text().strip()
如果存在且有内容,就把它放入 context["memories"]。如果不存在,memory 就是空字符串,后续 assemble_system_prompt 就不会加载 memory section。
这一步体现了 s10 的核心工程原则:prompt 由真实状态驱动,而不是由自然语言关键词驱动。
5. agent_loop:主循环不再使用硬编码 SYSTEM
最后看 agent_loop:
def agent_loop(messages: list, context: dict):
"""Main loop — uses assembled system prompt instead of hardcoded SYSTEM."""
system = get_system_prompt(context)
while True:
response = client.messages.create(
model=MODEL, system=system, messages=messages,
tools=TOOLS, max_tokens=8000)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
results = []
for block in response.content:
if block.type != "tool_use":
continue
print(f"\033[36m> {block.name}\033[0m")
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler else f"Unknown: {block.name}"
print(str(output)[:200])
results.append({"type": "tool_result",
"tool_use_id": block.id, "content": output})
messages.append({"role": "user", "content": results})
# Re-evaluate context and prompt after each tool round
context = update_context(context, messages)
system = get_system_prompt(context)
这里最重要的变化是:
system = get_system_prompt(context)
以前是直接把硬编码的 SYSTEM 传给模型。现在是每轮根据当前 context 获取 system prompt。
执行流程变成:
1. 根据当前 context 获取 system prompt;
2. 调用 LLM;
3. 如果模型请求工具,就执行工具;
4. 工具结果追加回 messages;
5. 工具执行后重新 update_context;
6. 根据新的 context 再次 get_system_prompt;
7. 继续下一轮。
这意味着如果某个工具调用改变了环境状态,例如创建了 .memory/MEMORY.md,那么下一轮 update_context 就会发现 memory 文件已经存在,get_system_prompt 就会重新组装 prompt,并把 memory section 加进去。
这也是 s10 和 s09 的连接点:s09 解决 “记忆如何存储、加载、提取、整理”,s10 解决 “这些运行时状态如何进入 system prompt”。
博主在给定下面的提示词情况下:
Read the file README.md
Note:观察始终加载的三个 section。
想通过调试看看整个过程发生了什么,我们来具体分析下:
Prompt 1:Loop 1

Prompt 1:第一次循环获取系统提示词

Prompt 1:第一次循环模型响应

Prompt 1:第一次循环工具执行结果

Prompt 1:第一次循环更新上下文
从第一轮调试可以看到,agent_loop() 一开始并没有继续使用过去那种固定写死的 SYSTEM 字符串,而是先调用:
system = get_system_prompt(context)
这里的 context 当前包含三个核心字段:enabled_tools、workspace 和 memories。由于此时还没有额外的 memory 内容,因此最终组装出来的 system prompt 只包含三个始终存在的 section:identity、tools 和 workspace。
也就是说,s10 的关键变化不是改变 Agent Loop 的基本形态,而是把原来散落在代码里的系统提示词,抽象成了一个可组装、可追踪、可缓存的运行时产物。此时终端中打印出了:
[assembled] sections: identity, tools, workspace
这说明当前 system prompt 是由这三个 section 动态拼接出来的,而不是一段固定文本。
随后模型收到用户请求 Read the file README.md,在第一轮响应中返回了两个 block:一个是 ThinkingBlock,表示模型内部判断用户希望读取 README.md;另一个是 ToolUseBlock,表示模型选择调用 read_file 工具读取文件。这一点和前面章节的 Agent Loop 逻辑保持一致:模型仍然负责 “决定要不要用工具”,工具执行权仍然在 harness 层。
工具执行完成后,read_file 返回 README.md 的文件内容,并被封装成 tool_result 追加回 messages。这一步之后,s10 额外做了一件重要的事:调用 update_context(context, messages),并重新执行:
system = get_system_prompt(context)
这表示每一轮工具执行结束后,系统都会重新评估当前运行时状态是否发生变化。如果 workspace、工具列表、memory index 等上下文没有变化,那么 system prompt 可以继续复用缓存;如果上下文发生变化,则需要重新组装。
因此,第一轮 Loop 的完整链路可以概括为:用户输入 → get_system_prompt(context) → 组装 identity/tools/workspace → LLM 选择 read_file → 工具执行并返回 README.md → tool_result 追加到 messages → update_context() → 再次检查 system prompt 是否需要更新。
这一轮调试说明,s10 的 system prompt 已经从 “静态配置” 变成了 “运行时状态的投影”。模型看到的不是硬编码字符串,而是由当前工具、工作目录、记忆状态等信息动态生成的执行说明。
Prompt 1:Loop 2

Prompt 1:第二次循环获取系统提示词

Prompt 1:第二次循环模型响应
第二轮 Loop 开始时,代码再次调用:
system = get_system_prompt(context)
从调试结果可以看到,这一轮生成的 system prompt 仍然只包含:identity、tools、workspace,也就是说,在第一轮读取 README.md 之后,虽然 messages 中已经多了工具调用结果,但 context 本身并没有出现新的 memory section,也没有修改工具列表或 workspace 信息,因此 system prompt 的结构没有发生变化。
模型在第二轮响应中不再继续发起工具调用,而是根据上一轮 read_file 返回的内容,直接生成 README.md 的总结。此时 response.stop_reason != "tool_use",Agent Loop 结束。
OK,本轮对话结束后我们继续使用下面的提示词写入记忆索引:
Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"
Prompt 2
调用工具写入文件这些我们就跳过分析了,我们直接来看整个过程完成中终端的输出:

Prompt 2:终端完整输出
Prompt 2 的目标是创建 .memory/MEMORY.md 文件:
Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"
这一步本质上是在模拟 s09 Memory 中的记忆索引文件。前面 Prompt 1 中,context["memories"] 为空,所以 system prompt 只包含 identity、tools、workspace 三个 section。而当 .memory/MEMORY.md 被写入后,系统的运行时状态发生了变化:memory index 从 “空” 变成了 “存在一条 test memory”。
从终端完整输出可以看到,在写入 .memory/MEMORY.md 之前,多次提示:
[cache hit] system prompt unchanged
这说明在创建文件的工具调用过程中,system prompt 仍然复用了旧缓存。但是当工具执行完成、update_context() 重新扫描上下文后,系统发现 memory index 已经变化,于是重新组装 system prompt,并打印:
[assembled] sections: identity, tools, workspace, memory
这就是 s10 最核心的现象:system prompt 的 section 不是固定存在的,而是根据运行时 context 按需拼接。只要 .memory/MEMORY.md 中出现了可用的记忆索引,memory section 就会被加入 system prompt。
因此,Prompt 2 的意义不是“写文件”本身,而是验证 system prompt 可以随着运行时状态变化而变化。新增 memory index 之后,系统提示词从:identity + tools + workspace 升级为 identity + tools + workspace + memory,这正好对应教程图中的主线:s09 负责让 memory 存在,s10 负责把 memory 作为一个 section 拼进 system prompt。
OK,本轮对话结束后我们继续使用下面的提示词观察下 memory section 是否出现:
Read the file code.py
Prompt 3

Prompt 3:第一次循环获取系统提示词
Prompt 3 再次请求读取文件:
Read the file code.py
这一次进入 agent_loop() 后,调试结果显示 get_system_prompt(context) 生成的 system prompt 中已经出现了 memory section:
Relevant memories:
- [test](test.md) — test memory
这说明 Prompt 2 写入的 .memory/MEMORY.md 已经被 update_context() 捕获,并在下一轮用户请求中进入 system prompt。换句话说,memory 不只是一个普通文件,而是被运行时上下文识别后,成为模型输入规则的一部分。
这次调试最重要的结论是:s10 没有改变 Agent Loop 的执行骨架,而是把 system prompt 从硬编码文本升级成了运行时组装机制。
在 Prompt 1 中,我们看到当 memory 为空时,系统只组装 identity、tools、workspace 三个基础 section。模型依然按照原来的 loop 方式选择工具、执行工具、接收工具结果,并在第二轮生成最终回答。
在 Prompt 2 中,我们通过写入 .memory/MEMORY.md 改变了运行时上下文。此时 system prompt 的缓存失效,系统重新组装提示词,并新增了 memory section。这说明 system prompt 的内容不是固定的,而是由当前 context 决定的。
在 Prompt 3 中,我们进一步验证了 memory section 已经真正进入 system prompt。模型后续请求中看到的不只是用户当前输入,还包括 harness 根据 .memory/MEMORY.md 拼接出来的长期记忆索引。
所以,s10 的工程价值可以概括为一句话:让 system prompt 从 “写死的一段字符串”,变成 “由多个可维护 section 按运行时状态动态拼接出来的产品”。
OK,以上就是 s10 System Prompt 工作原理的完整分析了。
那大家感兴趣的话可以试试下面这些 prompt 感受下 System Prompt 动态拼接后的一些变化:
1. Read the file README.md(观察始终加载的三个 section)
2. Create a file called .memory/MEMORY.md with content "- [test](test.md) — test memory"(写入记忆索引)
3. Read the file code.py(观察 memory section 是否出现)
6. 相对 s09 的变更
| 组件 | 之前 (s09) | 之后 (s10) |
|---|---|---|
| prompt | 硬编码 SYSTEM 字符串 | PROMPT_SECTIONS + assemble_system_prompt |
| 缓存 | 无 | get_system_prompt(json.dumps 检测 + 缓存) |
| 新函数 | — | assemble_system_prompt, get_system_prompt, update_context |
| 工具 | bash, read_file, write_file (3) | bash, read_file, write_file (3) — 不变 |
| 循环 | 用固定 SYSTEM | 用 get_system_prompt(context) |
7. 小结
s10 这一节表面上讲的是 system prompt,实际上讲的是一个更通用的 Agent Harness 设计原则:不要把运行时配置写死在 prompt 里,而要让 prompt 成为当前系统状态的组装结果。
在早期 Agent 中,硬编码 system prompt 足够简单直接。但当 Agent 开始拥有工具、权限、Hooks、记忆、技能、压缩、任务系统之后,prompt 就不能再被当成一段普通字符串维护。它必须被拆成 section,由不同子系统分别贡献内容,再由 harness 根据当前真实状态按需拼接。
这样一来,prompt 的职责边界更清楚,调试路径更明确,扩展能力也更强。新增一个能力,不再是往大字符串里硬塞一句话,而是新增一个有 owner、有加载条件、有缓存边界的 section。
所以 s10 的关键收获可以总结为:System Prompt 不是静态说明书,而是 Agent 运行态的结构化投影。Harness 负责收集真实状态、选择 prompt section、组装 system prompt,并在状态不变时复用缓存。这一步让 Agent 从 “写死规则” 走向 “运行时配置”,也为后续 Error Recovery、MCP、Comprehensive Agent 等更复杂能力打下了基础。
OK,以上就是本期想要分享的全部内容了。
结语
本篇文章我们围绕 s10 System Prompt 这一节,完整梳理了 Agent 是如何将 system prompt 从 “写死的一段字符串”,升级为可动态组装、可缓存、可复用的运行时产物。
相比 s09 Memory,s10 的核心变化不在于新增功能,而在于将运行时状态与模型输入明确分离:不同子系统(identity、tools、workspace、memory 等)各自贡献 prompt section,Harness 根据当前 context 按需组装,并在 context 未变化时复用缓存。这一设计让 prompt 变得可维护、可扩展,同时避免了无关 token 消耗和模型注意力分散。
从工程视角来看,这一步是对 Agent Loop 的重要优化:主循环无需关注 prompt 内部细节,权限、工具、工作目录、记忆状态的变化都通过 context 驱动 prompt 自动更新。这样,Agent 不再依赖硬编码规则,而是通过运行时状态生成输入,使模型始终看到的是反映真实环境的系统快照。
更进一步,s10 的设计体现了一个通用原则:系统越复杂,运行时配置越不应该硬编码;应通过结构化、按需加载和缓存机制管理信息流。这不仅提升了 prompt 的可调试性,也为后续 Error Recovery、MCP 和 Comprehensive Agent 等高级能力的落地奠定了基础。
可以说,s10 完成了从 “静态规则” 到 “动态运行态投影” 的关键跃迁,为整个 Agent 系统提供了可持续扩展和长期运行的底层架构能力。
下篇文章我们将来学习新版教程 s11 Error Recovery 章节的内容,敬请期待🤗
参考
更多推荐
所有评论(0)