跳到主要内容

系统提示词:每一轮都从零重建

30 秒导读: CowAgent 的模型每一轮都看到一份当场现拼的系统提示词——不是启动时定好、之后不变的常量,而是每次请求前从磁盘重读 AGENT.md/USER.md/RULE.md、刷新技能/工具/时间后,按"重要性顺序"拼出来的一大段文字。本章讲清楚:模型到底看到了什么,以及这个"每轮重建"的设计带来了什么。

本章只讲这些东西如何进入 prompt。技能、记忆、知识各自的运行时实现,分别见 05-skills-tools-mcp.md04-memory-knowledge.md;整个对话循环怎么转见 01-agent-loop.md


1. 这是什么(零基础也能懂)

一句话定义: 系统提示词(system prompt)是每次调用大模型时放在最前面、不属于对话内容的"给模型的开场说明"。CowAgent 把它做成了每一轮都重新生成的东西。

它解决什么问题? 想象你有一个 AI 管家,住在一个叫"工作空间"的文件夹里。这个文件夹里有几份文件定义它是谁(AGENT.md)、你是谁(USER.md)、这个家的规矩(RULE.md)。

现在问题来了:如果你在对话中说"以后叫你小牛吧",AI 用 editAGENT.md 里的名字改了——下一句话它还记得自己叫小牛吗?

  • 如果系统提示词是启动时生成、之后一直缓存的常量 → 不记得,得重启进程才生效。
  • CowAgent 的答案:下一轮请求前,它会把 AGENT.md 重新从磁盘读一遍,所以改动立刻生效。

一句话直觉: 别的 agent 常把"人格"当成开机时定死的启动参数;CowAgent 把它当成每轮都重新加载的配置文件。就像一个 Web 服务器开了"热重载"——你改了配置文件,不用重启,下一个请求就用上了。

用起来什么样? 你完全感知不到这个机制在跑。它藏在每次 agent.run_stream(...) 调用的第一步里:

# 示意,非源码:一轮对话开始时发生的第一件事
response1 = agent.run_stream("以后就叫你小牛") # 这轮里 AI 把 AGENT.md 的名字改成"小牛"
response2 = agent.run_stream("你叫什么?") # 这轮开始时重读 AGENT.md → 已经看到"小牛"
# 重点看:两轮之间没有重启,改动却生效了——因为每轮 prompt 都是现拼的

2. 顶层全景(它大概怎么转)

2.1 两个主角

整个机制就两个文件在唱主角:

部件干什么在哪个文件
get_full_system_prompt每轮的"总指挥":刷新技能、重读上下文文件、定语言,然后叫 builder 拼装agent/protocol/agent.py:106
build_agent_system_prompt真正的"拼装工":把工具/技能/记忆/知识/工作空间/身份/上下文/运行时按顺序拼成一大段agent/prompt/builder.py:78
load_context_files从磁盘读 AGENT.md/USER.md/RULE.md/MEMORY.md/BOOTSTRAP.mdagent/prompt/workspace.py:110

2.2 怎么读这张图

从上到下是一轮请求里"prompt 从无到有"的时间顺序;命中即往下走。

每次 agent.run_stream(user_message) 被调用


┌──────────────────────────────────────────────┐
│ get_full_system_prompt() agent.py:106 │ ← 每轮的入口,不用缓存
│ ① 刷新技能 refresh_skills() │
│ ② 从磁盘重读上下文文件 load_context_files() │ → AGENT/USER/RULE/MEMORY/BOOTSTRAP
│ ③ 定回复骨架语言 i18n.get_language() │
│ ④ 叫拼装工干活 ↓ │
└───────────────────────┬──────────────────────┘

┌──────────────────────────────────────────────┐
│ build_agent_system_prompt() builder.py:78 │ ← 按"重要性顺序"拼分节
│ 1 工具 → 2 技能 → 3 记忆 → 3.5 知识 │
│ → 4 工作空间 → 5 用户身份 → 6 项目上下文文件 │
│ → 7 运行时(时间/模型) → 8 回复语言 │
└───────────────────────┬──────────────────────┘

