migration
managed-agents/migration
History
managed-agents/migration Changed · +49 / -32 lines
kill "${stream_pid}" 2>/dev/null || true ``` - ```bash CLI - { read -r _ agent_id; read -r _ agent_version; } < <(ant beta:agents create \ - --name "Task Runner" \ - --model claude-opus-5 \ - --tool '{type: agent_toolset_20260401}' \ - --transform '{id,version}' --format yaml) + <MultiFileExample language="cli" label="CLI"> + ```bash CLI + { read -r _ agent_id; read -r _ agent_version; } < <(ant beta:agents create \ + --transform '{id,version}' --format yaml < task-runner.agent.yaml) - session_id=$(ant beta:sessions create \ - --agent "{type: agent, id: $agent_id, version: $agent_version}" \ - --environment-id "$environment_id" \ - --transform id --raw-output) + 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) + # 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 + ant beta:sessions:events send \ + --session-id "$session_id" \ + --event "{type: user.message, content: [{type: text, text: \"$task\"}]}" \ + > /dev/null - # Wait for the session to go idle (grep exits at the first match) - grep -m1 -x 'session.status_idle' <&"$stream" > /dev/null - exec {stream}<&- - ``` + # Wait for the session to go idle (grep exits at the first match) + grep -m1 -x 'session.status_idle' <&"$stream" > /dev/null + exec {stream}<&- + ``` + <File filename="task-runner.agent.yaml"> + ```yaml + name: Task Runner + model: claude-opus-5 + tools: + - type: agent_toolset_20260401 + ``` + </File> + </MultiFileExample> + ```python Python agent = client.beta.agents.create( name="Task Runner",
--json "$(jq -n --argjson version "$AGENT_VERSION" '{version: $version, model: "claude-opus-5"}')" ``` - ```bash CLI - ant beta:agents update \ - --agent-id "$AGENT_ID" \ - --version "$AGENT_VERSION" \ - --model claude-opus-5 - ``` + <MultiFileExample language="cli" label="CLI"> + ```bash CLI + ant beta:agents update --agent-id "$AGENT_ID" < agent.yaml + ``` + + <File filename="agent.yaml"> + ```yaml + name: Task Runner + model: claude-opus-5 + system: You are a task automation agent. Complete the task you are given end to end. + tools: + - type: agent_toolset_20260401 + ``` + </File> + </MultiFileExample> ```python Python client.beta.agents.update(
managed-agents/migration Changed · +1 / -0 lines
* **System prompt and model:** Same fields, now on the agent definition. * **Custom tools:** Still declared with JSON Schema. Execution moves from inline handling to responding to `agent.custom_tool_use` events. See [Session event stream](https://platform.claude.com/docs/en/managed-agents/events-and-streaming). +* **Web search and web fetch settings:** Same `allowed_domains`, `blocked_domains`, `max_content_tokens`, and `user_location` fields, now set once on the `web_search` and `web_fetch` entries of the agent toolset's `configs` array instead of on every request. The `max_uses`, `citations`, and `cache_control` fields are not available. See [Restrict web search and web fetch domains](https://platform.claude.com/docs/en/managed-agents/tools#restrict-web-search-and-web-fetch-domains). * **Context:** You can still inject context through the system prompt, [file resources](https://platform.claude.com/docs/en/managed-agents/files), or [skills](https://platform.claude.com/docs/en/managed-agents/skills). ## From the Claude Agent SDK
managed-agents/migration First recorded · 1401 lines, first recorded
## From a Messages API agent loop ### What you stop managing ### Code comparison ### What you still control ## From the Claude Agent SDK ### What changes ### Code comparison ### Features that move to your client ## Migration checklist ## Migrating between model versions
The first capture of this source. The page was already there, and this is what it said.
---
title: Migration
url: https://platform.claude.com/docs/en/managed-agents/migration
description: Move an existing agent built on the Messages API or the Claude Agent SDK to Claude Managed Agents.
---
Claude Managed Agents replaces your hand-written agent loop with managed infrastructure. This page covers what changes when you migrate from a custom loop built on the [Messages API](https://platform.claude.com/docs/en/build-with-claude/working-with-messages) or from the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview).
<Note>
Managed Agents API requests require the `managed-agents-2026-04-01` beta header, except memory store endpoints, which use `agent-memory-2026-07-22` instead. The SDK sets the correct beta header automatically. See [Beta headers](https://platform.claude.com/docs/en/api/beta-headers#endpoint-specific-headers).
</Note>
## From a Messages API agent loop
If you built an agent by calling `messages.create` in a `while` loop, running tool calls yourself, and appending results to the conversation history, most of that code goes away.
### What you stop managing
| Before | After |
| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| You maintain the conversation history array and pass it back on every turn. | The session stores history server-side. Send events, receive events. |
| You iterate `tool_use` content blocks, run each tool, and loop back with `tool_result` messages. | Pre-built tools run inside the sandbox automatically. You only handle custom tools through `agent.custom_tool_use` events. |
| You provision your own sandbox for running agent-generated code. | The session sandbox handles code execution, file operations, and bash. |
| You decide when the loop is done. | The session emits `session.status_idle` when the agent has nothing more to do. |
### Code comparison
**Before** (Messages API loop, simplified):
<CodeGroup>
```python Python
messages = [{"role": "user", "content": task}]
while True:
response = client.messages.create(
model="claude-opus-5",
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,
}
],
}
)
```
```typescript TypeScript
const messages: Anthropic.MessageParam[] = [{ role: "user", content: task }];
while (true) {
const response = await client.messages.create({
model: "claude-opus-5",
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
}
]
});
}
}
}
```
```csharp C#
List<MessageParam> messages = [new() { Role = Role.User, Content = task }];
while (true)
{
var response = await client.Messages.Create(new()
{
Model = Model.ClaudeOpus5,
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 }]),
});
}
}
}
```
```go Go
messages := []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(task)),
}
for {
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
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),
))
}
}
}
```
```java Java
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_5)
.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());
});
}
}
```
```php PHP
$messages = [['role' => 'user', 'content' => $task]];
while (true) {
$response = $client->messages->create(
model: 'claude-opus-5',
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,
],
],
];
}
}
}
```
```ruby Ruby
messages = [{ role: "user", content: task }]
loop do
response = client.messages.create(
model: "claude-opus-5",
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
```
</CodeGroup>
**After** (Claude Managed Agents):
<CodeGroup>
```bash cURL
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-5",
"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
# Wait for the session to go idle. grep exits at the first match, and
# reading via process substitution means the shell doesn't wait for
# tail (a foreground `tail -f | grep -m1` pipeline would hang: tail
# only dies on its next write, which never comes once the stream is idle).
grep -m1 '"session.status_idle"' <(tail -f -n +1 "${stream_log}") > /dev/null
kill "${stream_pid}" 2>/dev/null || true
Cut at 300 lines.