---
title: "多轮对话"
description: "理解 Claude Code 的会话编排设计：为什么需要 QueryEngine？会话如何持久化？成本如何追踪？模型如何热切换？"
keywords: ["多轮对话", "会话管理", "QueryEngine", "transcript", "成本追踪"]
---

## 单轮 vs 多轮

- **单轮**（一次 Agentic Loop）：用户说一句话，AI 可能执行多步工具调用后返回结果
- **多轮**（一个会话）：用户和 AI 反复对话，持续数分钟到数小时

单轮关注"AI 如何自主完成任务"，多轮关注"如何管理一个持续演进的会话"——这是完全不同的工程问题。

## 为什么需要会话编排器

单轮的 Agentic Loop 已经很复杂了（错误恢复、上下文压缩、流式处理）。多轮在此基础上还需要管理：

- **对话历史的累积**：消息不断增长，需要压缩策略
- **成本的持续追踪**：跨轮次的 token 用量累计
- **模型的热切换**：用户可能中途换模型
- **会话的持久化与恢复**：意外中断后能继续
- **文件的快照与回滚**：AI 改了文件，用户想撤回

这些职责与"执行一次 agentic loop"无关，所以系统引入了 **QueryEngine** 作为会话编排器——它在 Agentic Loop 之上管理会话级别的状态。

### 设计原则：编排器不参与循环

QueryEngine 只负责"准备好上下文，然后调用 agentic loop"。它不干预单轮循环的内部逻辑——循环中的错误恢复、工具执行、上下文压缩都是自治的。

这种分层使得 agentic loop 可以独立测试和优化，而不受会话状态管理的影响。

## 会话持久化

### 为什么持久化很重要

终端会话是脆弱的——网络断开、进程崩溃、意外关闭都可能丢失对话。持久化确保用户的工作不丢失。

### 设计选择：JSONL 追加写入

对话事件以 JSONL（每行一条 JSON）格式追加写入磁盘。

为什么选择 JSONL 而不是数据库或 JSON 文件？

| 方案 | 优势 | 劣势 |
|------|------|------|
| **JSONL 追加写入** | 写入快速、不需要读-改-写、崩溃安全 | 查询需要扫描整个文件 |
| JSON 文件 | 结构清晰 | 每次写入需要读取整个文件、修改、写回——大文件很慢 |
| SQLite 数据库 | 查询高效 | 引入额外依赖、事务管理复杂 |

追加写入是关键设计：每次新消息只需要在文件末尾追加一行，不需要读取和修改已有内容。即使写入过程中崩溃，也只会丢失最后一条记录，不会损坏整个文件。

### 存储结构

```
~/.claude/projects/<项目目录>/
  ├── <session-1>.jsonl
  ├── <session-2>.jsonl
  └── ...
```

每个项目的会话归入同一目录。同一项目的对话可以跨会话积累上下文。

### 大小防护

读取上限为 50MB。超过这个大小的会话文件不会被完整加载——这是防止超大会话导致内存溢出的安全措施。

### 会话恢复

当用户使用 `--resume` 恢复会话时，系统：

1. 从 JSONL 文件重建消息数组
2. 恢复累计费用状态
3. 恢复用户选择的模型和配置
4. 从中断点继续对话

整个过程对用户是透明的——恢复后的对话就像从来没有中断过。

## 成本追踪

### 为什么需要成本追踪

AI API 按 token 计费，一次复杂任务可能消耗大量 token。没有成本追踪，用户无法判断"这次对话花了多少钱"，也无法在费用过高时及时终止。

### 三层追踪架构

| 层 | 职责 | 持久性 |
|----|------|--------|
| **记录层** | 从每个 API 响应中提取 token 用量 | 实时 |
| **累计层** | 按模型汇总累计费用（切换模型时分别统计） | 会话内 |
| **持久化层** | 会话结束时保存到项目配置 | 跨重启 |

### 预算提醒

系统提供会话级的预算上限。当累计费用超过阈值时弹出提醒——这不是硬性阻断，而是"软提醒"。设计上选择了提醒而非强制终止，因为用户可能正在进行关键操作（如修复生产 bug），强制终止可能造成更大损失。

## 模型热切换

### 设计挑战

在一个持续对话中切换模型看似简单，实际上需要解决几个问题：

1. **上下文窗口不同**：Sonnet 的 200K 和 Opus 的 1M 需要不同的压缩策略
2. **对话历史兼容**：旧模型生成的内容新模型需要能理解
3. **费用计算**：不同模型定价不同，需要分别统计

### 设计决策：消息与模型解耦

对话历史（消息数组）和模型选择是独立的。切换模型只改变"下一次 API 调用用什么模型"，不修改已有消息。系统会在下次调用前根据新模型的规格重新计算上下文窗口和输出限制。

这意味着用户可以在对话中随时切换模型——例如用便宜的 Sonnet 做简单操作，遇到复杂问题时切换到 Opus——而不需要开始新的会话。

## 文件快照与回滚

### 比 git 更细粒度的追踪

AI 每次修改文件前，系统自动保存当前文件内容的快照。快照绑定到具体的消息 ID，使得用户可以精确恢复到对话中任意时间点的文件状态。

这比 `git checkout` 更细粒度——git 只追踪已提交的内容，而文件快照追踪的是 AI 每一次修改前的状态。

### 使用场景

- AI 改了代码但用户不满意 → 回滚到修改前的状态
- AI 进行了多轮修改 → 选择性回滚到某个中间状态
- 用户关闭终端后想撤销 → 通过会话恢复 + 文件回滚实现

## 接下来

- **系统提示词** — 理解每轮对话前的上下文组装策略
- **上下文压缩** — 深入了解对话过长时的自动压缩机制
- **令牌预算** — 理解 token 预算管理和成本控制
