Skip to content

Agent SDK 自动将每次 query() 的对话历史写入磁盘,你可以在后续调用中使用 continue(自动续接最近会话)、resume(按 ID 恢复指定会话)或 fork(从历史分叉出新会话)来保持上下文。捕获 session_id 在结果消息中,通过 ClaudeSDKClient(Python)或 continue: true(TypeScript)实现进程内的多轮对话。会话文件存储在 ~/.claude/projects/<编码后的cwd>/ 下,跨主机恢复需要移动文件或改用应用状态传递。

Claude Code 会话管理:resume、fork 与跨主机恢复

会话是 Agent SDK 在代理工作过程中积累的对话历史,包含你的提示词、每一次工具调用、工具结果以及响应。SDK 自动将会话写入磁盘,以便后续恢复。

恢复会话意味着代理拥有之前的全部上下文:它已经读过的文件、已经完成的分析、已经做出的决策。你可以追问问题、从中断中恢复,或者分支到另一个方向尝试不同方案。

会话持久化的是对话,而不是文件系统。要快照和回滚代理对文件所做的更改,请使用文件检查点

本指南涵盖如何为你的应用选择合适的方式、SDK 自动跟踪会话的接口、如何手动获取会话 ID 并使用 resumefork,以及跨主机恢复会话的注意事项。

选择会话管理方式

你需要多少会话管理取决于应用的场景。当你发送多个需要共享上下文的提示词时,会话管理就会发挥作用。在单次 query() 调用内,代理已经会自动进行它所需的多次回合,权限提示和 AskUserQuestion 会在循环内处理(不会结束调用)。

你正在构建的场景推荐做法
一次性任务:单提示,无后续什么都不用做。一个 query() 调用即可。
同一进程内的多轮对话使用 ClaudeSDKClient (Python) 或 continue: true (TypeScript)。SDK 自动跟踪会话,无需处理 ID。
进程重启后接续之前的工作设置 continue_conversation=True (Python) / continue: true (TypeScript)。自动恢复当前目录下最近的会话,无需 ID。
恢复某个特定的历史会话(不是最近的)捕获会话 ID 并传给 resume
尝试另一种方向而不丢失原始会话使用 fork 分支。
无状态任务,不希望写入磁盘(仅 TypeScript)设置 persistSession: false。会话仅在调用期间存在于内存中。Python 始终会持久化到磁盘。

continue、resume 和 fork 的区别

continue、resume 和 fork 都是 query() 的可选字段(Python 的 ClaudeAgentOptions,TypeScript 的 Options)。

continueresume 都会接续现有会话并添加新内容,区别在于如何找到该会话:

  • continue 会自动找到当前目录下最近的会话。你无需跟踪任何内容。适用于你的应用每次只运行一个对话的场景。
  • resume 需要一个具体的会话 ID。你需要自己跟踪这个 ID。当你有多条会话(例如多用户应用中每个用户一条)或者想恢复的不是最近的会话时,必须使用 resume。

fork 则不同:它会创建一个新会话,新会话从原始会话的历史副本开始。原始会话保持不变。使用 fork 可以尝试不同的方向,同时保留回到原始会话的选项。

自动会话管理

两个 SDK 都提供了在多次调用之间自动跟踪会话状态的接口,无需手动传递 ID。适用于同一进程内的多轮对话。

Python:ClaudeSDKClient

ClaudeSDKClient 内部处理会话 ID。每次调用 client.query() 会自动延续同一个会话。通过 client.receive_response() 迭代当前查询的消息。客户端通常作为异步上下文管理器使用。

以下示例对同一个 client 运行两次查询。第一次让代理分析一个模块,第二次让代理重构该模块。因为两次调用使用同一个客户端实例,第二次查询拥有第一次的完整上下文,无需显式 resume 或会话 ID:

python
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    ResultMessage,
    TextBlock,
)


def print_response(message):
    """打印消息中人类可读的部分。"""
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, TextBlock):
                print(block.text)
    elif isinstance(message, ResultMessage):
        cost = (
            f"${message.total_cost_usd:.4f}"
            if message.total_cost_usd is not None
            else "N/A"
        )
        print(f"[done: {message.subtype}, cost: {cost}]")


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Edit", "Glob", "Grep"],
    )

    async with ClaudeSDKClient(options=options) as client:
        # 第一次查询:客户端内部捕获会话 ID
        await client.query("Analyze the auth module")
        async for message in client.receive_response():
            print_response(message)

        # 第二次查询:自动延续同一个会话
        await client.query("Now refactor it to use JWT")
        async for message in client.receive_response():
            print_response(message)


