Claude Code官方文档Agent SDK会话

Claude Agent SDK 会话管理 - continue / resume / fork 用法

了解会话如何保持代理对话历史,以及何时使用 continue、resume 和 fork 返回到之前的运行。

· 阅读约 18 分钟

使用会话

会话如何保持代理对话历史记录,以及何时使用 continue、resume 和 fork 返回到之前的运行。

会话是 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。当您有多个会话(例如,多用户应用中每个用户一个)或想要返回到不是最近的会话时需要。

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

自动会话管理

两个 SDK 都提供了一个接口,可以跨调用为您跟踪会话状态,因此您无需手动传递 ID。将这些用于单个进程中的多轮对话。

Python:ClaudeSDKClient

ClaudeSDKClient 在内部处理会话 ID。每次调用 client.query() 都会自动继续同一会话。调用 client.receive_response() 以迭代当前查询的消息。客户端必须用作异步上下文管理器。

此示例针对同一 client 运行两个查询。第一个要求代理分析一个模块;第二个要求它重构该模块。因为两个调用都通过同一客户端实例进行,第二个查询具有来自第一个查询的完整上下文,无需任何显式 resume 或会话 ID:

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


def print_response(message):
    """Print only the human-readable parts of a 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:
        # First query: client captures the session ID internally
        await client.query("Analyze the auth module")
        async for message in client.receive_response():
            print_response(message)

        # Second query: automatically continues the same session
        await client.query("Now refactor it to use JWT")
        async for message in client.receive_response():
            print_response(message)


asyncio.run(main())

TypeScript:continue: true

稳定的 TypeScript SDK(整个文档中使用的 query() 函数,有时称为 V1)没有像 Python 的 ClaudeSDKClient 那样的会话保持客户端对象。相反,在每个后续 query() 调用上传递 continue: true,SDK 会在当前目录中选择最近的会话。无需 ID 跟踪。

此示例进行两个单独的 query() 调用。第一个创建一个新会话;第二个设置 continue: true,这告诉 SDK 在磁盘上查找并恢复最近的会话。代理具有来自第一个调用的完整上下文:

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

// First query: creates a new session
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);
  }
}

// Second query: continue: true resumes the most recent session
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(提供了带有 send / stream 模式的 createSession())已弃用。使用 V1 query() 函数和本页面上描述的会话选项。

将会话选项与 query() 一起使用

捕获会话 ID

Resume 和 fork 需要会话 ID。从结果消息上的 session_id 字段读取它(Python 中的 ResultMessage,TypeScript 中的 SDKResultMessage),该字段存在于每个结果上,无论成功还是错误。在 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 恢复

将会话 ID 传递给 resume 以返回到该特定会话。代理从会话中断的任何地方继续,具有完整的上下文。恢复的常见原因:

  • 跟进已完成的任务。 代理已经分析了某些内容;现在您希望它根据该分析采取行动,而无需重新读取文件。
  • 从限制中恢复。 第一次运行以 error_max_turnserror_max_budget_usd 结束;使用更高的限制恢复。
  • 重启您的进程。 您在关闭前捕获了 ID,并希望恢复对话。

此示例使用后续提示恢复捕获会话 ID 中的会话。因为您正在恢复,代理已经在上下文中具有先前的分析:

Python:

# Earlier session analyzed the code; now build on that analysis
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:

// Earlier session analyzed the code; now build on that analysis
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/<encoded-cwd>/*.jsonl 下,其中 <encoded-cwd> 是绝对工作目录,每个非字母数字字符都被替换为 -(所以 /Users/me/proj 变成 -Users-me-proj)。如果您的 resume 调用从不同的目录运行,SDK 会在错误的位置查找。会话文件也需要存在于当前机器上。

要在机器之间或在无服务器环境中恢复会话,请使用 SessionStore 适配器将记录镜像到共享存储。

Fork 以探索替代方案

Forking 创建一个新会话,从原始会话历史记录的副本开始,但从该点开始分支。fork 获得自己的会话 ID;原始的 ID 和历史记录保持不变。您最终会得到两个独立的会话,可以分别恢复。

ℹ️ Forking 分支对话历史记录,而不是文件系统。如果 forked 代理编辑文件,这些更改是真实的,对在同一目录中工作的任何会话都可见。要分支和还原文件更改,请使用文件检查点

此示例基于捕获会话 ID:您已经在 session_id 中分析了一个身份验证模块,并希望探索 OAuth2 而不丢失 JWT 焦点线程。第一个块 forks 会话并捕获 fork 的 ID(forked_id);第二个块恢复原始 session_id 以继续沿着 JWT 路径。您现在有两个会话 ID 指向两个单独的历史记录:

Python:

# Fork: branch from session_id into a new session
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  # The fork's ID, distinct from session_id
        if message.subtype == "success":
            print(message.result)

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

# Original session is untouched; resuming it continues the JWT thread
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: branch from sessionId into a new session
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; // The fork's ID, distinct from sessionId
  }
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

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

// Original session is untouched; resuming it continues the JWT thread
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 工作者、临时容器、无服务器),您有两个选项:

  • 移动会话文件。 从第一次运行中保持 ~/.claude/projects/<encoded-cwd>/<session-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()。使用它们按标签组织会话或给它们人类可读的标题。

相关资源


本文翻译自 Anthropic Claude Code 官方文档,最近一次同步:2025-05-01。