智能体
mp.weixin.qq.com ·
@大模型智能
万字长文解析 Pi Agent 的 Context 管理:Prompt 与 Context 如何区分
文章从“Skill 是 Prompt 还是 Context”的讨论出发,提出 Prompt 偏静态指令、Context 偏 Agent 运行时动态信息环境,并以 Pi Agent 为例拆解 System Prompt、Tool Definitions、User/Assistant Messages、Tool Results 五部分如何构成模型实际看到的上下文。文中给出 KV cache 友好的三条原则:保持前缀稳定、动态信息向后追加、使用标准消息结构,并介绍 AgentSession 与 AgentSessionRuntime 的分层设计。
AI推荐理由 文章以 Pi Agent 为例系统拆解 Agent 的 Context 构成与管理,区分 Prompt 与 Context,并给出 KV cache 友好的三条原则(前缀稳定、动态信息后追加、标准消息结构),对 Agent 开发者有实际参考价值。但正文为节选,未覆盖完整 14000 字内容,且属个人技术解读而非官方文档,需注意其观点性质。
70
正文
大模型智能 2026-09-28 00:00 吉林

Prompt和Context到底应该怎么区分?我的理解是,Prompt更偏向相对静态的指令设计,而Context更偏向Agent在运行过程中动态形成的信息环境。 Prompt往往提前定义了模型“应该怎么做”,而Context会随着任务推进不断变化,决定模型“此刻基于哪些信息来做”

大模型智能|分享
来源 | 青稞AI
作者 | cecilia

