# Context Compressor

Adopt this skill: https://proskills.md/skills/other/happyfaptain-context-compressor

Page: https://proskills.md/skills/other/happyfaptain-context-compressor
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: SKILL.md
size: 11780
upstream: https://github.com/happyfaptain/context-compressor/blob/HEAD/SKILL.md

````text
---
name: Context Compressor
slug: context-compressor
version: 5.0.0
description: 自动上下文压缩。当session超阈值时，先提取重要内容到日记，再压缩JSONL。支持light/standard/aggressive模式。触发条件：session>8K tokens、用户要求压缩、心跳维护时发现session过大。v5.0新增：防死循环、大输出截断、性能优化。
metadata: {"openclaw":{"emoji":"📦","requires":{"bins":[]},"os":["linux","darwin","win32"]}}
---

# Context Compressor 📦

**两阶段压缩：先提取，再压缩。不提取就压缩 = 丢信息。**

## 何时压缩

| 条件 | 动作 |
|------|------|
| Session > 8K tokens（~32K字符） | 主动压缩 |
| 用户说"压缩上下文"/"compress context" | 立即压缩 |
| 心跳检查发现session膨胀 | 主动压缩 |
| 长任务开始前，session已很大 | 先压缩再继续 |

**不要压缩**：任务进行中、session即将结束、刚压缩过且仍低于2倍阈值。

## Token估算

| 内容 | 公式 | 说明 |
|------|------|------|
| 主要中文 | `字符数 / 1.5` | 中文token密度高 |
| 主要英文 | `字符数 / 4` | 英文token密度低 |
| 混合 | `字符数 / 3` | 通用估算（脚本默认） |
| JSON/代码多 | `字符数 / 3.5` | 结构化内容更紧凑 |

**注意**：以上为粗略估算，实际token数因模型而异。建议用模型官方tokenizer精确计算。

**快速判断**：session文件 > 32K字符 ≈ > 8K tokens（混合内容），该压缩了。

---

## Phase 1: 提取（必须先做）

扫描session中间部分，把重要内容存到外部文件。

| 要提取的 | 存到哪里 | 格式 |
|---------|---------|------|
| 用户偏好/事实 | SOUL.md → 偏好部分 | 更新或新增bullet |
| 关键决策/结论 | memory/YYYY-MM-DD.md | `## Decision: <topic>` |
| 创作作品全文 | memory/YYYY-MM-DD.md | `## Creative: <title>` |
| 重要对话摘录 | memory/YYYY-MM-DD.md | `## Conversation: <topic>` |
| 技术解决方案 | memory/YYYY-MM-DD.md | `## Technical: <issue>` |
| 新话题/类别 | SOUL.md → 事件索引 | 新增条目 |
| 闲聊/日常 | **不存** | — |

**日记追加格式**：

```markdown
---

## 压缩提取 HH:mm

### <Category>: <Brief Title>
- **Context**: 为什么重要
- **Content**: 实际信息
- **Session ref**: 大致轮次范围
```

**日记追加，绝不覆盖。**

---

## Phase 2: 压缩Session

### 优先级系统（从低到高删）

| 优先级 | 内容 | 删减顺序 |
|--------|------|---------|
| P0 | 无法解析的行（安全保留） | 保留 |
| P1 | 大型tool输出（>2000字符） | 1st |
| P2 | 导航/探索调用（ls, pwd, grep未命中） | 2nd |
| P3 | 成功但常规的tool调用 | 3rd |
| P4 | 冗长的assistant解释 | 4th |
| P5 | 日常寒暄（"好的""谢谢"） | 5th |
| P6 | 有意图的用户消息 | 保留 |
| P7 | 最近对话（tail） | 保留 |
| P8 | System消息 | 保留 |
| P9 | 已有Summary（合并处理） | 合并 |
| P10 | Session header | 保留 |

### 压缩模式

| 模式 | Token目标 | 操作 | 适用场景 |
|------|----------|------|---------|
| light | 6K | 只删P1-P2 | 刚过阈值，主要是tool膨胀 |
| standard | 4K | 删P1-P3，摘要P4-P5 | 常规维护（默认） |
| aggressive | 2.5K | 删P1-P5，最大化摘要 | session很长，需要最大压缩 |

### 大输出截断策略

当tool输出 >2000字符时，不要只做全删/全留二选一。优先截断：

| 策略 | 操作 | 节省 |
|------|------|------|
| 截断到500字符 | 保留前500字符 + `...[truncated, N chars omitted]` | ~75% |
| 截断到200字符 | 保留前200字符 + 截断标记 | ~90% |
| 全删 | 完全移除该行 | 100% |

