# 启动增量恢复设计 ## 问题 PicoBot 重启后,`Session::from_storage()` 全量加载 `messages` 表,恢复的 history 可能直接超出上下文窗口,首次 LLM 调用即触发压缩,浪费 token。 ## 设计 ### 核心思路 用 `last_compressed_message_at` 标记最后压缩时刻。恢复时: - 加载该标记之后的原始消息 - 用该 session 的 Timeline 条目替代已压缩部分 - `seq_counter` 统一从 SQLite 查 `MAX(seq) + 1` ``` messages 表 memories(timeline) ┌──────────────────────────┐ ┌───────────────────────────┐ │ created_at = T1..T5 │ ← 跳过 │ session = feishu:oc:dialog │ │ (压缩已覆盖,用Timeline替代)│ │ created_at 降序 │ ├──────────────────────────┤ ├───────────────────────────┤ │ created_at > T6 │ ← 加载 │ 只取最近 3 条 │ └──────────────────────────┘ └───────────────────────────┘ ``` ### 数据变更 **`sessions` 表加列:** ```sql last_compressed_message_at INTEGER ``` **`SessionMeta` / `Session` 加字段:** `last_compressed_message_at: Option` ### Storage 层新增方法 | 方法 | SQL | |------|-----| | `get_max_message_seq(session_id)` | `SELECT MAX(seq) FROM messages WHERE session_id = ?` | | `load_messages_after_timestamp(session_id, after_ts)` | `WHERE created_at > ?` | | `load_session_timelines(session_id, limit)` | `WHERE session_id = ? AND category = 'timeline' ORDER BY created_at DESC LIMIT ?` | ### 压缩跟踪 `compress_if_needed()` 返回值改为 `CompressionResult { history, created_timelines: bool }`。 `compress_once()` 中 LLM 摘要路径才置 `true`(Tier 2),Tier 1/3 不产生 Timeline。 **记录时机**(`handle_message` 正常流、溢出重试流、`/compact` 统一): ```rust if result.created_timelines { session.last_compressed_message_at = Some(now()); session.persist_session_meta().await; } ``` ### Session::from_storage() 恢复逻辑 有压缩标记时: 1. `load_session_timelines(limit=4)` → 取 3 条给 LLM,第 4 条判"有更多" 2. 有更多 → 插入提示 user 消息 3. 逐条插入 Timeline 为 `[Previous Context]` user 消息 4. `load_messages_after_timestamp(after_ts)` → 原始尾消息 5. `repair_tool_call_chains` 无压缩标记 → 全量加载(现有行为)。 统一:`seq_counter = MAX(seq) + 1` ### 系统提示词 `Session.last_compressed_message_at` 非空时追加: ``` ## 历史会话 之前的对话摘要已归档。如需回顾历史上下文,使用 `timeline_recall` 工具搜索。 ``` ## 改动清单 | # | 文件 | 改动 | |---|------|------| | 1 | `storage/session.rs` | `SessionMeta` 加 `last_compressed_message_at` | | 2 | `storage/mod.rs` | DDL migration + upsert/get_session 加列 | | 3 | `storage/mod.rs` | 新增 `get_max_message_seq`, `load_messages_after_timestamp` | | 4 | `storage/memory.rs` | 新增 `load_session_timelines` | | 5 | `agent/context_compressor.rs` | 返回值改为 `CompressionResult` 含 `created_timelines` | | 6 | `session/session.rs` | `Session` 加字段,`persist_session_meta` 加字段 | | 7 | `session/session.rs` | `from_storage()` 重写恢复逻辑 | | 8 | `session/session.rs` | `handle_message()` 压缩后记录标记 | | 9 | `session/session.rs` | `/compact` 命令压缩后记录标记 | | 10 | `session/session.rs` | `build_system_prompt()` 注入 `last_compressed_message_at` |