拼好的一大段字符串

⑤ 若有 extra_system_suffix 就叠加在最后

作为 system 传给模型,开始这一轮推理

主线一句话: 每轮请求 → get_full_system_prompt 现读现拼 → 得到反映"此刻磁盘状态 + 当前时间"的 prompt → 喂给模型。下一轮再来一遍。


3. 核心原理

3.1 为什么"每轮重建"是关键设计

它要解决的小问题: 让 agent 能修改自己——改名字、改性格、写记忆、建知识——并且改动立刻在下一轮生效,而不是"下次重启才算"。

思路: 与其维护一堆"缓存失效"逻辑(哪份文件变了、要不要重刷),不如干脆每轮都当第一次:重新读盘、重新拼。读几个小文件的成本,远低于"状态不同步"带来的 bug。

真实实现——总指挥每轮做的四件事,见 agent/protocol/agent.py:106 get_full_system_prompt:

if self.skill_manager:
self.skill_manager.refresh_skills() # ① 重扫技能目录

context_files = load_context_files(self.workspace_dir) if self.workspace_dir else None # ② 重读磁盘

lang = i18n.get_language() # ③ 定语言
builder = PromptBuilder(workspace_dir=..., language=lang)
full = builder.build(tools=..., context_files=..., skill_manager=..., memory_manager=..., runtime_info=...) # ④ 拼

方法文档字符串把这个意图写得很直白(agent.py:106-113):"Build the complete system prompt from scratch every time. Re-reads AGENT.md / USER.md / RULE.md from disk ... so any change takes effect immediately."

关键细节:兜底不崩。 整个重建包在 try/except 里(agent.py:138-142):一旦重读/拼装出错,就回退到构造函数里存的 self.system_prompt 这份缓存旧值,而不是让这一轮直接崩掉。这体现了一个取舍——新鲜优先,但可用性更优先

它在哪被调用? 每次 run_stream 的开头(agent.py:428),拿到 full_system_prompt 再交给流式执行器。所以"每轮重建"是每次用户发消息都真实发生的,不是理论上的。

3.2 extra_system_suffix:给自进化留的叠加位

它要解决的小问题: 自进化 agent(见 06-self-evolution.md)在空闲时回看对话、悄悄成长时,它既要遵守用户在 AGENT.md/RULE.md 里定的一切偏好,又要知道自己此刻的"任务简报"(比如"你现在的活是复盘刚才那段对话")。

思路: 不去改那份共享的、每轮重建的完整 prompt,而是提供一个追加位:把任务简报拼在重建好的完整 prompt 之后。这样它继承了全部上下文(工具、工作空间、用户偏好、时间),又多带了一份专属任务说明。

真实实现,见 agent.py:135-137:

if self.extra_system_suffix:
full = f"{full}\n\n{self.extra_system_suffix}"
return full

字段本身的注释(agent.py:55-59)点明了用途:"Used by the self-evolution review agent to add its task brief on top of the full context ... so it both follows the user's preferences and knows its evolution job."

为什么放最后? 因为它是"临时叠加的任务",不是"长期身份"。放最后既不污染每轮重建的主体,又保证它在同一份 prompt 里可见。连兜底路径(重建失败时)也会把 suffix 叠加到缓存 prompt 上(agent.py:140-141)——任务简报不能因为重建出错就丢。

3.3 分节顺序:prompt 本身是一套"教程"

它要解决的小问题: 模型对同一份 prompt,靠前的内容权重更高。所以"先放什么"是一个设计决策,不是随便排的。

思路: CowAgent 按"能力 → 用法 → 环境 → 身份 → 事实"的逻辑流,把 8 个分节固定排序。build_agent_system_prompt 的文档字符串直接把这套顺序写成注释(builder.py:92-101),装配代码则严格照此执行(builder.py:118-154):