asyncio.run(main())

关于何时使用 ClaudeSDKClient 与独立 query() 函数的详细信息,请参阅 Python SDK 参考

TypeScript:continue: true

TypeScript SDK 没有类似 Python 的 ClaudeSDKClient 这样的会话持有对象。取而代之的是,在后续的每个 query() 调用中传入 continue: true,SDK 会拾取当前目录下最近的会话。无需跟踪 ID。

以下示例进行两次独立的 query() 调用。第一次创建新会话;第二次设置 continue: true,告诉 SDK 找到并恢复磁盘上最近的会话。代理拥有第一次调用的完整上下文:

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

// 第一次查询:创建新会话
for await (const message of query({
  prompt: "Analyze the auth module",
  options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

// 第二次查询:continue: true 恢复最近的会话
for await (const message of query({
  prompt: "Now refactor it to use JWT",
  options: {
    continue: true,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

实验性的 V2 会话 API(提供 createSession()send / stream 模式)已在 TypeScript Agent SDK 0.3.142 中移除。请使用本页描述的 query() 函数和会话选项。

query() 中使用会话选项

捕获会话 ID

resume 和 fork 都需要会话 ID。从结果消息(Python 的 ResultMessage,TypeScript 的 SDKResultMessage)的 session_id 字段读取,该字段在每次结果中都会出现,无论成功还是错误。在 TypeScript 中,会话 ID 也可更早地从初始 SystemMessage 的直接字段获得;在 Python 中它嵌套在 SystemMessage.data 内部。

python
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    session_id = None

    async for message in query(
        prompt="Analyze the auth module and suggest improvements",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Glob", "Grep"],
        ),
    ):
        if isinstance(message, ResultMessage):
            session_id = message.session_id
            if message.subtype == "success":
                print(message.result)

    print(f"Session ID: {session_id}")
    return session_id


session_id = asyncio.run(main())
typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

let sessionId: string | undefined;

for await (const message of query({
  prompt: "Analyze the auth module and suggest improvements",
  options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
  if (message.type === "result") {
    sessionId = message.session_id;
    if (message.subtype === "success") {
      console.log(message.result);
    }
  }
}

console.log(`Session ID: ${sessionId}`);

通过 ID 恢复会话(resume)

将会话 ID 传给 resume 可以恢复那个特定的会话。代理能从上次中断的地方继续,拥有完整上下文。常见的恢复场景:

  • 对已完成的任务进行后续跟进。 代理已经分析过某内容,现在你需要它基于分析结果行动,无需重新读取文件。
  • 从限制中恢复。 第一次运行以 error_max_turnserror_max_budget_usd 结束(参见处理结果),使用更高的限制恢复。
  • 重启进程。 你在关闭前捕获了会话 ID,现在想恢复对话。

以下示例从捕获会话 ID 恢复会话,并发出后续提示。因为是恢复,代理已经拥有之前分析过的上下文:

python
# 之前的会话分析过代码;现在基于该分析继续
async for message in query(
    prompt="Now implement the refactoring you suggested",
    options=ClaudeAgentOptions(
        resume=session_id,
        allowed_tools=["Read", "Edit", "Write", "Glob", "Grep"],
    ),
):
    if isinstance(message, ResultMessage) and message.subtype == "success":
        print(message.result)
typescript
// 之前的会话分析过代码;现在基于该分析继续
for await (const message of query({
  prompt: "Now implement the refactoring you suggested",
  options: {
    resume: sessionId,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

如果 resume 调用返回的是全新的会话而非预期的历史,最常见的原因是 cwd(当前工作目录)不匹配。会话存储在 ~/.claude/projects/<编码后的cwd>/*.jsonl 下,其中 <编码后的cwd> 是绝对工作目录的每个非字母数字字符被替换为 - 的结果(例如 /Users/me/proj 变成 -Users-me-proj)。如果你的 resume 调用从不同的目录运行,SDK 会在错误的地方查找。会话文件也必须在当前机器上存在。

要在不同机器或 serverless 环境中恢复会话,请使用 SessionStore 适配器 将会话转录镜像到共享存储。

分支探索(fork)

Fork 会创建一个新会话,该会话从原始会话的历史副本开始,但从此处开始分叉。新会话有自己的会话 ID;原始会话的 ID 和历史保持不变。最终你会得到两条可以分别恢复的独立会话。

Fork 分支的是对话历史,而不是文件系统。如果分叉后的代理编辑了文件,这些更改是真实的,任何在该目录下工作的会话都能看到。要分支并回滚文件更改,请使用文件检查点

以下示例基于捕获会话 ID:你已经在 session_id 中分析了 auth 模块,现在想探索 OAuth2 而不丢失 JWT 方向上的线程。第一个代码块分叉会话并捕获新会话的 ID(forked_id);第二个代码块恢复原始 session_id 继续 JWT 方向。你现在有两个会话 ID,指向两条独立的历史:

python
# Fork:从 session_id 分支到新会话
forked_id = None
async for message in query(
    prompt="Instead of JWT, implement OAuth2 for the auth module",
    options=ClaudeAgentOptions(
        resume=session_id,
        fork_session=True,
    ),
):
    if isinstance(message, ResultMessage):
        forked_id = message.session_id  # 新分叉的 ID,与 session_id 不同
        if message.subtype == "success":
            print(message.result)

print(f"Forked session: {forked_id}")

# 原始会话不变;恢复它将继续 JWT 线程
async for message in query(
    prompt="Continue with the JWT approach",
    options=ClaudeAgentOptions(resume=session_id),
):
    if isinstance(message, ResultMessage) and message.subtype == "success":
        print(message.result)
typescript
// Fork:从 sessionId 分支到新会话
let forkedId: string | undefined;

for await (const message of query({
  prompt: "Instead of JWT, implement OAuth2 for the auth module",
  options: {
    resume: sessionId,
    forkSession: true
  }
})) {
  if (message.type === "system" && message.subtype === "init") {
    forkedId = message.session_id; // 新分叉的 ID,与 sessionId 不同
  }
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

console.log(`Forked session: ${forkedId}`);

// 原始会话不变;恢复它将继续 JWT 线程
for await (const message of query({
  prompt: "Continue with the JWT approach",
  options: { resume: sessionId }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

跨主机恢复

会话文件是创建它的机器本地的。要在另一台主机(CI 工作节点、临时容器、serverless)上恢复会话,你有两种选择:

  • 移动会话文件。 将第一次运行生成的 ~/.claude/projects/<编码后的cwd>/<会话ID>.jsonl 持久化,然后在调用 resume 前将其恢复到新主机上的相同路径。cwd 必须匹配。
  • 不依赖会话恢复。 将你需要的结果(分析输出、决策、文件差异)作为应用状态捕获,然后将其传入新会话的提示词中。这通常比传输会话转录文件更健壮。

两个 SDK 都提供了枚举磁盘上会话并读取其消息的函数:TypeScript 的 listSessions()getSessionMessages(),Python 的 list_sessions()get_session_messages()。可以用它们构建自定义会话选择器、清理逻辑或转录查看器。

两个 SDK 还提供了查看和修改单个会话的函数:Python 的 get_session_info()rename_session()tag_session(),TypeScript 的 getSessionInfo()renameSession()tagSession()。可以用它们按标签组织会话或赋予人类可读的标题。

相关资源

常见问题

怎么恢复之前的会话?

使用 resume 参数传入会话 ID。会话 ID 可以从上一次 query() 的结果消息的 session_id 字段获取。确保当前工作目录与创建会话时相同,否则 SDK 会在不同路径下查找文件。

会话文件存在哪里?

会话文件存储在 ~/.claude/projects/<编码后的cwd>/<会话ID>.jsonl。其中 <编码后的cwd> 是将绝对工作目录中的非字母数字字符替换为 - 的结果,例如 /Users/me/proj 变成 -Users-me-proj

fork 和 resume 有什么区别?

resume 接续现有会话,新内容追加到原会话中;fork 创建原始会话的副本并在此基础上继续,原会话保持不变。fork 适用于你想尝试不同方向但保留回头路的情况,而 resume 用于正常接续。