05 - 会话持久化与历史恢复
学习目标
理解 CowAgent 如何通过 SQLite 持久化对话历史、恢复会话上下文,以及处理脏数据的完整方案。
项目实践
存储架构
CowAgent 使用 SQLite 存储对话历史,按 session_id 隔离:
关键设计:
seq字段保证消息顺序content使用 JSON 格式存储(支持 Claude content blocks 格式)channel_type标记消息来源渠道
历史恢复策略
当 Agent 为新 session 初始化时,_restore_conversation_history() 从 SQLite 加载历史:
def _restore_conversation_history(self, agent, session_id: str): store = get_conversation_store() max_turns = conf().get("agent_max_context_turns", 20)
# 调度器 session 使用更小的恢复窗口 if session_id.startswith("scheduler_"): restore_turns = max(1, max_turns // 5) # 默认 4 else: restore_turns = max(3, max_turns // 6) # 默认 3
saved = store.load_messages(session_id, max_turns=restore_turns) filtered = self._filter_text_only_messages(saved) agent.messages = filtered纯文本过滤
恢复的历史只包含纯文本 user/assistant 对,剥离所有 tool_use/tool_result:
def _filter_text_only_messages(messages): # 将消息分组为 turns for turn in turns: # 仅保留: # 1. 用户的第一个 text 消息(实际输入) # 2. 助手的最后一个 text 消息(最终回复) # 所有 tool_use, tool_result, 中间思考全部丢弃 filtered.append({"role": "user", "content": [{"type": "text", "text": user_text}]}) filtered.append({"role": "assistant", "content": [{"type": "text", "text": final_reply}]})这个设计有四个好处:
- 工具调用是中间过程,价值已包含在最终回复中
- 工具消息消耗大量上下文 token(常占 80%+)
- 不同模型的工具消息格式不兼容,跨模型切换会报 400 错误
- 消除了 tool_use/tool_result 配对错误
脏数据清理
当上下文溢出或消息格式错误时,CowAgent 不仅清空内存,还清理数据库:
def _clear_session_db(self): session_id = getattr(self.agent, '_current_session_id', None) if not session_id: return store = get_conversation_store() store.clear_session(session_id)这防止了破损的 tool_use/tool_result 配对在重启后被重新加载。
消息持久化时机
每次 Agent run 结束后,AgentBridge._persist_messages() 将新消息写入 SQLite:
# 思考模式开关控制 thinking 块是否持久化if not thinking_enabled: messages_to_store = self._strip_thinking_blocks(new_messages)else: messages_to_store = new_messages
store.append_messages(session_id, messages_to_store, channel_type=channel_type)问题与规避
| 问题 | CowAgent 的规避策略 |
|---|---|
| 恢复历史时 tool 格式不兼容 | _filter_text_only_messages() 只保留纯文本 |
| 数据库破损导致恢复失败 | try/except 捕获,不影响 Agent 启动 |
| 重启后 thinking 块暴露给用户 | _strip_thinking_blocks() 根据配置决定是否持久化 thinking |
| 调度器 session 历史膨胀 | 更小的恢复窗口(max_turns // 5) |
| 空 session 占用存储空间 | 0 条消息时调用 clear_session() 清理 |
设计取舍
为什么不持久化工具调用链?
CowAgent 明确注释:“Tool call chains are intermediate process, the value is already in the final assistant text reply.” 工具调用是过程,价值在结果(助手的最终回复)。持久化完整工具链消耗 4-5 倍存储空间,且在不同模型间不兼容。对于需要”追溯工具调用过程”的场景,用户可以通过查看日志获取。