**截断优先级**：先截断到500 → 仍超标则截断到200 → 仍超标则全删。

截断时保留tool调用的结构信息（tool name、参数摘要），只删输出内容。

### 操作步骤

```
1. 读取 session JSONL
   路径: ~/.openclaw-autoclaw/agents/<agentId>/sessions/<sessionId>.jsonl

2. 估算token数，低于阈值则退出

3. 检查防死循环标记（见下方），如触发则跳过

4. 备份原文件 → <sessionId>.jsonl.bak

5. 分类每行（P0-P10），缓存解析结果，避免重复JSON解析

6. 识别tail（从末尾往前数60行P6+内容）

7. 截断大输出（P1行先截断到500字符，再截断到200，最后全删）

8. 按优先级从低到高删除（不删tail）
   直到token数达标或只剩P6+
   每删一行计数+1，超过maxIter则停止

9. 生成 [SESSION SUMMARY]（合并已有摘要，不要堆叠）
   ≤6条bullet，每条≤2行，带文件引用

10. 组装：header + summary(1行) + tail

11. 验证：每行有效JSON、至少1条user+1条assistant、token数达标

12. 写入，验证通过后删.bak，失败则回滚

13. 写入防死循环标记

14. 日志：追加到 memory/YYYY-MM-DD.md
```

### Summary格式

```
[SESSION SUMMARY]
• [Decision] <决定内容> → memory/<YYYY-MM-DD>.md##Section
• [Technical] <解决方案> → memory/<YYYY-MM-DD>.md##Section
• [Fact] <关键事实>
• [State] <当前任务状态>
• [Warning] <未来必须知道的事>
```

**规则**：≤6条、每条≤2行、带文件引用、用标签分类、只写结果不写过程。日期使用当天日期。

### 处理已有Summary

如果session中已有 `[SESSION SUMMARY]`：
1. 解析旧summary
2. 与新内容合并
3. 去重：`[State]`替换为新`[State]`，已解决的`[Warning]`删除，同类多bullet保留（如多个`[Technical]`）
4. 结果始终只有一条 `[SESSION SUMMARY]`

**绝对不要堆叠多条summary。**

---

## 防死循环机制 ⚠️

本地模型执行压缩时可能陷入死循环。以下机制防止此问题：

### 1. 最大迭代限制

| 参数 | 默认值 | 说明 |
|------|--------|------|
| maxIter | 100 | 模型内操作时，删除操作的最大迭代次数 |
| maxIter (脚本) | 5000 | 脚本执行时的最大迭代次数 |

超过限制后立即停止，输出当前结果。

### 2. 压缩冷却期

压缩完成后，在session中写入标记：

```json
{"type":"system","lastCompressed":"<timestamp>","compressedTokens":<N>}
```

**规则**：
- 距上次压缩 < 5分钟 → 不压缩
- 上次压缩后token数未增长 > 20% → 不压缩
- 连续3次压缩后仍未达标 → 停止压缩，报告错误

### 3. 压缩效果检查

压缩后验证：
- 压缩后token数必须 < 压缩前token数 × 0.9（至少减少10%）
- 如果压缩后几乎没减少 → 说明session主要由P6+内容组成，无法进一步压缩
- 此时不要重试，而是报告："Session主要由高优先级内容组成，无法进一步压缩。建议增大阈值或手动清理。"

### 4. 模型执行安全

当由本地模型执行压缩时：
- **单次执行**：每次只执行一次完整的压缩流程
- **不自动重试**：压缩完成后不自动检查是否需要再次压缩
- **明确退出**：压缩完成后输出 `"Compression complete. <N> tokens remaining."` 然后停止
- **异常中断**：如果执行过程中遇到错误，立即停止并报告，不要尝试恢复

---

## 安全与恢复

| 步骤 | 动作 |
|------|------|
| 压缩前 | 复制 `.jsonl` → `.jsonl.bak` |
| 验证通过 | 删除 `.jsonl.bak` |
| 验证失败 | 回滚 `.jsonl.bak` → `.jsonl` |
| 手动恢复 | 用户可从 `.jsonl.bak` 恢复 |

验证清单：每行有效JSON ✅ 至少1条user消息 ✅ 至少1条assistant消息 ✅ token数达标 ✅

---

## 脚本（可选）

如需脚本化执行，见 `scripts/` 目录：