***01***
**前言**
写这篇文章,源于我最近在小红书上看到的一个讨论:
> Skill 到底是 Prompt,还是 Context?
看下来我发现,这个问题背后其实还有一个更基础的问题:**Prompt 和 Context 到底应该怎么区分?**
我的理解是,**Prompt 更偏向相对静态的指令设计,而 Context 更偏向 Agent 在运行过程中动态形成的信息环境。** Prompt 往往提前定义了模型“应该怎么做”,而 Context 会随着任务推进不断变化,决定模型“此刻基于哪些信息来做”。
所以这篇文章并不想直接讨论 Skill 应该被归类为 Prompt 还是 Context,而是想先回到更基础的问题:
> 到底什么是context, agent 中是怎么做context管理
本文会以 **Pi Agent** 为例,看看 Prompt、Skill、Tool Result、Message 等信息是如何被组织,并在 Agent 执行过程中逐步形成模型实际看到的 Context。
全文一共14000 + 字,建议先码后看
***02***
**Context 构建的基本原则**
上下文(Context)可以理解为 Agent 在每个决策点能够看到的全部信息。它就像 Agent 当前的“视野”,决定模型基于哪些信息进行下一步推理。
从一次 LLM 调用来看,Context 通常由五部分组成:
* • **System Prompt**:定义 Agent 的身份、规则和工作方式,也可能包含 Memory、环境信息等。
* • **Tool Definitions**:描述 Agent 当前可以使用哪些工具,以及工具的参数格式。
* • **User Messages**:用户输入,也可能包含通过 RAG 动态检索得到的外部知识。
* • **Assistant Messages**:模型此前产生的文本、Reasoning 和 Tool Calls。
* • **Tool Results**:工具执行后返回的结果,为 Agent 下一步决策提供新的信息。
其中,**System Prompt 和 Tool Definitions 通常构成相对静态的前缀;User Message、Assistant Message 和 Tool Result 则随着 Agent 执行不断产生和变化。**
这些信息并不是由 Model 自己管理的。更准确地说:
> Model 负责基于当前 Context 决定下一步;Harness 负责组装和管理 Context、校验并执行工具;Environment 则负责承载真实状态,并产生新的执行结果和观察。
三者不断循环,Context 也会随着 Agent 的执行持续变化。
为了使得上下文是kv cache 友好型,尽量遵守以下三点:
* • **保持前缀稳定**:System Prompt、Tool Definitions 等静态内容尽量固定,避免频繁修改导致缓存失效。
* • **动态信息向后追加**:时间、状态、工具结果等变化内容放到 Context 尾部,不要回头修改已有前缀。
* • **使用标准消息结构**:优先使用 API 原生的 `system / user / assistant / tool` 等结构化格式,避免自行拼接 Prompt。
***03***
**pi agent 基础概念**
由于 Pi Agent 内部定义了一些自己的基础类型和数据结构,后文分析 Context 管理时会频繁涉及。为了避免反复解释,本文先单独用一节介绍这些基础概念,方便理解后续章节解读。
### 3.1 会话运行层:Runtime 与 Session
Pi Agent 中,`AgentSession` 和 `AgentSessionRuntime` 分别对应两个不同层次:
* • **AgentSession:负责一个具体会话内部如何运行**
* • **AgentSessionRuntime:负责当前使用哪个 Session,以及 Session 的生命周期和运行环境**
`AgentSession` 可以理解为**单个会话的控制器**。它持有核心 `Agent`,并负责处理一次会话中的主要运行逻辑,包括 `prompt()`、`abort()`、`continue()`、任务队列、重试、Compaction,以及消息、System Prompt、Tool 和扩展事件的管理。
简单来说:
> AgentSession 管的是“这个会话怎么跑”。
而 `AgentSessionRuntime` 更像是 `AgentSession` 外层的**生命周期容器**。它持有当前的 `AgentSession`,同时维护 Session 运行所依赖的 `cwd`、`settingsManager`、`modelRuntime`、`resourceLoader` 等 Services。
因此,如果用户只是发送一个 Prompt,主要进入的是 `AgentSession` 内部的执行流程;而当用户切换 Session 时,则由 `AgentSessionRuntime` 完成 Session 的替换和重新绑定。
数据结构大致如下
```
AgentSession = {
agent,
// 底层 agent 执行器。真正负责 prompt/continue,维护 agent.state.messages,并驱动 agent loop。
sessionManager,
// 会话持久化和会话树管理。负责读写 jsonl、追加消息、获取 branch、sessionFile、sessionId 等。
settingsManager,
// 设置管理器。负责读取/修改全局和项目设置,比如默认模型、thinking level、compaction、retry、工具模式等。
modelRuntime,
// 模型运行时。负责模型注册、认证检查、provider 请求、stream 调用、模型列表刷新等。
resourceLoader,
// 资源加载器。负责加载扩展、skills、prompt templates、context files、themes、system prompt 等 cwd 相关资源。
message queues,
// 运行中收到的新消息队列。主要包括 steering、followUp、nextTurn。
// steering 偏“尽快插入当前运行”,followUp 偏“当前回答结束后继续”,nextTurn 会随下一次用户 prompt 一起进上下文。
compaction state,
// 上下文压缩状态。记录当前是否正在 compaction、自动压缩 controller、overflow 恢复是否尝试过等。
retry state,
// 自动重试状态。记录 retry controller、当前重试次数,用于 provider 错误或可恢复错误后的续跑。
bash state,
// bash 命令执行状态。记录正在运行的 bash controller,以及待写入会话/上下文的 bash execution message。
extension state,
// 扩展运行状态。包括 ExtensionRunner、扩展 UI context、扩展命令上下文、shutdown/abort/error handler 等。
tool registry,
// 工具注册表。保存当前可用工具、工具定义、工具 prompt snippet、工具使用 guideline。
// setTools/getTools、扩展工具、内置工具都通过这里统一管理。
system prompt state,
// system prompt 状态。包括基础 system prompt、构建参数,以及本轮临时 override。
// 扩展可以在 before_agent_start 阶段临时改 system prompt,跑完后清掉 override。
}
```
```
AgentSessionRuntime = {
session,
// 当前正在运行的 AgentSession。用户发 prompt、abort、切模型、压缩等,最终都落到这个对象上。
services,
// 当前会话绑定的一组基础服务。包括 cwd、agentDir、modelRuntime、settingsManager、resourceLoader、diagnostics。
// 切换 session/cwd 时,这组服务会跟着重建。
createRuntime,
// 创建新运行时的工厂函数。new session、resume session、fork/import 时,用它重新创建 services + AgentSession。
diagnostics,
// runtime 初始化或加载资源时产生的诊断信息,比如扩展加载错误、配置问题、模型 fallback 信息等。
rebind hooks,
// 会话替换后的回调。旧 session 被销毁、新 session 创建后,用它通知 UI/host 重新绑定当前 session。
}
```
### 3.2 Services: Runtime 与 AgentSession 的连接面
在 Pi Agent 中,切换 Session 并不是简单地替换一个 `AgentSession` 对象。
原因是,一个 Session 往往与当前 `cwd` 强绑定,而 `cwd` 又会进一步决定项目配置、`AGENTS.md`、Extensions、Skills、Prompt Templates、System Prompt,以及模型 Provider 等运行资源。
如果切换了 Session,却仍然沿用旧 Session 的这些依赖,就可能出现类似这样的错配:
> 当前运行的是 repo-b 的 Session,但实际加载的仍然是 repo-a 的配置、Skill 和扩展。
因此,Pi Agent 引入了 `AgentSessionServices`,将 `cwd`、`settingsManager`、`resourceLoader`、`modelRuntime` 等与当前运行环境相关的依赖统一封装起来。
当 `AgentSessionRuntime` 执行新建、切换、Fork 或恢复 Session 等操作时,会先根据目标 Session 的 `cwd` 重建对应的 Services,再基于这套运行环境创建新的 `AgentSession`。
所以,`AgentSessionServices` 解决的核心问题可以概括为:
> 切换 Session 时,不只切换会话状态,还要同步切换它所依赖的完整运行环境。
这样可以保证 `AgentSession` 与项目配置、资源和运行时始终保持一致,避免不同 Session 之间出现环境污染或资源错配。
***04***
**pi的context 管理**
### 4.1 context 的标准化表示
pi 中context 数据结构如下
```
Context = {
systemPrompt // 当前 agent 的完整系统提示词。它由 AgentSession 构建,来源包括基础 prompt、项目规则、skills 列表、AGENTS/context files、工具说明等。它不会进入 messages 数组,而是作为独立
字段传给底层模型适配层。
messages // 是一个列表,当前会话里模型可见的消息历史
tools // 是一个列表,当前这轮模型可调用的工具列表
}
```
其中大模型 API 的核心是messages,主要包含三种类型:
* • **UserMessage**:用户消息。表示用户输入的内容,可以是文本,也可以包含图片。
* • **AssistantMessage**:模型消息。表示模型返回的结果,既可以包含文本,也可以包含 Tool Call、停止原因和 Token 使用量等信息
* • **ToolResultMessage**:工具结果。Harness 执行 Tool Call 后,会将执行结果包装成 ToolResultMessage,并通过 toolCallId 与对应的 Tool Call 关联。随后,这条消息会被追加回 messages,作为模型下一轮推理的输入。
因此,Pi Agent 中一次典型的 Agent Loop 可以简化为:
UserMessage → AssistantMessage(Tool Call)→ ToolResultMessage → AssistantMessage ...
随着 Agent 不断执行,新的模型回复和工具结果会持续追加到 messages 中,形成不断变化的动态 Context。
### 4.2 AssistantMessage
`AssistantMessage` 是 Pi Agent 内部表示**模型单轮回复**的数据结构。它不仅记录模型生成了什么内容,还会保存本轮是否触发工具调用、停止原因、模型信息和 Token 使用量等元数据。
其中最关键的是 `content`。它并不只有文本,而是可以同时包含多种内容,例如:
```
content: [
{ type: "text", text: "我先读取一下文件。" },
{
type: "toolCall",
id: "call_123",
name: "read",
arguments: { path: "src/index.ts" }
}
]
```
因此,一条 `AssistantMessage` 既可以表示普通文本回复,也可以表示包含 Tool Call 的模型回复。**Tool Call 本身不是一条独立消息,而是 `AssistantMessage.content` 的一部分。**
当其中包含 Tool Call 时,后续流程是:
```
AssistantMessage
↓
发现 toolCall
↓
Pi Agent 执行工具
↓
生成 ToolResultMessage
↓
通过 toolCallId 与原 Tool Call 关联
↓
追加回 messages
↓
继续调用模型
```
也就是说,`AssistantMessage` 是模型决策的载体,而 `ToolResultMessage` 则负责把这次决策产生的真实执行结果重新带回 Context。
### 4.3 ToolResultMessage
ToolResultMessage 不是 Tool 的原始返回值,而是 Agent Loop 对工具执行结果处理后,重新写回 Context 的标准消息。
整体流程是:
1. 1. 模型在 `AssistantMessage` 中产生一个或多个 Tool Call。
2. 2. Agent Loop 根据配置决定这些 Tool Call **串行或并行执行**。
3. 3. 执行前完成工具查找、参数校验,并经过 `beforeToolCall` Hook。
4. 4. 调用 Tool 的 `execute()`;异常会被转换成错误结果,而不是直接中断 Agent Loop。
5. 5. 执行结果经过 `afterToolCall` Hook 后,被封装成 `ToolResultMessage`:
```
{
role: "toolResult",
toolCallId: "...",
toolName: "read",
content: [...],
isError: false
}
```
其中 `toolCallId` 用于关联对应的 Tool Call。
最终,`ToolResultMessage` 会被追加回 `messages`,进入下一轮模型推理。
```
AssistantMessage
→ Tool Call
→ 串行 / 并行执行
→ beforeToolCall
→ Tool.execute()
→ afterToolCall
→ ToolResultMessage
→ 写回 Context
```
### 4.4 skill 在上下文的呈现
在 Pi Agent 中,Skill 默认不会把完整的 `SKILL.md` 直接放进 Context,而是先以 **Skill 索引** 的形式出现在 System Prompt 中。
每个 Skill 只暴露三类信息:
```
name Skill 名称
description 适用场景
location SKILL.md 的文件路径
```
例如:
```
qa-expert
Golang 分布式存储测试专家...
/path/to/qa-expert/SKILL.md
```
这里并没有 Skill 的完整正文。System Prompt 只是告诉模型:**当前有哪些 Skill、分别适合什么任务,以及需要时去哪里读取。**
因此,Skill 通常分两个阶段进入 Context:
```
阶段 1:Skill 索引
System Prompt
→ name / description / location
阶段 2:Skill 正文
任务匹配
→ Model 调用 read
→ 读取 SKILL.md
→ 内容进入后续 Context
```
***05***
**长程任务的context 管理**
### 5.1 loop 机制中的context 管理
沿着执行流程看,Context 在 `runLoop` 运行期间会持续增长:
```
_runAgentPrompt(用户输入)
│
├─ agent.prompt()
│ └─ runLoop
│ ├─ 模型回复 → 工具执行 → 结果写回 Context
│ ├─ 模型回复 → 工具执行 → 结果继续写回 Context
│ └─ 模型 stop → agent_end
│
├─ _handlePostAgentRun()
│ └─ 可能执行 retry / compaction,进一步调整 Context
│
└─ finally
└─ 清理本轮临时状态,不清空消息历史
```
在 `runLoop` 内,每完成一轮模型调用和工具执行,产生的 `AssistantMessage`、`ToolResultMessage` 都会进入 `agent.state.messages`。
因此,整个 Run 期间,Context 实际上一直在演进:
```
Context₀
→ Assistant + ToolResult
→ Context₁
→ Assistant + ToolResult
→ Context₂
→ ...
→ agent_end
```
`agent_end` 可以看作这一次 Run 中 **Context 持续增长阶段的结束点**。
但这里的“结束”并不意味着 Context 被清空。
`agent.state.messages` 会保留下来。`finally` 中处理的主要是本轮运行相关的临时状态,例如恢复 system prompt、flush 消息以及发出 `agent_settled`,并不会删除已经沉淀下来的 transcript。
因此,当下一个用户 Prompt 到来时,Agent 会基于当前 state 创建新的 Context Snapshot:
```
上一个 Run 沉淀的 messages
+
新的 UserMessage
↓
新的 Context
↓
进入下一次 runLoop
```
也就是说,同一个 Session 内,新 Prompt 默认不是从零开始,而是站在之前已经积累的会话历史之上。
例如:
```
Run 1 结束:
messages =
[
user,
assistant(read),
toolResult,
assistant(edit),
toolResult,
assistant(stop)
]
↓ 用户过一段时间再次输入
Run 2 开始:
messages =
[
Run 1 的历史,
new user
]
```
等待多久本身不会改变这件事。
只要还是同一个 Session,`agent.state.messages` 就会继续作为后续 Run 的历史来源。区别只是:如果上一轮 Agent 还在运行,新消息可能以 steering / follow-up 的方式进入当前执行;如果上一轮已经 settled,则会开启一个新的 Run。
不过,历史也不会无限增长。
当 Context 超过阈值时,会触发 compaction,把较早的消息折叠成摘要。因此更准确地说,下一个 Prompt 拿到的是:
```
未发生 compaction:
完整历史 + 新 UserMessage
发生过 compaction:
历史摘要 + 压缩后新增消息 + 新 UserMessage
```
所以从 Context 管理的角度,可以把整个 Session 理解成:
```
Run 1
Context 持续增长
↓
沉淀到 state
Run 2
从已有 state 创建 Context
↓
继续增长
↓
再次沉淀
Run 3
...
```
核心是:
> `agent_end` 结束的是这一轮 Run 对 Context 的持续更新,而不是 Context 本身。已经产生的消息会沉淀到 Session State 中,成为下一次 Run 的起点;只有 compaction 才会真正改变历史的形态。
### 5.2 context 预算管理
工具调用是 Context 增长的重要来源,其中 bash 尤其明显。一次命令可能产生大量日志、测试结果甚至完整文件内容,如果全部写进 Context,很容易迅速撑满窗口,而且其中大部分信息模型其实并不需要。
所以这里的核心问题是:
> 如何把工具输出限制在可控的 Context 预算内,同时又不真正丢失信息。
Pi 的策略可以概括成三步:
1、 **预算内,全量保留**
输出较小时,直接进入 Context。
2、**超过预算,只保留预览**
超出部分不再进入 Context,只给模型保留有限的尾部内容。
3、 **完整输出落盘,按需读取**
完整结果写入临时文件,并把文件路径一起返回给模型。模型如果需要更多细节,可以再通过 `read` 获取。
整体上就是:
```
Tool Output
↓
是否超过预算?
├─ 否 → 全量进入 Context
└─ 是 → 截断预览进入 Context
+
完整内容落盘
↓
模型按需读取
```
在实现上,输出是边产生边处理的,而不是等命令结束后再一次性截断。
采集过程中会清理 ANSI、乱码和换行,同时只在内存中维护有限大小的尾部,因此即使命令输出非常大,内存和 Context 消耗也不会随输出无限增长。
预算同时受 **行数和字节数**限制,例如 2000 行和 50KB,任意一个先达到就触发截断。这样既能处理大量短行,也能防止单行超大的 JSON 等内容绕过限制。
最终模型拿到的不是全部输出,而是:
```
截断后的预览
+
完整输出文件的位置
```
本质上,这是把工具结果从 **“全部推入 Context”** 改成了 **“有限预览 + 按需读取”**,从而把工具输出对 Context 的影响控制在一个可预测的范围内。
### 5.3 failover 机制的context 管理
长程任务总是免不了需要做failover, 在 Pi Agent 中,可以区分两个层次:
* • **Agent Context**:Agent Loop 内部维护的工作上下文,包括消息历史、System Prompt、Tools 等。
* • **Model Input**:每次调用模型前,由 Agent Context 转换和组装得到的实际模型输入。
```
Agent Context
→ transform / convert
→ Model Input
→ Model
```
发生 Failover 时,也可以分成两个层级。
**Provider 层 Failover** 不修改 Agent Context,而是直接使用同一份 Model Input 重新发起请求。对 Agent 来说,这一轮模型调用还没有结束,因此不会产生新的上下文状态。
**Session 层 Failover** 则发生在一次 Agent Loop 已经失败之后。此时失败的 `AssistantMessage` 已经进入 Agent Context。Session 会先回滚这条失败消息,再从最近一个有效的 Agent Context 重新启动 Agent Loop,并重新构建 Model Input。
```
[user]
→ assistant(error)
→ Agent Loop 结束
→ 回滚 assistant(error)
→ [user]
→ 重新构建 Model Input
→ 再次调用 Model
```
因此,两种 Failover 最终发送给模型的 Model Input 可能完全相同,但系统经历的状态路径不同:
> Provider Failover 是同一次模型调用的底层重发;Session Failover 是在 Agent 状态已经失败后,先回滚 Context,再重新发起模型调用。
从 Context Management 的角度看,这说明 Context 不只是持续追加,也需要支持 **回滚、恢复和重新构建**。
***06***
**pi 的compact 机制**
### 6.1 compact 的时间点
Pi Agent 的 Context 压缩不发生在 `runLoop` 内,而是由 Session 层统一管理。
主要有三个时机:
1、 **一次 Agent Run 结束后**
Session 会检查当前 Context 是否已经接近上限。如果需要压缩,就先折叠历史,再基于压缩后的 Context 继续执行。
2、 **新的 Prompt 发送前**
如果上一轮留下的 Context 已经过大,会先压缩,再把新的 User Message 接到压缩后的历史上。
3、 **用户手动触发**
用户也可以主动执行 `/compact`,直接压缩当前 Context。
真正触发压缩,主要有两种情况:
```
Context 持续增长
↓
接近 context window 上限
↓
提前压缩
```
或者:
```
Context 已经溢出
↓
压缩历史
↓
重新尝试
```
所以从整体 Context 管理来看:
> RunLoop 负责不断产生和积累 Context,Session 层则负责在 Run 之间或新 Prompt 到来前检查 Context,并在必要时进行 Compaction。
### 6.2 compact 实现
整体可以理解成:
```
原始 Context
↓
根据 token 预算寻找切点
↓
旧历史 → Summary
近期消息 → 原样保留
↓
Summary + Recent Messages
↓
新的 Context
```
总结下来有9个细节点
**1. 先决定哪些历史需要压缩**
Compaction 首先要解决的是切点问题:**哪些消息进入摘要,哪些消息继续原样保留。**
Pi 会从最近的消息向前计算 token,用一个固定的 recent-message 预算来保留较新的 Context,其余更早的历史进入压缩区。
切点本质上是 token 预算驱动的,但有一个明确限制:
> 不能直接切在 Tool Result 上。
因为 Tool Result 本身依赖前面的 Tool Call,如果只留下结果而丢掉调用它的 Assistant Message,会产生明显断裂的 Context。
因此,压缩后的结构通常是:
```
[较早历史:压缩]
------------------- cut
[近期消息:原样保留]
```
**2. 如果切点打断了一个执行过程,就补一份 split-turn 摘要**
token 预算是硬约束,所以切点不一定正好落在完整交互边界上。
例如:
```
Assistant: 我要读取 config.go
Tool Call: read(config.go)
---------------- cut
Tool Result: ...
Assistant: 根据结果继续分析
```
如果前半段被压缩、后半段被保留,模型就可能看到一个缺少来路的执行结果。
Pi 的处理方式不是强行移动切点,而是在确认发生 split-turn 后,额外对被切掉的那部分生成一份摘要:
```
History Summary
+
Turn Context (split turn)
+
Recent Messages
```
这里的 split-turn summary 是一次独立的 LLM 调用,最后再和主摘要拼接。
它解决的是一个很具体的问题:
> 压缩可以切断原始消息,但不能切断后续推理所依赖的因果关系。
**3. 摘要不是每次从头生成,而是增量合并**
第一次 Compaction 时,模型直接总结需要压缩的历史。
但后续再次触发 Compaction 时,不会把最早的所有原始消息重新喂给模型。因为这些消息已经被上一轮摘要替代了。
此时输入变成两部分:
```
上一次已经生成的历史摘要
上一次压缩之后新增、这次需要继续折叠的消息
```
模型负责把两部分重新合并成一份新的完整摘要:
```
Previous Summary
+
新增历史
↓
LLM
↓
Updated Summary
```
所以多次 Compaction 本质上形成的是一个持续演进的摘要:
```
History₀
↓ compact
Summary₁
Summary₁ + New History
↓ compact
Summary₂
Summary₂ + New History
↓ compact
Summary₃
```
这也是 Pi 能够支持长时间 Agent 执行的关键:**旧历史逐渐从原始消息形态转变成摘要形态,而不是无限累积。**
**4. 摘要本身也有预算**
Compaction 并不是“生成摘要就结束”。
如果摘要本身无限增长,经过多轮压缩后最终还是会重新撑满 Context。因此摘要生成也有独立的 token 上限,通常会从预留 Context 预算中划出一部分作为 summary budget。
所以它控制的是两层预算:
```
原始历史太大
↓
压缩成 Summary
Summary 本身也受 token budget 限制
```
这保证了 Compaction 的结果本身仍然是可控的。
**5. 摘要是结构化任务记忆,而不是普通聊天总结**
Compaction 的目标也不是“帮用户总结一下刚才聊了什么”。
它更关注的是:**为了让 Agent 接着把任务做下去,哪些信息必须留下。**
因此摘要会重点保留:
* • 当前任务目标;
* • 用户约束和偏好;
* • 已完成、进行中、受阻的工作;
* • 关键决策;
* • 已发现的问题和错误;
* • 下一步应该继续做什么;
* • 关键文件、函数、路径等工程信息。
本质上,它生成的不是 conversation summary,而更接近一份 **task continuation state**。
**6. 对关键事实,不完全依赖 LLM 摘要**
Pi 还做了一层很重要的保护:一些能够程序化确定的事实,不交给摘要模型“记忆”。
例如文件操作。
系统会直接扫描历史 Tool Call,提取:
```
read → 读过哪些文件
write → 新写了哪些文件
edit → 修改过哪些文件
```
最终整理成类似:
```
...
...
```
并追加到摘要中。
这意味着:
> 语义信息交给 LLM 蒸馏,确定性事实则由程序直接保留。
这是一个很实用的 Context 管理思路,因为像“改过哪些文件”这样的信息一旦被摘要模型漏掉,后续 Agent 很容易重复工作或失去任务锚点。
**7. 摘要生成本身不走 Agent Loop**
Compaction 的 summary 生成并不是再启动一个完整 Agent。
它只是一次独立的 LLM 调用:
```
待压缩历史
+
摘要指令
↓
单次 LLM 调用
↓
Summary
```
不会执行工具,也不会进入 Tool Loop。
这样可以避免“为了压缩 Context,又产生更多 Context”的递归问题,也让压缩行为更可控。
**8. 摘要生成后,Context 会被真正重建**
Compaction 最终不是给现有 messages 做一个标记,而是重新构造 Agent 的消息状态。
压缩之后大致变成:
```
agent.state.messages =
[
CompactionSummary,
...RecentMessages
]
```
其中 `CompactionSummary` 是一种专门的消息类型,而不是普通字符串。
在真正构造给 LLM 的 Prompt 时,它会被转换成类似:
```
<s