Agent 每次请求模型时,并不是只发送用户刚输入的一句话。通常还会包含:
| 内容 | 来源 |
|---|---|
| 系统提示词 | Agent 内置规则、工具说明、行为约束 |
| 项目上下文 | AGENTS.md、项目规则、当前工作目录 |
| 技能索引 | 可用技能的名称和描述 |
| 历史消息 | 当前会话 leaf 上的消息路径 |
| 工具定义 | 当前启用工具的 schema |
这就是为什么上下文管理会变成 Agent 框架的核心问题。
Pi 的 DefaultResourceLoader 负责加载这些资源:
flowchart TD A["cwd / agentDir / settings"] --> R["DefaultResourceLoader"] R --> E["extensions"] R --> S["skills"] R --> P["prompt templates"] R --> T["themes"] R --> C["AGENTS.md / CLAUDE.md"] E --> SP["buildSystemPrompt"] S --> SP C --> SP
重点不是“加载文件”本身,而是把外部资源转成稳定的运行时输入:系统提示词、工具、命令、事件处理器。
Pi 的 Skills 机制只在系统提示词中放技能名称和描述。真正需要时,模型再用 read 读取完整 SKILL.md。
这样做的原因很实际:如果每个技能的全文都塞进系统提示词,长一点的技能库会直接吃掉上下文窗口。
flowchart LR
A["启动时扫描技能"] --> B["系统提示词只列 name + description"]
B --> C{"任务匹配技能?"}
C -->|"是"| D["模型读取 SKILL.md"]
C -->|"否"| E["不消耗技能全文 token"]长会话一定会遇到上下文窗口限制。Pi 的 compaction 不是简单删除老消息,而是:
CompactionEntry。flowchart TB
A["完整历史消息"] --> B["估算 token"]
B --> C{"超过阈值?"}
C -->|"否"| D["继续正常请求"]
C -->|"是"| E["选择 cut point"]
E --> F["总结旧消息"]
F --> G["append CompactionEntry"]
G --> H["上下文 = summary + recent messages"]摘要适合保存“做过什么、得出什么结论”,但它不适合替代最新几轮的细节。最近消息里通常有:
| 内容 | 为什么要保留原文 |
|---|---|
| 最新工具输出 | 模型可能还要基于精确文本继续处理 |
| 刚修改的文件片段 | 摘要容易丢细节 |
| 当前用户约束 | 最新指令应高优先级保留 |
| 错误日志 | 错误堆栈摘要后可能失真 |
所以真实系统通常是“摘要旧内容 + 原样保留最近内容”的组合。
我们的 Demo 4 会用一个非常朴素的 token 估算:按字符长度近似。压缩器也不是调用真实模型,而是把旧消息压成一段摘要文本。
它不智能,但能展示关键结构:
{
type: "compaction",
summary: "用户让 Agent 检查 README,工具读取了 README...",
firstKeptEntryId: "entry_8",
tokensBefore: 12400
}
理解结构比一开始追求摘要质量更重要。真正接模型时,你只需要替换 summarizer。
| 风险 | 表现 | 策略 |
|---|---|---|
| 摘要过粗 | 模型忘记关键文件、约束、错误 | 摘要中明确“已完成、关键事实、剩余任务、风险” |
| 保留太多 | 压缩后仍然接近窗口上限 | 只保留最近高价值消息,旧消息靠摘要承接 |
真实 Pi 还要处理“模型返回 context overflow 错误后自动压缩并重试”的场景。这里的重点是:压缩不是用户主动整理笔记,而是 Agent runtime 的生存机制。
| 机制 | 适合放什么 | 不适合放什么 |
|---|---|---|
| Context Files | 项目长期规则,例如编码规范、运行命令 | 一次性任务说明 |
| Skills | 可复用工作流,例如“如何做性能审计” | 当前项目私有状态 |
| Prompt Templates | 常用 prompt 模板和参数 | 大段知识库全文 |
| Compaction Summary | 历史会话事实和当前进度 | 新规则或新权限 |
把这些边界分清楚,读者后面自己扩展教学版 Agent 时就不会把所有东西都塞进 system prompt。
| 误区 | 后果 | 修正 |
|---|---|---|
| 把所有技能全文放进系统提示词 | token 很快耗尽 | 只放索引,按需读取全文 |
| 压缩摘要写得像聊天总结 | 丢失文件路径、命令、错误 | 用工程化摘要结构 |
| 工具输出不截断就进上下文 | 一次日志可能撑爆窗口 | 工具层截断,必要时摘要 |
| 忽略最新用户指令优先级 | 摘要中的旧目标覆盖新目标 | 最近消息原文保留,并在 prompt 中强调优先级 |
把 examples/demos/04-compaction.ts 中的 keepRecentCount 从 2 改成 4,观察摘要内容和最终上下文如何变化。