分节构建函数(builder.py)一句话职责
1🔧 工具_build_tooling_section :185先亮出"手脚":有哪些工具、怎么调
2🧩 技能_build_skills_section :289紧跟工具,因为技能靠 read 工具打开
3🧠 记忆_build_memory_section :353何时必须检索、何时主动写、写哪个文件
3.5📚 知识_build_knowledge_section :449注入 knowledge/index.md + 自动写库规则
4📂 工作空间_build_workspace_section :553路径规则 + "AGENT.md 是灵魂文件"
5👤 用户身份_build_user_identity_section :522姓名/称呼/时区(可选)
6📋 项目上下文_build_context_files_section :653把 AGENT/USER/RULE/MEMORY 原文贴进来
7⚙️ 运行时_build_runtime_section :699当前时间、模型
8🌐 回复语言_build_response_language_section :157永远追加,跟随用户输入语言

关键细节:每节都是条件装配。 没有工具就跳过工具节、没有 memory_manager 就跳过记忆节(builder.py:121-149);知识节还多一道开关 conf().get("knowledge", True)(builder.py:133)。prompt 因此只包含此刻真实拥有的能力,不给模型看不存在的东西。


4. 深入实现:几条"教学暗线"

这几个分节不只是罗列信息,它们把行为规则写死进了 prompt。这是本章最值得带走的部分——设计者用 prompt 文字充当"给模型的教程"。

4.1 工具节:精简一行摘要 + 调用风格约束

_build_tooling_section(builder.py:185)把每个工具的完整 schema 塞进 prompt,而是给每个已知工具配一行中文摘要(builder.py:210-228),细节留给工具 schema:

工具prompt 里的一行摘要
read读取文件内容
edit精确编辑文件
send发送本地文件给用户(仅限本地文件,URL 直接放回复里)
browser控制浏览器(关键结果或需要协助可截图发送给用户)

工具还按一个固定展示顺序 tool_order(builder.py:231-237)排列:读写编辑在前、shell/搜索居中、发送/视觉在后;不在列表里的工具排到最后。这让模型每轮看到的工具清单稳定、可预期。

摘要之后紧跟一段调用风格约束(builder.py:270-284),等于把"该怎么用工具"写死:

  • 多步骤/敏感操作要简要说明在做什么、为什么;
  • 持续推进到任务完成再报告;
  • 涉及密钥令牌必须脱敏;
  • URL 直接放回复文本,别下载后再用 send

4.2 记忆节:把"何时检索/何时写/存哪"写进 prompt

_build_memory_section(builder.py:353)是"prompt 即行为规范"的最典型例子。注意它先做条件判断:只有当工具里真的有 memory_search/memory_get 时才生成本节(builder.py:358-364)——没有记忆工具就不写记忆规则,避免让模型去调用不存在的能力。

它写死了三类硬规则:

① 何时必须检索(mandatory),builder.py:411-418——用户问过往、引用旧决定、提到人物/偏好/待办,或"你对某事不确定"时,必须先检索再回答;并给出三步降级:

不确定位置 → memory_search(关键词/语义)
已知位置 → memory_get(直接读对应行)
搜不到 → memory_get 读最近两天记忆

② 何时主动写入,builder.py:427-432——用户说"记住/以后/总是",或产生重要结论/约定时,主动写、无需告知用户

③ 存哪个文件(这是设计的精髓),builder.py:434-440:

内容落到哪
长期核心信息MEMORY.md
当天事件/进展memory/YYYY-MM-DD.md(prompt 里会填入今天的实际日期)
结构化知识knowledge/

那个"今天的日期"是在构建时用 datetime.now() 算出来的(builder.py:366-367)——又一个"每轮重建"带来的好处:日期永远是新鲜的。最后还有一条红线:禁止写入敏感信息(API 密钥、令牌)。

4.3 知识节:直接注入 index.md + 规定自动写库

_build_knowledge_section(builder.py:449)做两件事:

① 把 knowledge/index.md 的内容原样注入 prompt(builder.py:451-459, 504-510)。这意味着模型每轮都能看到"我现在都攒了哪些知识"的目录,不用额外调工具去查——文件不存在就整节跳过。

