마이그레이션

마이그레이션 (Migration)

Claude Managed Agents는 여러분이 직접 작성한 에이전트 루프를 관리형 인프라로 대체해요. 이 페이지는 Messages API 기반의 커스텀 루프에서 또는 Claude Agent SDK에서 마이그레이션할 때 무엇이 바뀌는지 다뤄요.

출처: 문서

본문

Claude Managed Agents는 여러분이 직접 작성한 에이전트 루프를 관리형 인프라로 대체해요. 이 페이지는 Messages API 기반의 커스텀 루프에서 또는 Claude Agent SDK에서 마이그레이션할 때 무엇이 바뀌는지 다뤄요.

Messages API 에이전트 루프에서 (From a Messages API agent loop)

while 루프 안에서 messages.create를 호출하고, 도구 호출을 직접 실행하고, 결과를 대화 기록에 추가하는 방식으로 에이전트를 만들었다면 그 코드의 대부분이 사라져요.

더 이상 관리하지 않게 되는 것 (What you stop managing)

이전 (Before) 이후 (After)
대화 기록 배열을 유지하고 매 턴마다 다시 전달했어요. 세션이 서버 측에 기록을 저장해요. 이벤트를 보내고, 이벤트를 받아요.
tool_use 콘텐츠 블록을 순회하며 각 도구를 실행하고 tool_result 메시지로 루프를 돌렸어요. 사전 구축된 도구가 샌드박스 안에서 자동으로 실행돼요. 커스텀 도구만 agent.custom_tool_use 이벤트로 처리하면 돼요.
에이전트가 생성한 코드를 실행하기 위해 자체 샌드박스를 프로비저닝했어요. 세션 샌드박스가 코드 실행, 파일 작업, bash를 처리해요.
루프가 끝났을 때를 직접 결정했어요. 에이전트가 더 할 일이 없을 때 세션이 session.status_idle을 발행해요.

코드 비교 (Code comparison)

이전 (Messages API 루프, 단순화):