| 脚本 | 平台 | 用法 |
|------|------|------|
| `compress-session.ps1` | Windows | `.\compress-session.ps1 -SessionFile <path> [-Mode standard] [-KeepTail 60] [-MaxIter 100] [-DryRun]` |
| `compress-session.sh` | Linux/macOS | `./compress-session.sh <session-file> [mode] [keep-tail] [dry-run]` |
| `compress-simple.py` | 跨平台 | `python compress-simple.py <session.jsonl> [mode] [tail_lines]` |
| `compress-all.py` | 跨平台(批量) | `python compress-all.py` |
| `diary-logger.py` | 跨平台 | 见下方日记记录章节 |

**模型内操作优先**：直接用read/write/exec工具按上述步骤执行，比脚本更灵活。

---

## 日记记录

### diary-logger.py 用法

```bash
# Phase 1: 压缩前提取重要内容到日记
python diary-logger.py extract <session.jsonl> <workspace_dir>

# 压缩后记录压缩事件
python diary-logger.py log-compression <workspace_dir> --mode standard --before 8000 --after 4000 --removed 45

# 验证日记完整性
python diary-logger.py validate <workspace_dir>

# 手动更新 MEMORY.md 索引
python diary-logger.py update-index <workspace_dir> --note "手动添加的备注"
```

### 日记记录触发机制

| 触发时机 | 动作 | 命令 |
|---------|------|------|
| 压缩前（Phase 1） | 提取重要内容到日记 | `diary-logger.py extract` |
| 压缩后 | 记录压缩事件 | `diary-logger.py log-compression` |
| 心跳检查时 | 验证日记完整性 | `diary-logger.py validate` |
| 手动 | 更新MEMORY.md索引 | `diary-logger.py update-index` |

### 日记数据格式

压缩提取追加到 `memory/YYYY-MM-DD.md`：

```markdown
---

## 压缩提取 HH:mm

### Decision: <简要标题>
- **Context**: 为什么重要
- **Content**: 实际信息
- **Source**: user/assistant/summary
```

压缩事件追加到 `memory/YYYY-MM-DD.md`：

```markdown
---

## 压缩执行 HH:mm

### Compression: standard mode
- **Before**: 8,000 tokens
- **After**: 4,000 tokens
- **Saved**: 50.0%
- **Removed lines**: 45
- **Timestamp**: 2026-04-21T12:00:00+08:00
```

### 错误处理策略

| 错误场景 | 处理方式 |
|---------|---------|
| 日记文件不存在 | 自动创建，含正确标题 |
| 写入失败 | 从 .bak 回滚，报告错误 |
| MEMORY.md 格式异常 | 前缀匹配日期，兼容 `### 2026-04-21 — 标题` 格式 |
| 提取无重要内容 | 输出提示，不创建空条目 |
| .bak 文件残留 | validate 命令检测并报告 |

### 完整性验证

`diary-logger.py validate` 检查以下项目：

1. ✅ 今日日记文件存在
2. ✅ 日记标题格式正确（`# YYYY-MM-DD`）
3. ✅ 日记包含章节内容
4. ✅ MEMORY.md 包含今日条目
5. ✅ SOUL.md 存在
6. ✅ memory/ 目录存在且有文件
7. ✅ 无残留 .bak 文件
8. ⚠️ 检测空章节
9. ⚠️ 检测压缩提取次数过多（>3次）

---

## 配置

| 参数 | 默认值 | 说明 |
|------|--------|------|
| tokenThreshold | 8000 | 超过此token数触发压缩 |
| charThreshold | 32000 | 备用：超过此字符数触发 |
| keepTail | 60 | 保留最近对话的最小行数 |
| mode | standard | 压缩模式：light/standard/aggressive |
| autoBackup | true | 压缩前自动备份 |
| maxIter | 100 | 模型内操作最大迭代次数 |
| truncateThreshold | 2000 | 超过此字符数的tool输出触发截断 |
| truncateSize | 500 | 截断后保留的字符数 |
| cooldownMinutes | 5 | 两次压缩之间的最小间隔（分钟） |

---

## 最佳实践

1. **先提取再压缩** — 不提取就压缩 = 永久丢失信息
2. **按token不是按行** — 100行短对话 ≠ 100行tool输出
3. **先截断再删除** — 截断大输出比全删更安全，保留上下文线索
4. **先删tool调用** — 最低价值、最高token，通常单这一步就能省20-40%
5. **只有一条summary** — 合并不堆叠
6. **summary里引用外部文件** — 让未来的自己能找到详情
7. **不中途压缩** — 等任务边界再压缩
8. **遵守冷却期** — 不要连续压缩，至少间隔5分钟
9. **日志记录** — 压缩后追加到日记：`"Compressed X→Y tokens (mode)"`
10. **压缩失败就停止** — 不要重试，报告错误让用户决定
````
