- cron-job-tool.md: Implementation plan for scheduled task system - auto-memory-refresh.md: Design for pre-compaction memory flush (future) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
416 lines
12 KiB
Markdown
416 lines
12 KiB
Markdown
# Auto Memory Refresh 实现方案
|
||
|
||
## 概述
|
||
|
||
在上下文压缩(compaction)发生之前,自动触发一个特殊的 agent turn,让 agent 分析即将被删除的消息,提取关键信息并写入 memory 文件,防止重要上下文丢失。
|
||
|
||
## OpenClaw 的实现分析
|
||
|
||
### 核心机制
|
||
|
||
OpenClaw 采用 **Pre-compaction Memory Flush** 策略:
|
||
|
||
```
|
||
Session 运行中,token 累积
|
||
↓
|
||
totalTokens >= (contextWindow - reserveTokens - softThreshold)?
|
||
↓ YES
|
||
触发 Memory Flush Turn(特殊的 agent 对话轮次)
|
||
↓
|
||
Agent 分析会话,将重要信息保存到 memory/YYYY-MM-DD.md
|
||
↓
|
||
然后才执行 Compaction(删除旧消息)
|
||
```
|
||
|
||
### 关键设计点
|
||
|
||
1. **Soft Threshold(软阈值)**
|
||
- 默认值:`4000 tokens`
|
||
- 触发条件:`totalTokens >= contextWindow - reserveTokens - softThreshold`
|
||
- 在真正达到 compaction 阈值之前就触发 memory flush
|
||
|
||
2. **Memory Flush Prompt**
|
||
```
|
||
Pre-compaction memory flush. Store durable memories now
|
||
(use memory/YYYY-MM-DD.md; create memory/ if needed).
|
||
If nothing to store, reply with [SILENT].
|
||
```
|
||
|
||
3. **防重复机制**
|
||
- 使用 `memoryFlushCompactionCount` 追踪
|
||
- 确保每次 compaction 周期只触发一次 flush
|
||
|
||
4. **Memory 文件结构**
|
||
```
|
||
~/.super-multica/agent-profiles/<profile-id>/
|
||
├── memory.md # 主 memory 文件
|
||
└── memory/
|
||
├── 2024-01-15.md # 日期分片
|
||
├── 2024-01-16.md
|
||
└── topics/
|
||
└── project-x.md # 主题分片
|
||
```
|
||
|
||
---
|
||
|
||
## Super Multica 实现方案
|
||
|
||
### Phase 1: 核心实现
|
||
|
||
#### 1.1 新增配置项
|
||
|
||
**文件:** `src/agent/session/session-manager.ts`
|
||
|
||
```typescript
|
||
export type SessionManagerOptions = {
|
||
// ... existing options ...
|
||
|
||
// Memory Flush 配置
|
||
/** 是否启用自动 memory flush(默认:true) */
|
||
enableMemoryFlush?: boolean | undefined;
|
||
/** Memory flush 软阈值(在 compaction 前多少 tokens 触发),默认 4000 */
|
||
memoryFlushSoftTokens?: number | undefined;
|
||
};
|
||
```
|
||
|
||
#### 1.2 新增 Memory Flush 模块
|
||
|
||
**文件:** `src/agent/memory/memory-flush.ts`
|
||
|
||
```typescript
|
||
/** Memory flush 配置 */
|
||
export type MemoryFlushSettings = {
|
||
/** 软阈值(tokens),在达到 compaction 阈值前多少 tokens 触发 */
|
||
softThresholdTokens: number;
|
||
/** Memory flush 使用的系统 prompt */
|
||
systemPrompt: string;
|
||
/** Memory flush 使用的用户 prompt */
|
||
userPrompt: string;
|
||
};
|
||
|
||
export const DEFAULT_MEMORY_FLUSH_SETTINGS: MemoryFlushSettings = {
|
||
softThresholdTokens: 4000,
|
||
systemPrompt: `You are in a pre-compaction memory flush turn. The session is approaching context limit and old messages will be deleted soon.
|
||
|
||
Your task: Review the conversation and extract any important information that should be preserved in long-term memory. Focus on:
|
||
- User preferences and settings
|
||
- Key decisions made
|
||
- Important technical details or solutions
|
||
- Project-specific knowledge
|
||
- Anything the user would want remembered in future sessions
|
||
|
||
Use the memory_write tool to save important information. If there's nothing worth saving, respond with [SILENT].`,
|
||
|
||
userPrompt: `[SYSTEM] Pre-compaction memory flush triggered. Please review recent conversation and save any important information to memory before context compression occurs.`,
|
||
};
|
||
|
||
/** 检查是否应该触发 memory flush */
|
||
export function shouldRunMemoryFlush(params: {
|
||
currentTokens: number;
|
||
contextWindowTokens: number;
|
||
reserveTokens: number;
|
||
softThresholdTokens: number;
|
||
lastMemoryFlushCompactionCount?: number;
|
||
currentCompactionCount: number;
|
||
}): boolean {
|
||
const {
|
||
currentTokens,
|
||
contextWindowTokens,
|
||
reserveTokens,
|
||
softThresholdTokens,
|
||
lastMemoryFlushCompactionCount,
|
||
currentCompactionCount,
|
||
} = params;
|
||
|
||
// 如果当前 compaction 周期已经 flush 过,不再触发
|
||
if (lastMemoryFlushCompactionCount === currentCompactionCount) {
|
||
return false;
|
||
}
|
||
|
||
// 计算 flush 阈值
|
||
const flushThreshold = contextWindowTokens - reserveTokens - softThresholdTokens;
|
||
|
||
return currentTokens >= flushThreshold;
|
||
}
|
||
```
|
||
|
||
#### 1.3 扩展 SessionEntry 类型
|
||
|
||
**文件:** `src/agent/session/types.ts`
|
||
|
||
```typescript
|
||
export type SessionMeta = {
|
||
// ... existing fields ...
|
||
|
||
/** 上次 memory flush 的时间戳 */
|
||
memoryFlushAt?: number;
|
||
/** 上次 memory flush 时的 compaction 计数 */
|
||
memoryFlushCompactionCount?: number;
|
||
/** Compaction 次数 */
|
||
compactionCount?: number;
|
||
};
|
||
```
|
||
|
||
#### 1.4 修改 Agent Runner
|
||
|
||
**文件:** `src/agent/runner.ts`
|
||
|
||
```typescript
|
||
// 在 maybeCompact 之前检查并执行 memory flush
|
||
private async maybeCompact() {
|
||
const messages = this.agent.state.messages.slice();
|
||
|
||
// Phase 0: Check if memory flush is needed
|
||
if (this.enableMemoryFlush) {
|
||
const shouldFlush = shouldRunMemoryFlush({
|
||
currentTokens: this.estimateCurrentTokens(messages),
|
||
contextWindowTokens: this.contextWindowGuard.tokens,
|
||
reserveTokens: this.reserveTokens,
|
||
softThresholdTokens: this.memoryFlushSoftTokens,
|
||
lastMemoryFlushCompactionCount: this.session.getMeta()?.memoryFlushCompactionCount,
|
||
currentCompactionCount: this.session.getCompactionCount(),
|
||
});
|
||
|
||
if (shouldFlush) {
|
||
await this.runMemoryFlush();
|
||
}
|
||
}
|
||
|
||
// 继续原有的 compaction 逻辑...
|
||
if (!this.session.needsCompaction(messages)) return;
|
||
// ...
|
||
}
|
||
|
||
private async runMemoryFlush() {
|
||
this.emitMulticaEvent({ type: "memory_flush_start" });
|
||
|
||
try {
|
||
// 创建一个临时的 agent turn 来执行 memory flush
|
||
const flushResult = await this.executeMemoryFlushTurn();
|
||
|
||
// 更新 session metadata
|
||
this.session.saveMeta({
|
||
...this.session.getMeta(),
|
||
memoryFlushAt: Date.now(),
|
||
memoryFlushCompactionCount: this.session.getCompactionCount(),
|
||
});
|
||
|
||
this.emitMulticaEvent({
|
||
type: "memory_flush_end",
|
||
saved: flushResult.memoriesSaved,
|
||
});
|
||
} catch (error) {
|
||
console.error("[Agent] Memory flush failed:", error);
|
||
// Memory flush 失败不阻塞 compaction
|
||
}
|
||
}
|
||
|
||
private async executeMemoryFlushTurn(): Promise<{ memoriesSaved: number }> {
|
||
// 使用特殊的 system prompt 和 user prompt
|
||
// 让 agent 分析当前会话并保存重要信息
|
||
// 只允许使用 memory_write 工具
|
||
|
||
const originalSystemPrompt = this.agent.state.systemPrompt;
|
||
const originalTools = this.agent.state.tools;
|
||
|
||
try {
|
||
// 临时切换到 memory flush 模式
|
||
this.agent.setSystemPrompt(this.memoryFlushSettings.systemPrompt);
|
||
this.agent.setTools([this.memoryWriteTool]); // 只允许 memory_write
|
||
|
||
// 执行一个 agent turn
|
||
await this.agent.run(this.memoryFlushSettings.userPrompt);
|
||
|
||
// 统计保存了多少 memory
|
||
return { memoriesSaved: this.countMemoryWriteCalls() };
|
||
} finally {
|
||
// 恢复原始设置
|
||
this.agent.setSystemPrompt(originalSystemPrompt);
|
||
this.agent.setTools(originalTools);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 1.5 新增 Memory Flush Events
|
||
|
||
**文件:** `src/agent/events.ts`
|
||
|
||
```typescript
|
||
/** Memory flush 开始事件 */
|
||
export type MemoryFlushStartEvent = {
|
||
type: "memory_flush_start";
|
||
};
|
||
|
||
/** Memory flush 结束事件 */
|
||
export type MemoryFlushEndEvent = {
|
||
type: "memory_flush_end";
|
||
/** 保存的 memory 条目数 */
|
||
saved: number;
|
||
};
|
||
|
||
/** Union of all Multica-specific events */
|
||
export type MulticaEvent =
|
||
| CompactionStartEvent
|
||
| CompactionEndEvent
|
||
| MemoryFlushStartEvent
|
||
| MemoryFlushEndEvent;
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 2: Memory 文件管理
|
||
|
||
#### 2.1 Memory 文件结构
|
||
|
||
```
|
||
~/.super-multica/agent-profiles/<profile-id>/
|
||
├── identity.md # 身份设定(已有)
|
||
├── memory.md # 主 memory 文件(已有)
|
||
├── memory/ # Memory 分片目录(新增)
|
||
│ ├── 2024-01-15.md # 日期分片
|
||
│ ├── 2024-01-16.md
|
||
│ └── ...
|
||
└── sessions/ # Session 记录(已有)
|
||
```
|
||
|
||
#### 2.2 Memory Write Tool 增强
|
||
|
||
**文件:** `src/agent/tools/memory/memory-write.ts`
|
||
|
||
```typescript
|
||
// 支持写入日期分片
|
||
export function resolveMemoryPath(
|
||
profileDir: string,
|
||
targetFile?: string
|
||
): string {
|
||
if (!targetFile) {
|
||
// 默认写入今天的日期分片
|
||
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
||
const memoryDir = path.join(profileDir, 'memory');
|
||
ensureDirSync(memoryDir);
|
||
return path.join(memoryDir, `${today}.md`);
|
||
}
|
||
|
||
// 支持指定文件
|
||
if (targetFile.startsWith('memory/')) {
|
||
return path.join(profileDir, targetFile);
|
||
}
|
||
|
||
return path.join(profileDir, 'memory.md');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Phase 3: 前端集成
|
||
|
||
#### 3.1 SDK 事件类型
|
||
|
||
**文件:** `packages/sdk/src/types.ts`
|
||
|
||
```typescript
|
||
export type LocalChatEvent =
|
||
| MessageStartEvent
|
||
| MessageUpdateEvent
|
||
| MessageEndEvent
|
||
| ToolExecutionStartEvent
|
||
| ToolExecutionEndEvent
|
||
| CompactionStartEvent
|
||
| CompactionEndEvent
|
||
| MemoryFlushStartEvent // 新增
|
||
| MemoryFlushEndEvent; // 新增
|
||
```
|
||
|
||
#### 3.2 Zustand Store
|
||
|
||
**文件:** `packages/store/src/agent-store.ts`
|
||
|
||
```typescript
|
||
interface AgentState {
|
||
// ... existing state ...
|
||
|
||
/** 是否正在进行 memory flush */
|
||
memoryFlushing: boolean;
|
||
/** 上次 memory flush 信息 */
|
||
lastMemoryFlush: {
|
||
timestamp: number;
|
||
saved: number;
|
||
} | null;
|
||
}
|
||
```
|
||
|
||
#### 3.3 Desktop UI 提示
|
||
|
||
在 compaction 提示之前显示 "正在保存重要记忆..."
|
||
|
||
---
|
||
|
||
## 实现顺序
|
||
|
||
1. **Step 1**: 新增 `memory-flush.ts` 模块,定义类型和判断逻辑
|
||
2. **Step 2**: 扩展 `SessionMeta` 类型,添加 flush 相关字段
|
||
3. **Step 3**: 新增 `MemoryFlushStartEvent` 和 `MemoryFlushEndEvent` 事件
|
||
4. **Step 4**: 修改 `Agent` 类,添加 `runMemoryFlush` 方法
|
||
5. **Step 5**: 修改 `maybeCompact` 流程,在 compaction 前检查并执行 flush
|
||
6. **Step 6**: 增强 `memory_write` tool,支持日期分片
|
||
7. **Step 7**: SDK 和 Store 集成
|
||
8. **Step 8**: Desktop UI 提示
|
||
|
||
---
|
||
|
||
## 配置项汇总
|
||
|
||
| 配置项 | 类型 | 默认值 | 说明 |
|
||
|--------|------|--------|------|
|
||
| `enableMemoryFlush` | boolean | true | 是否启用自动 memory flush |
|
||
| `memoryFlushSoftTokens` | number | 4000 | 在 compaction 阈值前多少 tokens 触发 |
|
||
|
||
---
|
||
|
||
## 与现有功能的关系
|
||
|
||
```
|
||
Token 累积
|
||
↓
|
||
达到 Memory Flush 阈值? ──────────────────────┐
|
||
↓ YES │
|
||
Memory Flush Turn(新功能) │
|
||
├─ Agent 分析会话 │
|
||
├─ 调用 memory_write 保存重要信息 │
|
||
└─ 更新 memoryFlushCompactionCount │
|
||
↓ │
|
||
达到 Compaction 阈值? ←───────────────────────┘
|
||
↓ YES ↓ NO
|
||
┌─────────────────────────────┐ │
|
||
│ Tool Result Pruning(已实现)│ │
|
||
│ Soft-trim / Hard-clear │ │
|
||
└─────────────────────────────┘ │
|
||
↓ │
|
||
Message Compaction │
|
||
├─ 删除旧消息 │
|
||
└─ 或生成摘要 │
|
||
↓ │
|
||
继续会话 ←──────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 风险和注意事项
|
||
|
||
1. **Token 消耗**: Memory flush turn 本身会消耗 tokens,需要控制 prompt 长度
|
||
2. **循环触发**: 需要 `memoryFlushCompactionCount` 防止重复触发
|
||
3. **Tool 限制**: Flush turn 应该只允许 `memory_write`,防止执行其他操作
|
||
4. **超时处理**: Flush turn 需要有超时机制,不能阻塞太久
|
||
5. **静默响应**: 如果没有需要保存的内容,agent 应该返回 `[SILENT]` 跳过
|
||
|
||
---
|
||
|
||
## 测试计划
|
||
|
||
1. **单元测试**: `shouldRunMemoryFlush` 判断逻辑
|
||
2. **集成测试**: Memory flush turn 执行流程
|
||
3. **E2E 测试**: 完整的 flush → compaction 流程
|
||
4. **边界测试**:
|
||
- 连续多次 compaction 只触发一次 flush
|
||
- Flush 失败不阻塞 compaction
|
||
- Agent 返回 [SILENT] 时正常跳过
|