跳转到内容

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}]})

这个设计有四个好处:

  1. 工具调用是中间过程,价值已包含在最终回复中
  2. 工具消息消耗大量上下文 token(常占 80%+)
  3. 不同模型的工具消息格式不兼容,跨模型切换会报 400 错误
  4. 消除了 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 倍存储空间,且在不同模型间不兼容。对于需要”追溯工具调用过程”的场景,用户可以通过查看日志获取。

参考来源