这是本系列最重要的"横向能力"篇。前面所有篇都在讲"Agent 怎么运行",这一篇讲Agent 每轮调用 LLM 时,最终进入模型窗口的那堆 token 到底是怎么来的。
每轮给模型构造的上下文,才是 Agent 的真正 CPU。 模型选错了可以换,上下文构造错了,Agent 会"越跑越傻"------这正是本系列一直强调"Context 是编译出来的"的完整展开。
〇、先建立全景:一次推理的上下文从哪里来
某一轮调用 LLM 时,进入窗口的内容可以画成一条流水线:
text
用户输入
↓
System Prompt
↓
项目级 instructions(AGENTS.md / CLAUDE.md)
↓
Skill instructions(按需读取的 SKILL.md)
↓
历史 conversation(会话树 → 当前叶子路径)
↓
tool result(工具执行结果)
↓
文件内容(read 工具读进来的代码/文档)
↓
错误信息(bash 报错、测试失败输出)
↓
压缩 / 截断 / 摘要(compaction 检查点)
↓
最终 Context
↓
LLM
Pi 对这条链路的工程化,散在本系列各篇里;这篇把它们收拢成一个完整学科。记住:模型看到什么,决定模型做什么。上下文构造是 Agent 的隐性控制器。
一、Context Assembly:谁有资格进窗口
Pi 的上下文由这些来源组装(每个来源都有独立的生命周期和信任等级):
| 来源 | Pi 的机制 | 生命周期 |
|---|---|---|
| System Prompt | 构建链:AGENTS.md → skills 目录 → 模板 → before_run 覆盖 | 每 run 构建,可覆盖 |
| 项目 instructions | AGENTS.md/CLAUDE.md/.pi/SYSTEM.md(未信任也加载) |
每会话加载 |
| Skill instructions | <available_skills> 目录 + 模型按需 read 全文 |
渐进式披露 |
| 历史对话 | 会话树 → 当前叶子到根的路径 | append-only |
| 工具结果 | ToolResultMessage 随消息落账 | 每轮追加 |
| 运行时元数据 | env 注入的 PI_SESSION_ID 等 |
每命令解析 |
关键原则(本系列反复出现):
- 系统提示词保持极简:skills 只列描述不内联,因为"给模型多少判断原料"是预算。
- 项目宪法优先 :
AGENTS.md在项目未信任时也加载------"这个仓库的规矩"优先级高于一切。 transform_context是最后一道变换 :只改 provider 看到的、不改会话存的------"呈现层"和"存储层"分离,防止为临时目的污染永久记录。
二、Context Budget:窗口满了谁被扔
假设模型窗口 200k,Agent 跑久以后:
text
历史消息 70k
代码 80k
tool output 50k
system 5k
----------------
205k ← 超了
"谁被扔"是决定 Agent 是否"越跑越傻"的关键决策。 候选策略(按信息价值从低到高):
| 策略 | 丢弃对象 | 代价 |
|---|---|---|
| 时间优先 | 最早的消息 | 可能丢"关键但早"的事实 |
| 重要度优先 | 低优先级消息(闲聊) | 需要消息分级(总纲上下文篇) |
| 工具结果优先 | 旧工具输出 | 模型可能"忘了"自己做过什么 |
| system 永不删 | 系统提示词恒定 | 占固定预算 |
| 文件重读而非常驻 | 不保存文件内容,用时再 read | 每次重新检索 |
Pi 的立场是"追加不变式 + 一次性失效":
Across the requests of a lane, provider context only grows at the tail. An insertion before the previous request's tail invalidates the provider's KV cache from that point on and multiplies token cost.
上下文只允许尾增长------因为中间插入会无效化 KV cache 并放大成本。所以:回合中途的写入推迟到 checkpoint 追加,compaction 是唯一一次刻意的缓存失效。
预算的硬护栏 :reserveTokens(默认 16k 留给响应)+ 溢出判定(显式超限错误 / 输入超窗口 / 可恢复 length)。预算要在发请求前就算好,超了就分类处理,事后才裁已经来不及。
三、Compaction:长周期运行的命脉
Compaction 是 Agent 长周期运行的核心技术。完整生命周期:
text
Raw history
↓ 触发(阈值 / /compact 手动 / overflow 溢出)
Summary generation(LLM 摘要)
↓
Important state extraction(保留关键状态)
↓
Old messages replacement(CompactionEntry 落账)
↓
Continue execution(从 retainedTail 重建上下文)
Pi 的 CompactionEntry带三样东西:summary(摘要)、firstKeptEntryId(保留起点)、retainedTail(自包含检查点)。retainedTail 让重建可以从检查点直接往后走,不翻旧条目。
Compaction 的四个"能不能压"的判断,是工程精华:
- 摘要丢信息怎么办? 分级处理------普通对话可压,代码/SQL/合同这类"语义敏感"内容压了等于改语义。
- 工具执行事实能不能被 summary 覆盖? 危险。工具做了什么(改了哪个文件、结果是什么)是审计和继续推理的锚点,Pi 的 overflow compaction 明确把链接的那个响应从摘要准备中排除("exact overflow-response omission")------关键事实留原文。
- 文件修改记录该不该压缩? 不该。修改记录是"Agent 对世界做过的改变",压掉就无法审计/回滚。
- 用户约束是否允许压缩? 用户明确说的要求("不要改这个文件")是 CRITICAL 级,永不压(总纲上下文篇)。
compaction 的正确姿势:压缩的是"过程性的旧对话",保留的是"结论性的状态和约束"。
四、Working Memory vs Long-term Memory:五种不同的存储
很多 Agent 项目把"记忆"当成一个筐,但 Pi(和 CC 的对照)揭示了至少五种不同的存储,各有生命周期:
| 存储 | 生命周期 | 例子 | 谁来管 |
|---|---|---|---|
| Conversation History | 一次会话 | 会话树 | 引擎 |
| Working Memory | 一次 run | 变量池/车道记录 | 运行时 |
| Long-term Memory | 跨会话 | CC 的 memory 文件、项目事实 | 应用 |
| Project State | 随项目 | .pi/settings.json、AGENTS.md |
应用 |
| Artifact State | 随产物 | git 历史、生成文件 | 外部 |
关键区分(总纲上下文篇):知识进上下文靠检索(RAG),经验进行为靠记忆(memory 文件),事实靠状态存储(项目/产物)。 三件事别混进一个筐:
- Pi 的会话账本是 Conversation History + Working Memory 的合体(append-only + lane records);
- CC 的 memory 文件是 Long-term Memory(跨会话的身份/偏好/被纠正过的工作方式);
- 项目文件本身就是 Project/Artifact State(Agent 不需要"记住",需要时 read)。
设计你自己的 Agent 时,先回答:这五种存储各归谁、各活多久、谁能写。 最常见的错误是把 Long-term Memory 塞进 Conversation History------一旦会话压缩,记忆跟着丢。
五、对照你的工程:上下文系统的设计清单
| 能力 | 做法 | 优先级 |
|---|---|---|
| ContextBuilder 单一管道 | 所有模块不许自己拼 prompt(总纲) | P0 |
| 组装顺序即策略 | 身份→检索→去重→排序→过滤→预算(总纲) | P0 |
| 预算前置 | 发请求前算 token,超了分类处理 | P0 |
| 追加不变式 | 上下文只尾增长,不中间插入 | P0 |
| compaction 分级 | 普通对话可压,约束/事实/敏感内容不压 | P0 |
| 五种存储分离 | 对话/工作/长期/项目/产物各归其位 | P1 |
| transform_context 呈现层 | 改 provider 看的,不改会话存的 | P1 |
| 文件重读而非常驻 | 大文件用时 read,不长期占窗口 | P1 |
一句话收束:上下文工程的本质是"在有限的窗口里,决定模型每一轮该看到什么、不该看到什么"。 它比模型选择更决定 Agent 质量------因为再强的模型,喂错上下文也会胡说。
知识卡片(本节体系归档)
text
┌──────────────────────────────────────────────────────────┐
│ 知识节点:上下文工程(Agent 的真正 CPU) │
│ │
│ What Context Assembly 流水线 + Budget(谁被扔)+ │
│ Compaction(压过程留结论)+ 五层记忆分离 │
│ │
│ Why 一般原理:模型看到什么,决定模型做什么。不变量: │
│ ① 上下文只尾增长(KV cache 不失效) │
│ ② 预算前置:发请求前算好,超了就分类处理 │
│ ③ compaction 压的是过程性的旧对话,留的是结论和约束 │
│ │
│ How 校验动作: │
│ 画上下文流水线 + PaiFlow 预算前置 │
│ │
│ Pits 坑点: │
│ 越跑越傻 / 上下文灌爆 / 记忆混筐 │
│ │
│ Transfer 到 PaiFlow:ContextBuilder 预算前置 + 分级压缩 │
└──────────────────────────────────────────────────────────┘