```python Python messages = [{"role": "user", "content": task}] while True: response = client.messages.create( model="claude-opus-5-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, } ], } ) ```
const messages: Anthropic.MessageParam[] = [{ role: "user", content: task }];
while (true) {
  const response = await client.messages.create({
    model: "claude-opus-5-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
          }
        ]
      });
    }
  }
}
List<MessageParam> messages = [new() { Role = Role.User, Content = task }];
while (true)
{
    var response = await client.Messages.Create(new()
    {
        Model = Model.ClaudeOpus5_5,
        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.ModelClaudeOpus5_5,
		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_5_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());
        });
    }
}
$messages = [['role' => 'user', 'content' => $task]];
while (true) {
    $response = $client->messages->create(
        model: 'claude-opus-5-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,
                    ],
                ],
            ];
        }
    }
}
messages = [{ role: "user", content: task }]
loop do
  response = client.messages.create(
    model: "claude-opus-5-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

이후 (Claude Managed Agents):

```bash cURL agent=$( curl --fail-with-body -sS "https://api.anthropic.com/v1/agents?beta=true" \ -H "x-api-key: ${ANTH...EY}" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" \ --json '{ "name": "Task Runner", "model": "claude-opus-5-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: ${ANTH...EY}"
-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: ${ANTH...EY}"
-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: ${ANTH...EY}"
-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


<MultiFileExample language="cli" label="CLI">
  ```bash CLI
  ant apply agent.md

  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

  # 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="agent.md">
    ```markdown
    ---
    name: Task Runner
    model: claude-opus-5-5
    tools:
      - type: agent_toolset_20260401
    ---
    ```
  </File>
</MultiFileExample>

```python Python
agent = client.beta.agents.create(
    name="Task Runner",
    model="claude-opus-5-5",
    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-5-5",
  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.ClaudeOpus5_5,
    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.BetaManagedAgentsModelClaudeOpus5_5,
		},
		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_5_5)
            .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-5-5',
    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-5-5",
  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

여전히 제어하는 것 (What you still control)

  • 시스템 프롬프트와 모델: 동일한 필드, 이제 에이전트 정의에 있어요.
  • 커스텀 도구: 여전히 JSON Schema로 선언해요. 실행이 인라인 처리에서 agent.custom_tool_use 이벤트에 응답하는 것으로 옮겨요. 세션 이벤트 스트림을 참고하세요.
  • 웹 검색과 웹 가져오기 설정: 동일한 allowed_domains, blocked_domains, max_content_tokens, user_location 필드, 이제는 매 요청마다가 아니라 에이전트 도구세트의 configs 배열에 있는 web_searchweb_fetch 항목에 한 번 설정해요. max_uses, citations, cache_control 필드는 사용할 수 없어요. 웹 검색과 웹 가져오기 도메인 제한하기를 참고하세요.
  • 컨텍스트: 시스템 프롬프트, 파일 리소스, 또는 스킬을 통해 컨텍스트를 주입할 수 있어요.

Claude Agent SDK에서 (From the Claude Agent SDK)

Claude Agent SDK로 구축했다면 이미 에이전트, 도구, 세션을 개념으로 다루고 있어요. 차이는 그것들이 어디서 실행되는가예요: SDK는 여러분이 운영하는 프로세스에서 실행되고, Managed Agents는 Anthropic의 인프라에서 실행돼요. 마이그레이션의 대부분은 SDK 구성 객체를 API 측 등가물로 매핑하는 것이에요.

무엇이 바뀌나 (What changes)

Agent SDK Managed Agents
매 실행마다 구성되는 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_promptCLAUDE.md 계층 구조 에이전트에 단일 system 문자열. 에이전트를 바꾸는 각 업데이트는 새 서버 측 버전을 만들어요. 배포 없이 승격하거나 롤백하려면 세션을 특정 버전에 고정하세요. 에이전트 설정을 참고하세요.
mcp_servers를 한 곳에서 구성·인증 에이전트에 서버를 선언하고, 세션의 볼트로 자격 증명을 제공해요.
permission_mode, can_use_tool 도구별 permission_policy(always_allow, always_ask 또는 auto); 승인을 위해 멈추는 호출에는 user.tool_confirmation 이벤트를 보내요.

코드 비교 (Code comparison)

이전 (Agent SDK):

```python Python 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-5-5", 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)


```typescript TypeScript
import { createSdkMcpServer, query, tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const getWeather = tool(
  "get_weather",
  "Get the current weather for a city.",
  { city: z.string() },
  async (args) => ({
    content: [{ type: "text", text: `${args.city}: 18°C, clear` }]
  })
);

for await (const message of query({
  prompt: "What's the weather in Tokyo?",
  options: {
    model: "claude-opus-5-5",
    systemPrompt: "You are a concise weather assistant.",
    mcpServers: {
      weather: createSdkMcpServer({ name: "weather", version: "1.0", tools: [getWeather] })
    }
  }
})) {
  console.log(message);
}

이후 (Managed Agents):

```python Python from anthropic import Anthropic

client = Anthropic()

agent = client.beta.agents.create( name="weather-agent", model="claude-opus-5-5", 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 event in stream: match event.type: case "agent.message": print( "".join( block.text for block in event.content if block.type == "text" ) ) case "agent.custom_tool_use": result = get_weather(**event.input) client.beta.sessions.events.send( session.id, events=[ { "type": "user.custom_tool_result", "custom_tool_use_id": event.id, "content": [{"type": "text", "text": result}], } ], ) case "session.status_idle": if event.stop_reason and event.stop_reason.type == "end_turn": break


```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const agent = await client.beta.agents.create({
  name: "weather-agent",
  model: "claude-opus-5-5",
  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"]
      }
    }
  ]
});
const environment = await client.beta.environments.create({
  name: "weather-env",
  config: { type: "cloud", networking: { type: "unrestricted" } }
});

const session = await client.beta.sessions.create({
  agent: { type: "agent", id: agent.id, version: agent.version },
  environment_id: environment.id
});

function getWeather({ city }: Record<string, unknown>): string {
  return `${city}: 18°C, clear`;
}

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: "What's the weather in Tokyo?" }]
    }
  ]
});

loop: for await (const event of stream) {
  switch (event.type) {
    case "agent.message":
      for (const block of event.content) {
        if (block.type === "text") {
          console.log(block.text);
        }
      }
      break;
    case "agent.custom_tool_use": {
      const result = getWeather(event.input);
      await client.beta.sessions.events.send(session.id, {
        events: [
          {
            type: "user.custom_tool_result",
            custom_tool_use_id: event.id,
            content: [{ type: "text", text: result }]
          }
        ]
      });
      break;
    }
    case "session.status_idle":
      if (event.stop_reason?.type === "end_turn") {
        break loop;
      }
      break;
  }
}
using System.Text.Json;

using Anthropic.Models.Beta.Agents;
using Anthropic.Models.Beta.Environments;
using Anthropic.Models.Beta.Sessions;
using Anthropic.Models.Beta.Sessions.Events;

AnthropicClient client = new();

var agent = await client.Beta.Agents.Create(new()
{
    Name = "weather-agent",
    Model = BetaManagedAgentsModel.ClaudeOpus5_5,
    System = "You are a concise weather assistant.",
    Tools =
    [
        new BetaManagedAgentsCustomToolParams
        {
            Type = "custom",
            Name = "get_weather",
            Description = "Get the current weather for a city.",
            InputSchema = new()
            {
                Properties = new Dictionary<string, JsonElement>
                {
                    ["city"] = JsonSerializer.SerializeToElement(new { type = "string" }),
                },
                Required = ["city"],
            },
        },
    ],
});
var environment = await client.Beta.Environments.Create(new()
{
    Name = "weather-env",
    Config = new BetaCloudConfigParams
    {
        Networking = new BetaUnrestrictedNetwork(),
    },
});

var session = await client.Beta.Sessions.Create(new()
{
    Agent = new BetaManagedAgentsAgentParams
    {
        Type = "agent",
        ID = agent.ID,
        Version = agent.Version,
    },
    EnvironmentID = environment.ID,
});

static string GetWeather(string city) => $"{city}: 18°C, clear";

using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);

await client.Beta.Sessions.Events.Send(session.ID, new()
{
    Events =
    [
        new BetaManagedAgentsUserMessageEventParams
        {
            Type = "user.message",
            Content = [new BetaManagedAgentsTextBlock { Type = "text", Text = "What's the weather in Tokyo?" }],
        },
    ],
});

await foreach (var streamEvent in stream.Enumerate())
{
    if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
    {
        var text = string.Concat(message.Content.Select(block =>
            block.Value is BetaManagedAgentsTextBlock textBlock ? textBlock.Text : ""));
        Console.WriteLine(text);
    }
    else if (streamEvent.Value is BetaManagedAgentsAgentCustomToolUseEvent toolUse)
    {
        var result = GetWeather(toolUse.Input["city"].GetString()!);
        await client.Beta.Sessions.Events.Send(session.ID, new()
        {
            Events =
            [
                new BetaManagedAgentsUserCustomToolResultEventParams
                {
                    Type = "user.custom_tool_result",
                    CustomToolUseID = toolUse.ID,
                    Content =
                    [
                        new BetaManagedAgentsTextBlock
                        {
                            Type = "text",
                            Text = result,
                        },
                    ],
                },
            ],
        });
    }
    else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent idle
        && idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
    {
        break;
    }
}
client := anthropic.NewClient()
ctx := context.Background()

agent, err := client.Beta.Agents.New(ctx, anthropic.BetaAgentNewParams{
	Name: "weather-agent",
	Model: anthropic.BetaManagedAgentsModelConfigParams{
		ID: anthropic.BetaManagedAgentsModelClaudeOpus5_5,
	},
	System: anthropic.String("You are a concise weather assistant."),
	Tools: []anthropic.BetaAgentNewParamsToolUnion{{
		OfCustom: &anthropic.BetaManagedAgentsCustomToolParams{
			Type:        anthropic.BetaManagedAgentsCustomToolParamsTypeCustom,
			Name:        "get_weather",
			Description: "Get the current weather for a city.",
			InputSchema: anthropic.BetaManagedAgentsCustomToolInputSchemaParam{
				Properties: map[string]any{
					"city": map[string]any{"type": "string"},
				},
				Required: []string{"city"},
			},
		},
	}},
})
if err != nil {
	panic(err)
}
environment, err := client.Beta.Environments.New(ctx, anthropic.BetaEnvironmentNewParams{
	Name: "weather-env",
	Config: anthropic.BetaEnvironmentNewParamsConfigUnion{
		OfCloud: &anthropic.BetaCloudConfigParams{
			Networking: anthropic.BetaCloudConfigParamsNetworkingUnion{
				OfUnrestricted: &anthropic.BetaUnrestrictedNetworkParam{},
			},
		},
	},
})
if err != nil {
	panic(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 {
	panic(err)
}

getWeather := func(city string) string {
	return fmt.Sprintf("%s: 18°C, clear", city)
}

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: "What's the weather in Tokyo?",
				},
			}},
		},
	}},
})
if err != nil {
	panic(err)
}

loop:
for stream.Next() {
	event := stream.Current()
	switch event.Type {
	case "agent.message":
		for _, block := range event.AsAgentMessage().Content {
			if block.Type == "text" {
				fmt.Println(block.Text)
			}
		}
	case "agent.custom_tool_use":
		toolUse := event.AsAgentCustomToolUse()
		result := getWeather(toolUse.Input["city"].(string))
		if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
			Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
				OfUserCustomToolResult: &anthropic.BetaManagedAgentsUserCustomToolResultEventParams{
					Type:            anthropic.BetaManagedAgentsUserCustomToolResultEventParamsTypeUserCustomToolResult,
					CustomToolUseID: toolUse.ID,
					Content: []anthropic.BetaManagedAgentsUserCustomToolResultEventParamsContentUnion{{
						OfText: &anthropic.BetaManagedAgentsTextBlockParam{
							Type: anthropic.BetaManagedAgentsTextBlockTypeText,
							Text: result,
						},
					}},
				},
			}},
		}); err != nil {
			panic(err)
		}
	case "session.status_idle":
		idle := event.AsSessionStatusIdle()
		if _, ok := idle.StopReason.AsAny().(anthropic.BetaManagedAgentsSessionEndTurn); ok {
			break loop
		}
	}
}
if err := stream.Err(); err != nil {
	panic(err)
}
import java.util.Map;
import java.util.function.Function;

import com.anthropic.models.beta.agents.AgentCreateParams;
import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolInputSchema;
import com.anthropic.models.beta.agents.BetaManagedAgentsCustomToolParams;
import com.anthropic.models.beta.agents.BetaManagedAgentsModel;
import com.anthropic.models.beta.environments.BetaCloudConfigParams;
import com.anthropic.models.beta.environments.BetaUnrestrictedNetwork;
import com.anthropic.models.beta.environments.EnvironmentCreateParams;
import com.anthropic.models.beta.sessions.BetaManagedAgentsAgentParams;
import com.anthropic.models.beta.sessions.SessionCreateParams;
import com.anthropic.models.beta.sessions.events.BetaManagedAgentsStreamSessionEvents;
import com.anthropic.models.beta.sessions.events.BetaManagedAgentsUserCustomToolResultEventParams;
import com.anthropic.models.beta.sessions.events.BetaManagedAgentsUserMessageEventParams;
import com.anthropic.models.beta.sessions.events.EventSendParams;

var client = AnthropicOkHttpClient.fromEnv();

var agent = client.beta().agents().create(AgentCreateParams.builder()
    .name("weather-agent")
    .model(BetaManagedAgentsModel.CLAUDE_OPUS_5_5)
    .system("You are a concise weather assistant.")
    .addTool(BetaManagedAgentsCustomToolParams.builder()
        .type(BetaManagedAgentsCustomToolParams.Type.CUSTOM)
        .name("get_weather")
        .description("Get the current weather for a city.")
        .inputSchema(BetaManagedAgentsCustomToolInputSchema.builder()
            .properties(BetaManagedAgentsCustomToolInputSchema.Properties.builder()
                .putAdditionalProperty("city", JsonValue.from(Map.of("type", "string")))
                .build())
            .addRequired("city")
            .build())
        .build())
    .build());
var environment = client.beta().environments().create(EnvironmentCreateParams.builder()
    .name("weather-env")
    .config(BetaCloudConfigParams.builder()
        .networking(BetaUnrestrictedNetwork.builder().build())
        .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());

Function<String, String> getWeather = city -> city + ": 18°C, clear";

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("What's the weather in Tokyo?")
                .build())
            .build());

    loop:
    for (var event : (Iterable<BetaManagedAgentsStreamSessionEvents>) stream.stream()::iterator) {
        switch (event.type().value()) {
            case AGENT_MESSAGE -> {
                for (var block : event.asAgentMessage().content()) {
                    block.text().ifPresent(textBlock -> IO.println(textBlock.text()));
                }
            }
            case AGENT_CUSTOM_TOOL_USE -> {
                var toolUse = event.asAgentCustomToolUse();
                var city = toolUse.input()._additionalProperties().get("city").asStringOrThrow();
                var result = getWeather.apply(city);
                client.beta().sessions().events().send(
                    session.id(),
                    EventSendParams.builder()
                        .addEvent(BetaManagedAgentsUserCustomToolResultEventParams.builder()
                            .type(BetaManagedAgentsUserCustomToolResultEventParams.Type.USER_CUSTOM_TOOL_RESULT)
                            .customToolUseId(toolUse.id())
                            .addTextContent(result)
                            .build())
                        .build());
            }
            case SESSION_STATUS_IDLE -> {
                if (event.asSessionStatusIdle().stopReason().isEndTurn()) {
                    break loop;
                }
            }
        }
    }
}
use Anthropic\Client;
use Anthropic\Beta\Agents\BetaManagedAgentsCustomToolInputSchema;
use Anthropic\Beta\Agents\BetaManagedAgentsCustomToolParams;
use Anthropic\Beta\Sessions\BetaManagedAgentsAgentParams;
use Anthropic\Beta\Sessions\Events\ManagedAgentsAgentCustomToolUseEvent;
use Anthropic\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent;
use Anthropic\Beta\Sessions\Events\ManagedAgentsSessionEndTurn;
use Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent;
use Anthropic\Beta\Sessions\Events\ManagedAgentsTextBlock;

$client = new Client();

$agent = $client->beta->agents->create(
    name: 'weather-agent',
    model: 'claude-opus-5-5',
    system: 'You are a concise weather assistant.',
    tools: [
        BetaManagedAgentsCustomToolParams::with(
            type: 'custom',
            name: 'get_weather',
            description: 'Get the current weather for a city.',
            inputSchema: BetaManagedAgentsCustomToolInputSchema::with(
                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: BetaManagedAgentsAgentParams::with(
        type: 'agent',
        id: $agent->id,
        version: $agent->version,
    ),
    environmentID: $environment->id,
);

function getWeather(string $city): string
{
    return "{$city}: 18°C, clear";
}

$stream = $client->beta->sessions->events->streamStream($session->id);

$client->beta->sessions->events->send(
    $session->id,
    events: [
        [
            'type' => 'user.message',
            'content' => [['type' => 'text', 'text' => "What's the weather in Tokyo?"]],
        ],
    ],
);

foreach ($stream as $event) {
    switch (true) {
        case $event instanceof ManagedAgentsAgentMessageEvent:
            foreach ($event->content as $block) {
                if ($block instanceof ManagedAgentsTextBlock) {
                    echo $block->text . "\n";
                }
            }
            break;
        case $event instanceof ManagedAgentsAgentCustomToolUseEvent:
            $result = getWeather($event->input['city']);
            $client->beta->sessions->events->send(
                $session->id,
                events: [
                    [
                        'type' => 'user.custom_tool_result',
                        'custom_tool_use_id' => $event->id,
                        'content' => [['type' => 'text', 'text' => $result]],
                    ],
                ],
            );
            break;
        case $event instanceof ManagedAgentsSessionStatusIdleEvent:
            if ($event->stopReason instanceof ManagedAgentsSessionEndTurn) {
                break 2;
            }
            break;
    }
}
$stream->close();
require "anthropic"

client = Anthropic::Client.new

agent = client.beta.agents.create(
  name: "weather-agent",
  model: "claude-opus-5-5",
  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)
  "#{city}: 18°C, clear"
end

stream = client.beta.sessions.events.stream_events(session.id)
client.beta.sessions.events.send_(
  session.id,
  events: [{type: "user.message", content: [{type: "text", text: "What's the weather in Tokyo?"}]}]
)

stream.each do |event|
  case event
  when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
    event.content.each do |block|
      puts block.text if block.is_a?(Anthropic::Beta::Sessions::BetaManagedAgentsTextBlock)
    end
  when Anthropic::Beta::Sessions::BetaManagedAgentsAgentCustomToolUseEvent
    result = get_weather(event.input[:city])
    client.beta.sessions.events.send_(
      session.id,
      events: [
        {
          type: "user.custom_tool_result",
          custom_tool_use_id: event.id,
          content: [{type: "text", text: result}]
        }
      ]
    )
  when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
    break if event.stop_reason.is_a?(Anthropic::Beta::Sessions::BetaManagedAgentsSessionEndTurn)
  end
end

에이전트와 환경은 한 번 만들어져 세션들에서 재사용돼요. 도구 함수는 여전히 여러분의 프로세스에서 실행돼요. 차이는 SDK가 디스패치해 주는 대신 여러분이 agent.custom_tool_use 이벤트를 읽고 결과를 명시적으로 보낸다는 것이에요.

클라이언트로 옮겨가는 기능들 (Features that move to your client)

Anthropic이 에이전트 루프를 실행하는 대가는, SDK가 자동으로 처리하던 몇 가지가 여러분의 클라이언트 책임이 된다는 것이에요.

SDK 기능 Managed Agents 방식
Plan mode / 계획 모드 먼저 계획 전용 세션을 실행하고, 그다음 계획을 실행할 두 번째 세션을 실행해요.
출력 스타일, 슬래시 명령 user.message를 보내기 전이나 agent.message를 받은 후에 클라이언트에서 적용해요.
PreToolUse / PostToolUse 여러분의 클라이언트는 응답하기 전에 이미 모든 agent.custom_tool_use 이벤트를 봐요. 로직을 거기에 넣으세요. 내장 도구는 permission_policy: always_ask를 사용해 모든 호출을 검토하세요. auto는 대신 서버가 각 호출을 평가하게 하지만, 서버가 호출을 안전하다고 평가하면 여러분의 클라이언트에 닿지 않고 실행돼요.
max_turns 클라이언트 측에서 턴을 세세요.

마이그레이션 체크리스트 (Migration checklist)

  1. 에이전트에 필요한 네트워킹과 런타임으로 환경을 만드세요.
  2. 시스템 프롬프트와 도구 선택을 에이전트 정의로 포팅하세요.
  3. 루프를 sessions.createsessions.events.stream으로 교체하세요.
  4. 에이전트가 읽는 로컬 파일은 Files API로 업로드하고 resources로 마운트하세요.
  5. 커스텀 도구 핸들러는 실행을 agent.custom_tool_use 이벤트에 응답하는 이벤트 루프로 옮기세요.
  6. 새 흐름에 프로덕션 트래픽을 돌리기 전에 테스트 세션으로 검증하세요.

모델 버전 간 마이그레이션 (Migrating between model versions)

새 Claude 모델이 출시될 때, Claude Managed Agents 통합을 마이그레이션하는 것은 대개 한 필드 변경이에요: 에이전트 정의에서 model을 업데이트하면 다음에 만드는 세션에 그 변경이 적용돼요.

```bash cURL curl -sS --fail-with-body "https://api.anthropic.com/v1/agents/$AGENT_ID?beta=true" \ -H "x-api-key: $ANTHR...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-5-5"}')" ``` ```bash CLI ant apply agent.md ```
<File filename="agent.md">
  ```markdown
  ---
  name: Task Runner
  model: claude-opus-5-5
  tools:
    - type: agent_toolset_20260401
  ---

  You are a task automation agent. Complete the task you are given end to end.
  ```
</File>
client.beta.agents.update(
    agent.id,
    version=agent.version,
    model="claude-opus-5-5",
)
await client.beta.agents.update(agent.id, {
  version: agent.version,
  model: "claude-opus-5-5"
});
await client.Beta.Agents.Update(agent.ID, new()
{
    Version = agent.Version,
    Model = BetaManagedAgentsModel.ClaudeOpus5_5,
});
_, err = client.Beta.Agents.Update(ctx, agent.ID, anthropic.BetaAgentUpdateParams{
	Version: agent.Version,
	Model: anthropic.BetaManagedAgentsModelConfigParams{
		ID: anthropic.BetaManagedAgentsModelClaudeOpus5_5,
	},
})
if err != nil {
	panic(err)
}
client.beta().agents().update(
    agent.id(),
    AgentUpdateParams.builder()
        .version(agent.version())
        .model(BetaManagedAgentsModel.CLAUDE_OPUS_5_5)
        .build()
);
$client->beta->agents->update(
    $agent->id,
    version: $agent->version,
    model: 'claude-opus-5-5',
);
client.beta.agents.update(
  agent.id,
  version: agent.version,
  model: "claude-opus-5-5"
)

Messages API 마이그레이션 가이드에 문서화된 대부분의 모델 수준 동작 변경은 여러분 쪽에서 조치를 요구하지 않아요:

  • 요청 파라미터 변경(max_tokens 기본값, thinking 구성)은 Claude Managed Agents 런타임이 처리해요. 이 필드들은 에이전트 정의에 노출되지 않아요.
  • 어시스턴트 메시지 프리필링은 이벤트 기반 세션 모델에 존재하지 않아서, 새 모델에서의 제거는 no-op이에요.
  • 도구 인자 JSON 이스케이핑agent.custom_tool_use 이벤트를 받기 전에 런타임이 파싱해요. 여러분은 구조화된 데이터를 보지, 원시 문자열을 보지 않아요.

Messages API 가이드의 동작 설명(모델이 무엇을 다르게 하는지)은 여전히 적용돼요. 마이그레이션 단계(요청 코드를 어떻게 바꾸는지)는 적용되지 않아요.

더 알아보기 (Learn more)