What: switch to 你/你的 tone; standardize Skills/Gateway网关/local loopback/私信 wording Why: align zh-CN docs with issue 6995 feedback + idiomatic tech style Tests: pnpm docs:build
289 lines
10 KiB
Markdown
289 lines
10 KiB
Markdown
---
|
||
read_when:
|
||
- 你需要调试会话 ID、对话记录 JSONL 或 sessions.json 字段
|
||
- 你正在修改自动压缩行为或添加"压缩前"清理逻辑
|
||
- 你想要实现记忆刷写或静默系统轮次
|
||
summary: 深入解析:会话存储 + 对话记录、生命周期及(自动)压缩内部机制
|
||
title: 会话管理深入解析
|
||
x-i18n:
|
||
generated_at: "2026-02-01T21:37:55Z"
|
||
model: claude-opus-4-5
|
||
provider: pi
|
||
source_hash: bf3715770ba634363933f6038117b6a91af11c62f5191aaaf97e6bce099bc120
|
||
source_path: reference/session-management-compaction.md
|
||
workflow: 15
|
||
---
|
||
|
||
# 会话管理与压缩(深入解析)
|
||
|
||
本文档解释 OpenClaw 如何端到端管理会话:
|
||
|
||
- **会话路由**(入站消息如何映射到 `sessionKey`)
|
||
- **会话存储**(`sessions.json`)及其跟踪的内容
|
||
- **对话记录持久化**(`*.jsonl`)及其结构
|
||
- **对话记录清理**(运行前的提供商特定修复)
|
||
- **上下文限制**(上下文窗口 vs 跟踪的 token 数)
|
||
- **压缩**(手动 + 自动压缩)以及压缩前工作的挂钩位置
|
||
- **静默清理**(例如不应产生用户可见输出的记忆写入)
|
||
|
||
如果你想先了解更高层级的概览,请从以下内容开始:
|
||
|
||
- [/concepts/session](/concepts/session)
|
||
- [/concepts/compaction](/concepts/compaction)
|
||
- [/concepts/session-pruning](/concepts/session-pruning)
|
||
- [/reference/transcript-hygiene](/reference/transcript-hygiene)
|
||
|
||
---
|
||
|
||
## 数据源:Gateway网关
|
||
|
||
OpenClaw 围绕单个 **Gateway网关进程**设计,该进程拥有会话状态。
|
||
|
||
- UI(macOS 应用、Web 控制界面、TUI)应向 Gateway网关查询会话列表和 token 计数。
|
||
- 在远程模式下,会话文件位于远程主机上;"检查本地 Mac 文件"不会反映 Gateway网关正在使用的内容。
|
||
|
||
---
|
||
|
||
## 两个持久化层
|
||
|
||
OpenClaw 通过两个层持久化会话:
|
||
|
||
1. **会话存储(`sessions.json`)**
|
||
- 键值映射:`sessionKey -> SessionEntry`
|
||
- 体积小、可变、可安全编辑(或删除条目)
|
||
- 跟踪会话元数据(当前会话 ID、最后活动时间、开关、token 计数器等)
|
||
|
||
2. **对话记录(`<sessionId>.jsonl`)**
|
||
- 具有树结构的追加写入对话记录(条目包含 `id` + `parentId`)
|
||
- 存储实际对话 + 工具调用 + 压缩摘要
|
||
- 用于重建未来轮次的模型上下文
|
||
|
||
---
|
||
|
||
## 磁盘位置
|
||
|
||
每个智能体,在 Gateway网关主机上:
|
||
|
||
- 存储:`~/.openclaw/agents/<agentId>/sessions/sessions.json`
|
||
- 对话记录:`~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl`
|
||
- Telegram 话题会话:`.../<sessionId>-topic-<threadId>.jsonl`
|
||
|
||
OpenClaw 通过 `src/config/sessions.ts` 解析这些路径。
|
||
|
||
---
|
||
|
||
## 会话键(`sessionKey`)
|
||
|
||
`sessionKey` 标识*你所在的对话桶*(路由 + 隔离)。
|
||
|
||
常见模式:
|
||
|
||
- 主/直接聊天(每个智能体):`agent:<agentId>:<mainKey>`(默认 `main`)
|
||
- 群组:`agent:<agentId>:<channel>:group:<id>`
|
||
- 房间/频道(Discord/Slack):`agent:<agentId>:<channel>:channel:<id>` 或 `...:room:<id>`
|
||
- 定时任务:`cron:<job.id>`
|
||
- Webhook:`hook:<uuid>`(除非被覆盖)
|
||
|
||
规范规则详见 [/concepts/session](/concepts/session)。
|
||
|
||
---
|
||
|
||
## 会话 ID(`sessionId`)
|
||
|
||
每个 `sessionKey` 指向一个当前的 `sessionId`(继续对话的对话记录文件)。
|
||
|
||
经验法则:
|
||
|
||
- **重置**(`/new`、`/reset`)为该 `sessionKey` 创建新的 `sessionId`。
|
||
- **每日重置**(默认为 Gateway网关主机本地时间凌晨 4:00)在重置边界后的下一条消息时创建新的 `sessionId`。
|
||
- **空闲过期**(`session.reset.idleMinutes` 或旧版 `session.idleMinutes`)在空闲窗口后收到消息时创建新的 `sessionId`。当每日重置和空闲过期同时配置时,先到期的优先。
|
||
|
||
实现细节:决策发生在 `src/auto-reply/reply/session.ts` 中的 `initSessionState()` 函数。
|
||
|
||
---
|
||
|
||
## 会话存储结构(`sessions.json`)
|
||
|
||
存储的值类型是 `src/config/sessions.ts` 中的 `SessionEntry`。
|
||
|
||
关键字段(非完整列表):
|
||
|
||
- `sessionId`:当前对话记录 ID(文件名由此派生,除非设置了 `sessionFile`)
|
||
- `updatedAt`:最后活动时间戳
|
||
- `sessionFile`:可选的显式对话记录路径覆盖
|
||
- `chatType`:`direct | group | room`(帮助 UI 和发送策略)
|
||
- `provider`、`subject`、`room`、`space`、`displayName`:群组/频道标签的元数据
|
||
- 开关:
|
||
- `thinkingLevel`、`verboseLevel`、`reasoningLevel`、`elevatedLevel`
|
||
- `sendPolicy`(每会话覆盖)
|
||
- 模型选择:
|
||
- `providerOverride`、`modelOverride`、`authProfileOverride`
|
||
- Token 计数器(尽力而为 / 依赖提供商):
|
||
- `inputTokens`、`outputTokens`、`totalTokens`、`contextTokens`
|
||
- `compactionCount`:该会话键的自动压缩完成次数
|
||
- `memoryFlushAt`:上次压缩前记忆刷写的时间戳
|
||
- `memoryFlushCompactionCount`:上次刷写运行时的压缩计数
|
||
|
||
存储可安全编辑,但 Gateway网关是权威来源:随着会话运行,它可能重写或重新注入条目。
|
||
|
||
---
|
||
|
||
## 对话记录结构(`*.jsonl`)
|
||
|
||
对话记录由 `@mariozechner/pi-coding-agent` 的 `SessionManager` 管理。
|
||
|
||
文件格式为 JSONL:
|
||
|
||
- 第一行:会话头(`type: "session"`,包含 `id`、`cwd`、`timestamp`、可选 `parentSession`)
|
||
- 之后:带 `id` + `parentId` 的会话条目(树结构)
|
||
|
||
主要条目类型:
|
||
|
||
- `message`:用户/助手/工具结果消息
|
||
- `custom_message`:扩展注入的消息,*会*进入模型上下文(可从 UI 隐藏)
|
||
- `custom`:扩展状态,*不会*进入模型上下文
|
||
- `compaction`:持久化的压缩摘要,包含 `firstKeptEntryId` 和 `tokensBefore`
|
||
- `branch_summary`:导航树分支时的持久化摘要
|
||
|
||
OpenClaw 有意**不**"修复"对话记录;Gateway网关使用 `SessionManager` 来读写它们。
|
||
|
||
---
|
||
|
||
## 上下文窗口 vs 跟踪的 token
|
||
|
||
两个不同的概念很重要:
|
||
|
||
1. **模型上下文窗口**:每个模型的硬上限(模型可见的 token 数)
|
||
2. **会话存储计数器**:写入 `sessions.json` 的滚动统计(用于 /status 和仪表板)
|
||
|
||
如果你正在调整限制:
|
||
|
||
- 上下文窗口来自模型目录(可通过配置覆盖)。
|
||
- 存储中的 `contextTokens` 是运行时估算/报告值;不要将其视为严格保证。
|
||
|
||
更多信息请参阅 [/token-use](/token-use)。
|
||
|
||
---
|
||
|
||
## 压缩:什么是压缩
|
||
|
||
压缩将较旧的对话总结为对话记录中的持久化 `compaction` 条目,并保持最近的消息不变。
|
||
|
||
压缩后,后续轮次会看到:
|
||
|
||
- 压缩摘要
|
||
- `firstKeptEntryId` 之后的消息
|
||
|
||
压缩是**持久化的**(不同于会话修剪)。参阅 [/concepts/session-pruning](/concepts/session-pruning)。
|
||
|
||
---
|
||
|
||
## 自动压缩何时触发(Pi 运行时)
|
||
|
||
在嵌入式 Pi 智能体中,自动压缩在两种情况下触发:
|
||
|
||
1. **溢出恢复**:模型返回上下文溢出错误 → 压缩 → 重试。
|
||
2. **阈值维护**:在成功的轮次之后,当:
|
||
|
||
`contextTokens > contextWindow - reserveTokens`
|
||
|
||
其中:
|
||
|
||
- `contextWindow` 是模型的上下文窗口
|
||
- `reserveTokens` 是为提示词 + 下一次模型输出预留的余量
|
||
|
||
这些是 Pi 运行时语义(OpenClaw 消费事件,但由 Pi 决定何时压缩)。
|
||
|
||
---
|
||
|
||
## 压缩设置(`reserveTokens`、`keepRecentTokens`)
|
||
|
||
Pi 的压缩设置位于 Pi 设置中:
|
||
|
||
```json5
|
||
{
|
||
compaction: {
|
||
enabled: true,
|
||
reserveTokens: 16384,
|
||
keepRecentTokens: 20000,
|
||
},
|
||
}
|
||
```
|
||
|
||
OpenClaw 还为嵌入式运行强制执行安全下限:
|
||
|
||
- 如果 `compaction.reserveTokens < reserveTokensFloor`,OpenClaw 会将其提升。
|
||
- 默认下限为 `20000` token。
|
||
- 设置 `agents.defaults.compaction.reserveTokensFloor: 0` 可禁用下限。
|
||
- 如果已经更高,OpenClaw 不做修改。
|
||
|
||
原因:为压缩不可避免之前的多轮"清理"(如记忆写入)留出足够余量。
|
||
|
||
实现:`src/agents/pi-settings.ts` 中的 `ensurePiCompactionReserveTokens()`
|
||
(从 `src/agents/pi-embedded-runner.ts` 调用)。
|
||
|
||
---
|
||
|
||
## 用户可见界面
|
||
|
||
你可以通过以下方式观察压缩和会话状态:
|
||
|
||
- `/status`(在任何聊天会话中)
|
||
- `openclaw status`(CLI)
|
||
- `openclaw sessions` / `sessions --json`
|
||
- 详细模式:`🧹 Auto-compaction complete` + 压缩计数
|
||
|
||
---
|
||
|
||
## 静默清理(`NO_REPLY`)
|
||
|
||
OpenClaw 支持用于后台任务的"静默"轮次,用户不应看到中间输出。
|
||
|
||
约定:
|
||
|
||
- 助手以 `NO_REPLY` 开始其输出,表示"不要向用户传递回复"。
|
||
- OpenClaw 在传递层中剥离/抑制此标记。
|
||
|
||
自 `2026.1.10` 起,当部分块以 `NO_REPLY` 开头时,OpenClaw 还会抑制**草稿/输入中的流式传输**,因此静默操作不会在轮次中途泄露部分输出。
|
||
|
||
---
|
||
|
||
## 压缩前"记忆刷写"(已实现)
|
||
|
||
目标:在自动压缩发生之前,运行一个静默的智能体轮次,将持久状态写入磁盘(例如智能体工作区中的 `memory/YYYY-MM-DD.md`),以防压缩擦除关键上下文。
|
||
|
||
OpenClaw 使用**预阈值刷写**方法:
|
||
|
||
1. 监控会话上下文使用量。
|
||
2. 当超过"软阈值"(低于 Pi 的压缩阈值)时,向智能体运行静默的"立即写入记忆"指令。
|
||
3. 使用 `NO_REPLY` 使用户看不到任何内容。
|
||
|
||
配置(`agents.defaults.compaction.memoryFlush`):
|
||
|
||
- `enabled`(默认:`true`)
|
||
- `softThresholdTokens`(默认:`4000`)
|
||
- `prompt`(刷写轮次的用户消息)
|
||
- `systemPrompt`(刷写轮次追加的额外系统提示词)
|
||
|
||
备注:
|
||
|
||
- 默认的 prompt/systemPrompt 包含 `NO_REPLY` 提示以抑制传递。
|
||
- 每个压缩周期刷写运行一次(在 `sessions.json` 中跟踪)。
|
||
- 刷写仅在嵌入式 Pi 会话中运行(CLI 后端跳过)。
|
||
- 当会话工作区为只读(`workspaceAccess: "ro"` 或 `"none"`)时跳过刷写。
|
||
- 工作区文件布局和写入模式参阅[记忆](/concepts/memory)。
|
||
|
||
Pi 也在扩展 API 中暴露了 `session_before_compact` 钩子,但 OpenClaw 的刷写逻辑目前在 Gateway网关侧。
|
||
|
||
---
|
||
|
||
## 故障排除清单
|
||
|
||
- 会话键错误?从 [/concepts/session](/concepts/session) 开始,确认 `/status` 中的 `sessionKey`。
|
||
- 存储与对话记录不匹配?确认 Gateway网关主机以及 `openclaw status` 中的存储路径。
|
||
- 压缩过于频繁?检查:
|
||
- 模型上下文窗口(太小)
|
||
- 压缩设置(`reserveTokens` 相对于模型窗口过高可能导致更早触发压缩)
|
||
- 工具结果膨胀:启用/调整会话修剪
|
||
- 静默轮次泄露?确认回复以 `NO_REPLY`(精确 token)开头,且你使用的构建版本包含流式传输抑制修复。
|