pi-mono 记忆管理机制 - 上下文压缩与跨模型传递
详解 pi-mono 的上下文压缩、跨模型上下文传递、会话恢复与分支、Token 追踪机制
2.1 上下文压缩算法
压缩触发条件
当会话 tokens 接近模型上下文窗口限制时,pi-mono 自动触发压缩:
const CONTEXT_THRESHOLD = 0.8 // 80% 窗口利用率
async function checkAndCompact(context: Context, model: Model) {
const tokens = countTokens(context.messages)
const limit = model.contextWindow
if (tokens > limit * CONTEXT_THRESHOLD) {
await compact(context)
}
}
默认阈值:
- 触发点:80% 上下文窗口
- 目标:压缩后降至 50% 以下
压缩流程
[原始会话] [压缩后]
┌─────────────────────────────────────────┐ ┌─────────────────────────────┐
│ UserMessage: "帮我实现 JWT 认证" │ │ CompactionEntry: │
│ AssistantMessage: "好的,我先了解需求" │ ──────┐ │ "讨论了 JWT 认证需求..." │
│ UserMessage: "需要支持刷新令牌" │ │ │ │
│ ... (早期对话 45K tokens) ... │ │ │ UserMessage: "下一步做什么?" │
│ UserMessage: "下一步做什么?" │ └────>│ AssistantMessage: "..." │
│ AssistantMessage: "..." │ └─────────────────────────────┘
└─────────────────────────────────────────┘
步骤:
- 调用 LLM 生成早期消息摘要
- 创建
CompactionEntry存储摘要 - 记录
firstKeptEntryId(保留的第一条消息) - 后续
buildSessionContext()优先输出摘要
摘要生成提示词
const COMPACTION_PROMPT = `
Summarize the conversation history, capturing:
- Key decisions made
- Code changes implemented
- Open questions
- User preferences
Keep it concise but preserve essential context for continuation.
`
压缩策略对比
| 策略 | pi-mono | Claude Code | Cursor |
|---|---|---|---|
| 方法 | LLM 摘要 | 滑动窗口 | 向量摘要 |
| 可解释性 | ✅ 高 | ⚠️ 中 | ⚠️ 中 |
| 信息保留 | ⚠️ 依赖摘要质量 | ❌ 丢失早期内容 | ✅ 向量检索 |
| 计算成本 | ⚠️ 一次 LLM 调用 | ✅ 无 | ⚠️ 向量嵌入 |
2.2 跨模型上下文传递
设计动机
pi-mono 支持在会话中切换模型(如从 Claude 切换到 GPT),这需要处理:
- Thinking traces 格式差异:各提供商返回不同的推理内容格式
- 工具调用签名:不同 API 的工具调用结构不同
- 元数据兼容性:token 计数、缓存读取等字段不一致
上下文序列化
import { Context } from '@mariozechner/pi-ai'
const context: Context = {
messages: [
{
role: "assistant",
content: [
{ type: "thinking", thinking: "..." },
{ type: "text", text: "..." }
]
}
]
}
// 序列化为 JSON(可存储或传输)
const serialized = JSON.stringify(context)
序列化内容:
- 所有消息及内容块
- 工具调用及结果
- Thinking traces(如有)
- 使用量统计
跨提供商转换
Anthropic → OpenAI
// Anthropic thinking traces
{
role: "assistant",
content: [
{ type: "thinking", thinking: "Let me analyze..." },
{ type: "text", text: "The answer is..." }
]
}
// 转换为 OpenAI 格式
{
role: "assistant",
content: [
{ type: "text", text: "<thinking>Let me analyze...</thinking>\nThe answer is..." }
]
}
转换规则:
- Thinking traces 包裹在
<thinking>标签中 - 工具调用转换为函数调用格式
- 使用量字段重新映射
OpenAI → Google
// OpenAI tool calls
{
role: "assistant",
tool_calls: [
{ id: "call_123", function: { name: "bash", arguments: "..." } }
]
}
// 转换为 Google 格式
{
role: "model",
functionCalls: [
{ name: "bash", args: { command: "..." } }
]
}
上下文反序列化
// 从 JSON 恢复上下文
const restored: Context = JSON.parse(serialized)
// 可在任意模型上继续
const model = getModel('google', 'gemini-2.5-flash')
const response = await complete(model, restored)
限制:
- Thinking traces 可能丢失语义(变为纯文本)
- 工具调用参数需重新验证
- 缓存读取统计不跨提供商
2.3 会话恢复与分支
会话恢复模式
Continue(继续最近)
pi --continue
行为:
- 扫描
~/.pi/agent/sessions/<path>/目录 - 按时间戳排序找到最近会话
- 加载到最后一条 leaf 节点
- 继续对话
Resume(恢复指定)
pi --resume 20260307_140000_abc12345
行为:
- 解析会话文件路径
- 完整加载 JSONL 文件
- 重建消息树
- 定位到 leaf 节点
Fork(分叉新会话)
pi --fork <session-id> --from <entry-id>
行为:
- 创建新会话文件
- 复制源会话到指定条目
- 添加
parentSession元数据 - 新消息形成独立分支
分支导航
# 查看会话树
pi --tree
# 切换到历史节点
pi --goto <entry-id>
# 从当前节点创建分支
pi --branch
树状显示:
* a1b2c3d4 - User: "帮我实现 JWT 认证"
b2c3d4e5 - Assistant: "好的,我先了解需求"
c3d4e5f6 - User: "需要支持刷新令牌"
* d4e5f6g7 - User: "使用 Redis 存储" ← 当前分支
e5f6g7h8 - User: "使用数据库存储"
f6g7h8i9 - Assistant: "数据库方案如下..."
分支摘要
切换分支时自动创建摘要:
{
"type": "branch_summary",
"id": "g7h8i9j0",
"parentId": "a1b2c3d4",
"fromId": "d4e5f6g7",
"summary": "在该分支上,用户选择了 Redis 存储方案,讨论了过期时间设置..."
}
作用:
- 保留离开分支的上下文
- 新分支可理解之前的探索
- 避免重复讨论
2.4 Token 追踪机制
使用量统计
interface Usage {
input: number // 输入 tokens
output: number // 输出 tokens
cacheRead: number // 缓存读取 tokens
cacheWrite: number // 缓存写入 tokens
totalTokens: number
cost: {
input: number // 输入成本(美元)
output: number // 输出成本
cacheRead: number // 缓存读取成本
cacheWrite: number// 缓存写入成本
total: number // 总成本
}
}
成本计算
const PRICING = {
'claude-sonnet-4-5': {
input: 3e-6, // $3 / 1M tokens
output: 15e-6, // $15 / 1M tokens
cacheRead: 0.3e-6,
cacheWrite: 3.75e-6
}
}
function calculateCost(usage: Usage, model: string): Cost {
const rates = PRICING[model]
return {
input: usage.input * rates.input,
output: usage.output * rates.output,
// ...
}
}
会话级追踪
每条 AssistantMessage 包含使用量:
{
"type": "message",
"message": {
"role": "assistant",
"usage": {
"input": 15000,
"output": 2000,
"totalTokens": 17000,
"cost": { "total": 0.075 }
}
}
}
累计统计:
# 查看会话总成本
pi --stats
# 输出示例
Session: 20260307_140000_abc12345
Total tokens: 125,000
Total cost: $0.52
Messages: 45
跨会话统计
# 所有会话统计
pi --stats --all
# 按项目分组
pi --stats --group-by project
本章小结
pi-mono 的记忆管理机制特点:
- 自动压缩:LLM 生成摘要,保留关键上下文
- 跨模型兼容:序列化/反序列化支持任意模型切换
- 树状分支:原生支持多路径探索
- 成本透明:精确追踪 tokens 和成本
下一章将介绍如何通过扩展 API 增强记忆功能。
参考资料
- pi-mono Session Documentation. https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/session.md
- pi-mono SDK Documentation. https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/sdk.md
- Context Handoff Example.
packages/ai/src/context.ts