迁移
将基于 Messages API 或 Claude Agent SDK 构建的现有代理迁移到 Claude 托管代理。
Claude 托管代理用托管基础设施取代了您手写的代理循环。本页介绍了从基于 Messages API 构建的自定义循环或从 Claude Agent SDK 迁移时的变化。
所有托管代理 API 请求都需要 managed-agents-2026-04-01 beta 头。SDK 会自动设置该 beta 头。
从 Messages API 代理循环迁移
如果您通过在 while 循环中调用 messages.create、自行执行工具调用并将结果附加到对话历史来构建代理,那么大部分代码都可以去掉。
您不再需要管理的内容
| 之前 | 之后 |
|---|---|
| 您维护对话历史数组并在每一轮中传递它。 | 会话在服务端存储历史。发送事件,接收事件。 |
您迭代 tool_use 内容块,运行每个工具,并用 tool_result 消息循环回来。 | 预构建工具在容器内自动运行。您只需通过 agent.custom_tool_use 事件处理自定义工具。 |
| 您自己配置沙箱来运行代理生成的代码。 | 会话容器处理代码执行、文件操作和 bash。 |
| 您决定循环何时结束。 | 当代理没有更多任务时,会话发出 session.status_idle。 |
代码对比
之前(Messages API 循环,简化版):
messages = [{"role": "user", "content": task}]
while True:
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
break
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
messages.append(
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
}
],
}
)
const messages: Anthropic.MessageParam[] = [{ role: "user", content: task }];
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages,
tools
});
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason === "end_turn") {
break;
}
for (const block of response.content) {
if (block.type === "tool_use") {
const result = executeTool(block.name, block.input);
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: block.id,
content: result
}
]
});
}
}
}
List<MessageParam> messages = [new() { Role = Role.User, Content = task }];
while (true)
{
var response = await client.Messages.Create(new()
{
Model = Model.ClaudeOpus4_7,
MaxTokens = 1024,
Messages = messages,
Tools = tools,
});
messages.Add(new()
{
Role = Role.Assistant,
Content = new([.. response.Content.Select(block => new ContentBlockParam(block.Json))]),
});
if (response.StopReason == StopReason.EndTurn)
{
break;
}
foreach (var block in response.Content)
{
if (block.Value is ToolUseBlock toolUse)
{
var result = ExecuteTool(toolUse.Name, toolUse.Input);
messages.Add(new()
{
Role = Role.User,
Content = new([new ToolResultBlockParam { ToolUseID = toolUse.ID, Content = result }]),
});
}
}
}
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(task)),
}
for {
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus4_7,
MaxTokens: 1024,
Messages: messages,
Tools: tools,
})
if err != nil {
log.Fatal(err)
}
messages = append(messages, response.ToParam())
if response.StopReason == anthropic.StopReasonEndTurn {
break
}
for _, block := range response.Content {
if toolUse, ok := block.AsAny().(anthropic.ToolUseBlock); ok {
result := executeTool(toolUse.Name, toolUse.Input)
messages = append(messages, anthropic.NewUserMessage(
anthropic.NewToolResultBlock(toolUse.ID, result, false),
))
}
}
}
var messages = new ArrayList<MessageParam>();
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.content(task)
.build());
while (true) {
var response = client.messages().create(MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_7)
.maxTokens(1024)
.messages(messages)
.tools(tools)
.build());
messages.add(response.toParam());
if (StopReason.END_TURN.equals(response.stopReason().orElse(null))) {
break;
}
for (var block : response.content()) {
block.toolUse().ifPresent(toolUse -> {
var result = executeTool(toolUse.name(), toolUse._input());
messages.add(MessageParam.builder()
.role(MessageParam.Role.USER)
.contentOfBlockParams(List.of(
ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
.toolUseId(toolUse.id())
.content(result)
.build())))
.build());
});
}
}
$messages = [['role' => 'user', 'content' => $task]];
while (true) {
$response = $client->messages->create(
model: 'claude-opus-4-7',
maxTokens: 1024,
messages: $messages,
tools: $tools,
);
$messages[] = ['role' => 'assistant', 'content' => $response->content];
if ($response->stopReason === 'end_turn') {
break;
}
foreach ($response->content as $block) {
if ($block->type === 'tool_use') {
$result = executeTool($block->name, $block->input);
$messages[] = [
'role' => 'user',
'content' => [
[
'type' => 'tool_result',
'tool_use_id' => $block->id,
'content' => $result,
],
],
];
}
}
}
messages = [{ role: "user", content: task }]
loop do
response = client.messages.create(
model: "claude-opus-4-7",
max_tokens: 1024,
messages: messages,
tools: tools
)
messages << { role: "assistant", content: response.content }
break if response.stop_reason == :end_turn
response.content.each do |block|
next unless block.type == :tool_use
result = execute_tool(block.name, block.input)
messages << {
role: "user",
content: [
{
type: "tool_result",
tool_use_id: block.id,
content: result
}
]
}
end
end
之后(Claude 托管代理):
agent=$(
curl --fail-with-body -sS "https://api.anthropic.com/v1/agents?beta=true" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json '{
"name": "Task Runner",
"model": "claude-opus-4-7",
"tools": [{"type": "agent_toolset_20260401"}]
}'
)
agent_id=$(jq -r '.id' <<< "${agent}")
session_id=$(
curl --fail-with-body -sS "https://api.anthropic.com/v1/sessions?beta=true" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json "$(jq -n --argjson a "${agent}" --arg env "${environment_id}" \
'{agent: {type: "agent", id: $a.id, version: $a.version}, environment_id: $env}')" \
| jq -r '.id'
)
# Open the SSE stream in the background, then send the user message.
stream_log=$(mktemp)
curl --fail-with-body -sS -N \
"https://api.anthropic.com/v1/sessions/${session_id}/events/stream?beta=true" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
> "${stream_log}" &
stream_pid=$!
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/sessions/${session_id}/events?beta=true" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json "$(jq -n --arg text "${task}" \
'{events: [{type: "user.message", content: [{type: "text", text: $text}]}]}')" \
> /dev/null
# Read events until the session goes idle.
while IFS= read -r line; do
[[ ${line} == data:* ]] || continue
event_type=$(jq -r '.type // empty' 2>/dev/null <<< "${line#data: }" || true)
[[ ${event_type} == "session.status_idle" ]] && break
done < <(tail -f -n +1 "${stream_log}")
kill "${stream_pid}" 2>/dev/null || true
{ read -r _ agent_id; read -r _ agent_version; } < <(ant beta:agents create \
--name "Task Runner" \
--model claude-opus-4-7 \
--tool '{type: agent_toolset_20260401}' \
--transform '{id,version}' --format yaml)
session_id=$(ant beta:sessions create \
--agent "{type: agent, id: $agent_id, version: $agent_version}" \
--environment-id "$environment_id" \
--transform id --raw-output)
# Open the stream first, then send the user message
exec {stream}< <(ant beta:sessions:events stream \
--session-id "$session_id" \
--transform type --raw-output)
ant beta:sessions:events send \
--session-id "$session_id" \
--event "{type: user.message, content: [{type: text, text: \"$task\"}]}" \
> /dev/null
while IFS= read -r -u "$stream" type; do
[[ $type == session.status_idle ]] && break
done
exec {stream}<&-
agent = client.beta.agents.create(
name="Task Runner",
model="claude-opus-4-7",
tools=[{"type": "agent_toolset_20260401"}],
)
session = client.beta.sessions.create(
agent={"type": "agent", "id": agent.id, "version": agent.version},
environment_id=environment.id,
)
with client.beta.sessions.events.stream(session.id) as stream:
client.beta.sessions.events.send(
session.id,
events=[{"type": "user.message", "content": [{"type": "text", "text": task}]}],
)
for event in stream:
if event.type == "session.status_idle":
break
const agent = await client.beta.agents.create({
name: "Task Runner",
model: "claude-opus-4-7",
tools: [{ type: "agent_toolset_20260401" }]
});
const session = await client.beta.sessions.create({
agent: { type: "agent", id: agent.id, version: agent.version },
environment_id: environment.id
});
const stream = await client.beta.sessions.events.stream(session.id);
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: task }]
}
]
});
for await (const event of stream) {
if (event.type === "session.status_idle") {
break;
}
}
var agent = await client.Beta.Agents.Create(new()
{
Name = "Task Runner",
Model = BetaManagedAgentsModel.ClaudeOpus4_7,
Tools =
[
new BetaManagedAgentsAgentToolset20260401Params
{
Type = "agent_toolset_20260401",
},
],
});
var session = await client.Beta.Sessions.Create(new()
{
Agent = new BetaManagedAgentsAgentParams
{
Type = "agent",
ID = agent.ID,
Version = agent.Version,
},
EnvironmentID = environment.ID,
});
var stream = client.Beta.Sessions.Events.StreamStreaming(session.ID);
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = "user.message",
Content = [new BetaManagedAgentsTextBlock { Type = "text", Text = task }],
},
],
});
await foreach (var streamEvent in stream)
{
if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
break;
}
}
agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
Name: "Task Runner",
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: anthropic.BetaManagedAgentsModelClaudeOpus4_7,
},
Tools: []anthropic.BetaAgentNewParamsToolUnion{{
OfAgentToolset20260401: &anthropic.BetaManagedAgentsAgentToolset20260401Params{
Type: anthropic.BetaManagedAgentsAgentToolset20260401ParamsTypeAgentToolset20260401,
},
}},
})
if err != nil {
log.Fatal(err)
}
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
Agent: anthropic.BetaSessionNewParamsAgentUnion{
OfBetaManagedAgentsAgents: &anthropic.BetaManagedAgentsAgentParams{
Type: anthropic.BetaManagedAgentsAgentParamsTypeAgent,
ID: agent.ID,
Version: anthropic.Int(agent.Version),
},
},
EnvironmentID: environment.ID,
})
if err != nil {
log.Fatal(err)
}
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{})
defer stream.Close()
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &anthropic.BetaManagedAgentsTextBlockParam{
Type: anthropic.BetaManagedAgentsTextBlockTypeText,
Text: task,
},
}},
},
}},
})
if err != nil {
log.Fatal(err)
}
for stream.Next() {
event := stream.Current()
if event.Type == "session.status_idle" {
break
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
var agent = client.beta().agents().create(
AgentCreateParams.builder()
.name("Task Runner")
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.addTool(
BetaManagedAgentsAgentToolset20260401Params.builder()
.type(BetaManagedAgentsAgentToolset20260401Params.Type.AGENT_TOOLSET_20260401)
.build()
)
.build()
);
var session = client.beta().sessions().create(
SessionCreateParams.builder()
.agent(
BetaManagedAgentsAgentParams.builder()
.type(BetaManagedAgentsAgentParams.Type.AGENT)
.id(agent.id())
.version(agent.version())
.build()
)
.environmentId(environment.id())
.build()
);
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(
BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent(task)
.build()
)
.build()
);
stream.stream()
.takeWhile(event -> !event.isSessionStatusIdle())
.forEach(_ -> {});
}
$agent = $client->beta->agents->create(
name: 'Task Runner',
model: 'claude-opus-4-7',
tools: [
BetaManagedAgentsAgentToolset20260401Params::with(
type: 'agent_toolset_20260401',
),
],
);
$session = $client->beta->sessions->create(
agent: BetaManagedAgentsAgentParams::with(
type: 'agent',
id: $agent->id,
version: $agent->version,
),
environmentID: $environment->id,
);
$stream = $client->beta->sessions->events->streamStream($session->id);
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [['type' => 'text', 'text' => $task]],
],
],
);
foreach ($stream as $event) {
if ($event->type === 'session.status_idle') {
break;
}
}
agent = client.beta.agents.create(
name: "Task Runner",
model: "claude-opus-4-7",
tools: [{type: "agent_toolset_20260401"}]
)
session = client.beta.sessions.create(
agent: {type: "agent", id: agent.id, version: agent.version},
environment_id: environment.id
)
stream = client.beta.sessions.events.stream_events(session.id)
client.beta.sessions.events.send_(
session.id,
events: [{type: "user.message", content: [{type: "text", text: task}]}]
)
stream.each do
break if it.type == :"session.status_idle"
end
您仍然控制的内容
- **系统提示和模型:**相同的字段,现在在代理定义上。
- **自定义工具:**仍然用 JSON Schema 声明。执行从内联处理移至响应
agent.custom_tool_use事件。请参阅会话事件流。 - **上下文:**您仍然可以通过系统提示、文件资源或技能注入上下文。
从 Claude Agent SDK 迁移
如果您使用 Claude Agent SDK 构建,您已经在使用代理、工具和会话作为概念。区别在于它们的运行位置:SDK 在您操作的进程中执行,而托管代理在 Anthropic 的基础设施中运行。迁移的大部分内容是将 SDK 配置对象映射到其 API 端的等价物。
变化内容
| Agent SDK | 托管代理 |
|---|---|
每次运行时构造 ClaudeAgentOptions(...) | client.beta.agents.create(...) 一次;代理在服务端持久化和版本化。请参阅代理设置。 |
async with ClaudeSDKClient(...) 或 query(...) | client.beta.sessions.create(...) 然后发送和接收事件。 |
SDK 自动分派的 @tool 装饰函数 | 在代理上声明为 {"type": "custom", ...};您的客户端处理 agent.custom_tool_use 事件并回复 user.custom_tool_result。请参阅工具。 |
| 内置工具在您的进程中针对您的文件系统运行 | {"type": "agent_toolset_20260401"} 在会话容器内针对 /workspace 运行相同的工具。 |
cwd、add_dirs 指向本地路径 | 上传或挂载文件作为会话资源。 |
system_prompt 和 CLAUDE.md 层次结构 | 代理上的单个 system 字符串。每次更新都会产生新的服务端版本;将会话固定到特定版本以在不部署的情况下进行推广或回滚。请参阅代理设置。 |
mcp_servers 在一个地方配置和认证 | 在代理上声明服务器;通过会话上的保险库提供凭据。 |
permission_mode、can_use_tool | 每个工具的 permission_policy;为 always_ask 工具发送 user.tool_confirmation 事件。 |
代码对比
之前(Agent SDK):
from claude_agent_sdk import (
ClaudeAgentOptions,
ClaudeSDKClient,
create_sdk_mcp_server,
tool,
)
@tool("get_weather", "Get the current weather for a city.", {"city": str})
async def get_weather(args: dict) -> dict:
return {"content": [{"type": "text", "text": f"{args['city']}: 18°C, clear"}]}
options = ClaudeAgentOptions(
model="claude-opus-4-7",
system_prompt="You are a concise weather assistant.",
mcp_servers={
"weather": create_sdk_mcp_server("weather", "1.0", tools=[get_weather])
},
)
async with ClaudeSDKClient(options=options) as agent:
await agent.query("What's the weather in Tokyo?")
async for msg in agent.receive_response():
print(msg)
之后(托管代理):
from anthropic import Anthropic
client = Anthropic()
agent = client.beta.agents.create(
name="weather-agent",
model="claude-opus-4-7",
system="You are a concise weather assistant.",
tools=[
{
"type": "custom",
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}
],
)
environment = client.beta.environments.create(
name="weather-env",
config={"type": "cloud", "networking": {"type": "unrestricted"}},
)
session = client.beta.sessions.create(
agent={"type": "agent", "id": agent.id, "version": agent.version},
environment_id=environment.id,
)
def get_weather(city: str) -> str:
return f"\{city\}: 18°C, clear"
with client.beta.sessions.events.stream(session.id) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "What's the weather in Tokyo?"}],
}
],
)
for ev in stream:
if ev.type == "agent.message":
print("".join(block.text for block in ev.content if block.type == "text"))
elif ev.type == "agent.custom_tool_use":
result = get_weather(**ev.input)
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.custom_tool_result",
"custom_tool_use_id": ev.id,
"content": [{"type": "text", "text": result}],
}
],
)
elif (
ev.type == "session.status_idle"
and ev.stop_reason
and ev.stop_reason.type == "end_turn"
):
break
代理和环境只需创建一次,可在会话之间重用。工具函数仍然在您的进程中运行;区别在于您读取 agent.custom_tool_use 事件并显式发送结果,而不是由 SDK 为您分派。
移至客户端的功能
Anthropic 运行代理循环的权衡是,SDK 自动处理的一些事情变成了您客户端的责任。
| SDK 功能 | 托管代理方式 |
|---|---|
| 计划模式 | 先运行一个仅计划的会话,然后运行第二个会话来执行计划。 |
| 输出样式、斜杠命令 | 在发送 user.message 之前或接收 agent.message 之后在客户端中应用。 |
PreToolUse / PostToolUse 钩子 | 您的客户端在响应之前已经看到了每个 agent.custom_tool_use 事件;将逻辑放在那里。对于内置工具,使用 permission_policy: always_ask。 |
max_turns | 在客户端侧计数轮次。 |
迁移清单
- 创建环境,配置代理所需的网络和运行时。
- 将您的系统提示和工具选择移植到代理定义。
- 用
sessions.create和sessions.events.stream替换您的循环。 - 对于代理读取的任何本地文件,通过 Files API 上传并挂载为
resources。 - 对于任何自定义工具处理程序,将执行移至您的事件循环中,作为对
agent.custom_tool_use事件的响应。 - 在将生产流量指向新流程之前,使用测试会话进行验证。
在模型版本之间迁移
当新的 Claude 模型发布时,迁移 Claude 托管代理集成通常只需更改一个字段:更新代理定义上的 model,更改将在您创建的下一个会话中生效。
curl -sS --fail-with-body "https://api.anthropic.com/v1/agents/$AGENT_ID?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
--json "$(jq -n --argjson version "$AGENT_VERSION" '{version: $version, model: "claude-opus-4-7"}')"
ant beta:agents update \
--agent-id "$AGENT_ID" \
--version "$AGENT_VERSION" \
--model claude-opus-4-7
client.beta.agents.update(
agent.id,
version=agent.version,
model="claude-opus-4-7",
)
await client.beta.agents.update(agent.id, {
version: agent.version,
model: "claude-opus-4-7"
});
await client.Beta.Agents.Update(agent.ID, new()
{
Version = agent.Version,
Model = BetaManagedAgentsModel.ClaudeOpus4_7,
});
_, err = client.Beta.Agents.Update(ctx, agent.ID, anthropic.BetaAgentUpdateParams{
Version: agent.Version,
Model: anthropic.BetaManagedAgentsModelConfigParams{
ID: anthropic.BetaManagedAgentsModelClaudeOpus4_7,
},
})
if err != nil {
panic(err)
}
client.beta().agents().update(
agent.id(),
AgentUpdateParams.builder()
.version(agent.version())
.model(BetaManagedAgentsModel.CLAUDE_OPUS_4_7)
.build()
);
$client->beta->agents->update(
$agent->id,
version: $agent->version,
model: 'claude-opus-4-7',
);
client.beta.agents.update(
agent.id,
version: agent.version,
model: "claude-opus-4-7"
)
Messages API 迁移指南中记录的大多数模型级行为更改不需要您采取行动:
- 请求参数更改(
max_tokens默认值、thinking配置)由 Claude 托管代理运行时处理。这些字段不会在代理定义上公开。 - 助手消息预填充在基于事件的会话模型中不存在,因此在较新模型中删除它是一个无操作。
- 工具参数 JSON 转义在您接收
agent.custom_tool_use事件之前由运行时解析。您看到的是结构化数据,而不是原始字符串。
Messages API 指南中的行为描述(模型的不同行为)仍然适用。迁移步骤(如何更改请求代码)则不适用。