② 规定自动写库规则(mandatory),builder.py:490-500:分享文章 → 写 knowledge/sources/;讨论出结论 → knowledge/analysis/;涉及重要实体 → knowledge/entities/;讨论技术概念 → knowledge/concepts/。并且用加粗强调:"不要问『要不要存到知识库』——符合场景就直接写入,这是你的本能行为。"(builder.py:500)

4.4 工作空间节:相对/绝对路径规则 + "AGENT.md 是灵魂文件"

_build_workspace_section(builder.py:553)给模型讲清"这个家怎么用":

路径规则(builder.py:601-615):所有相对路径都相对于工作空间目录;访问工作空间之外的目录(项目代码、系统文件)必须用绝对路径;不确定就 bash pwd 确认。这段直接把 workspace_dir 的真实值插进文字,所以模型看到的是具体路径而非占位符。

"文件已自动加载"提醒(builder.py:617-624):明确告诉模型 AGENT.md/USER.md/RULE.md/MEMORY.md 已经在 prompt 里了,别再用 read 重复读——省一次工具往返。

"AGENT.md 是灵魂文件"这个说法出现在项目上下文节里(_build_context_files_section, builder.py:680-687):当 prompt 里检测到有 AGENT.md,就加一句"AGENT.md 是你的灵魂文件 🪞:严格遵循其人格、语气……当用户透露对你性格/风格/能力边界的新期望,主动用 edit 更新 AGENT.md"。这把"自我进化"从抽象概念落成了一条可执行指令

4.5 上下文文件从哪来:load_context_files

前面几节的规则是"死"文字,而真正因人而异的身份来自 load_context_files(workspace.py:110)。它按优先级读五个文件(workspace.py:121-129):

AGENT.md → USER.md → RULE.md → MEMORY.md → BOOTSTRAP.md

三个精巧处:

  • 占位符跳过:文件还是模板占位符(如 *(在首次对话时填写))就不加载(workspace.py:154-155, _is_template_placeholder :216)——避免把空模板塞进 prompt。
  • MEMORY.md 截断:长期记忆按"取最后 N 行、200 行 / 25 KB 封顶"截断(workspace.py:158-160, _truncate_memory_content :190),保护上下文窗口;超了会在开头加一句"更早的条目已截断,用 memory_search/memory_get 取全文"。
  • BOOTSTRAP.md 自清理:首次引导文件,一旦 onboarding 完成就自动删除、不再加载(workspace.py:139-148)。

这些文件的原文最终被 _build_context_files_section(builder.py:653)以 ## 文件名 + 原文的形式贴进 prompt 第 6 节。

4.6 运行时节:动态时间的两种取法

_build_runtime_section(builder.py:699)支持动态时间:优先调用 runtime_info["_get_current_time"]() 这个 callable 取实时时间(builder.py:713-720),取不到才回退到静态的 current_time 字段(builder.py:721-733)。模型同理也支持 _get_model() 动态取值(builder.py:742-748)。因为 prompt 每轮重建,这里拿到的时间总是这一轮的当下

4.7 回复语言:骨架语言 ≠ 回复语言

最后一节 _build_response_language_section(builder.py:157)有个容易忽略的设计:prompt 的骨架用什么语言(中/英),由 i18n.get_language() 决定;但回复用什么语言,永远跟随用户输入。

所以这一节无条件追加(builder.py:152),写死一句"默认使用与用户输入相同的语言回复"(builder.py:171-176)。效果:即便整份 prompt 是中文骨架搭的,遇到英文用户照样用英文答——注释原话是"a Chinese-built prompt still answers an English user in English"(builder.py:161-162)。


5. 巧妙之处(可借鉴的技术)

  • "每轮从零重建"替代"缓存 + 失效逻辑"。 用重读几个小文件的确定性成本,换掉一整类"状态不同步"的 bug,让 agent 的自我修改能即时生效。get_full_system_prompt(agent.py:106)。
  • 重建失败必回退,永不因刷新崩掉。 try/except 兜底到缓存 self.system_prompt(agent.py:138-142)——新鲜优先,可用性更优先。
  • prompt 即行为规范。 把"何时检索记忆、何时写库、存哪个文件"这类规则写死进文字(_build_memory_section :353_build_knowledge_section :449),而不是塞进代码逻辑——规则对模型可见、可调、可 edit
  • 条件装配 = prompt 只反映真实能力。 没工具/没记忆管理器/知识开关关了,对应节直接不出现(builder.py:121-149),模型永远不会被引导去用不存在的能力。
  • 叠加位而非改主体。 extra_system_suffix(agent.py:135-137)让自进化任务简报"贴在完整上下文之后",复用全部偏好而不污染共享 prompt。
  • 骨架语言与回复语言解耦。 追加式的回复语言节(builder.py:157)保证 prompt 用什么语言搭建,和它该用什么语言回话,是两件独立的事。

6. 边界与局限

  • 每轮重建有 I/O 成本。 每次请求都读多份磁盘文件、扫技能目录。对本地单用户助手可忽略,但高并发/网络文件系统场景下这是需要留意的开销(代码未见针对性缓存)。
  • MEMORY.md 截断可能丢上下文。 超过 200 行 / 25 KB 的旧记忆不会进 prompt(workspace.py:190),模型得靠 memory_search/memory_get 主动取——它是否记得去取,取决于记忆节规则是否被遵守。
  • 规则靠模型"自觉"。 记忆/知识的"mandatory"是写在 prompt 里的约束文字,不是硬性拦截;模型不照做时,代码层面并不强制。真正的落库实现见 04-memory-knowledge.md
  • 本章不覆盖运行时实现。 技能如何渐进式披露、记忆如何混合检索、知识如何写入,都在别的章;这里只讲它们如何进入 prompt

7. 横向对比

同 shelf 的其它 chat-agent 常把系统提示词当启动常量:进程起来时定好,之后靠"注入/追加"局部更新。CowAgent 的取舍相反——把 prompt 当每轮重算的纯函数,输入是"此刻的磁盘状态 + 工具 + 时间",输出是这一轮的 prompt。

代价是每轮的读盘开销,收益是自我修改即时生效无需维护缓存失效。这与它"住在一个可自我编辑的工作空间里、能改自己灵魂文件"的整体设计(index.md)是自洽的:身份既然能被 edit,prompt 就必须每轮重读才对得上。


8. 代码地图(导航索引)

主题文件符号
每轮重建入口(总指挥)agent/protocol/agent.py:106Agent.get_full_system_prompt
自进化任务简报叠加位agent/protocol/agent.py:135 / :55extra_system_suffix
重建在对话循环中的调用点agent/protocol/agent.py:428Agent.run_stream
分节装配(按重要性顺序)agent/prompt/builder.py:78build_agent_system_prompt
构建器类封装agent/prompt/builder.py:23PromptBuilder.build
工具节(一行摘要 + 调用风格)agent/prompt/builder.py:185_build_tooling_section
技能节(靠 read 打开 SKILL.md)agent/prompt/builder.py:289_build_skills_section
记忆节(检索/写入/存哪写死)agent/prompt/builder.py:353_build_memory_section
知识节(注入 index.md + 自动写库)agent/prompt/builder.py:449_build_knowledge_section
工作空间节(路径规则 + 灵魂文件)agent/prompt/builder.py:553_build_workspace_section
项目上下文节(AGENT.md 是灵魂文件)agent/prompt/builder.py:653_build_context_files_section
运行时节(动态时间/模型)agent/prompt/builder.py:699_build_runtime_section
回复语言节(跟随用户输入)agent/prompt/builder.py:157_build_response_language_section
从磁盘读上下文文件agent/prompt/workspace.py:110load_context_files
MEMORY.md 截断agent/prompt/workspace.py:190_truncate_memory_content
模板占位符跳过agent/prompt/workspace.py:216_is_template_placeholder