콘텐츠로 이동

Managed Agents로 서버에서 에이전트 돌리기

한눈에 요약
  • Managed Agents는 “에이전트 루프 + 샌드박스”를 Anthropic이 운영해 주는 서비스예요. 내 코드는 세션을 만들고 이벤트를 주고받기만 해요. 기능은 베타이고 요청에 managed-agents-2026-04-01 베타 헤더가 필요해요(SDK가 자동으로 붙여요).
  • 구조는 **에이전트(한 번 만들기) → 환경(실행 장소) → 세션(실행 1회) → 이벤트(주고받는 메시지)**예요. 에이전트는 버전이 있는 저장 객체라서 매번 새로 만들지 않아요.
  • 비용은 **토큰 + 세션 런타임($0.08/시간, running 상태일 때만)**이에요. Batch API 할인은 적용되지 않아요. 세션 생성 때 budget으로 달러 상한을 거세요.
  • 수백 개 조각으로 나뉘는 일은 **동적 워크플로(베타)**로 에이전트가 여러 에이전트를 단계별로 돌리게 할 수 있어요. 토큰을 많이 쓰니 필요한 일에만 켜세요.
  • ZDR(데이터 무보관)과 HIPAA BAA 대상이 아니에요. 세션과 대화 기록이 서버에 저장되는 구조예요.

예제는 공식 문서 기준이에요. 2026-10-11에 문법을 검증했고, 설치한 SDK(Python anthropic 1.13.0, TypeScript @anthropic-ai/sdk 0.133.0)에서 메서드와 파라미터 이름이 있는지, TypeScript는 타입 검사까지 확인했어요. 실제 호출은 하지 않았어요. 베타라서 필드와 수치가 바뀔 수 있으니 문서를 함께 확인하세요.

이 튜토리얼로 할 수 있게 되는 것

섹션 제목: “이 튜토리얼로 할 수 있게 되는 것”
  • Managed Agents가 Messages API, Agent SDK와 무엇이 다른지 설명하고 고를 수 있어요.
  • 에이전트와 환경을 한 번 만들고, 세션을 열어 메시지를 보내고 스트리밍으로 결과를 받을 수 있어요.
  • 세션 예산과 런타임 요금으로 한 번 실행의 비용을 계산하고 상한을 걸 수 있어요.
  • 동적 워크플로를 켜고, 끝났는지 올바르게 판단하는 방법을 알 수 있어요.
구성 요소 뜻
에이전트(Agent) 모델, 시스템 프롬프트, 도구, MCP 서버, 스킬을 묶은 설정이에요. 저장되고 버전이 붙어요.
환경(Environment) 세션이 도는 곳이에요. Anthropic이 관리하는 클라우드 샌드박스이거나, 내 인프라의 self-hosted 샌드박스예요.
세션(Session) 환경 안에서 도는 에이전트 한 번의 실행이에요. 대화 기록과 샌드박스 파일이 유지돼요.
이벤트(Events) 내 앱과 에이전트가 주고받는 메시지예요. 사용자 메시지, 도구 결과, 상태 알림이 모두 이벤트예요.

흐름은 이래요. 에이전트를 한 번 만들고 ID를 저장해요. 세션을 열 때마다 에이전트 ID와 환경 ID를 가리켜요. 이벤트를 보내면 에이전트가 샌드박스에서 bash, 파일 작업, 웹 검색, MCP 도구를 알아서 쓰고, 과정을 서버 전송 이벤트(SSE)로 흘려 줘요. 이벤트 기록은 서버에 저장돼서 나중에 다시 가져올 수 있어요.

필요한 것 고를 것
응답을 직접 받아 내 루프로 제어하고 싶어요 Messages API + 도구 사용
내 서버 프로세스에서 Claude Code 같은 에이전트를 돌리고 싶어요 Agent SDK (호스팅은 내가 해요)
몇 분~몇 시간 걸리는 비동기 작업, 상태가 남는 샌드박스, 크론 실행을 맡기고 싶어요 Managed Agents

공식 개요 문서가 꼽는 Managed Agents의 용도는 장시간 실행, 클라우드 샌드박스, 규정이나 데이터 위치 때문에 필요한 self-hosted 실행, 직접 만들 인프라 최소화, 상태가 남는 세션, 크론 스케줄 실행(scheduled deployments)이에요. Agent SDK 호스팅 문서도 에이전트 루프를 직접 호스팅하고 싶지 않다면 Managed Agents를 고려하라고 안내해요.

0/7 완료
  1. SDK 설치와 키 설정

    Managed Agents는 일반 anthropic SDK의 client.beta.* 아래에 있어요. Agent SDK 패키지가 아니에요.

    터미널 창
    python3 -m venv .venv && source .venv/bin/activate
    pip install anthropic
    export ANTHROPIC_API_KEY="your-api-key-here"
    • 키는 코드에 적지 말고 환경변수로만 다루세요(C16).
    • TypeScript는 npm install @anthropic-ai/sdk예요. 명령줄 도구 ant(brew install anthropics/tap/ant)로 에이전트 설정을 파일로 관리하는 방법도 있어요. 공식 퀵스타트에도 이 방법이 함께 소개돼요.
    • 모든 Managed Agents 요청에 managed-agents-2026-04-01 베타 헤더가 필요한데, SDK가 자동으로 붙여요.
  2. 환경과 에이전트를 한 번만 만들기

    환경은 샌드박스 설정이에요. 아래는 클라우드 샌드박스에 limited 네트워킹을 쓰고 패키지 관리자만 허용해요. 에이전트는 모델, 시스템 프롬프트, 도구 묶음 agent_toolset_20260401을 정해요. 웹 검색과 웹 가져오기는 이 예제에 필요 없어서 끕니다.

    • limited 네트워킹에서는 allowed_hosts 목록이 웹 검색·가져오기에도 적용돼요. 호스트를 나열하지 않으면 두 도구는 결과를 가져오지 못해요. 필요한 사이트만 나열해서 켜세요.
    • 이 파일은 준비용이에요. 실행할 때마다 부르지 말고 한 번만 실행해서 ID를 저장하세요.

    ma_setup.py로 저장해요.

    """준비 단계: 환경과 에이전트를 한 번만 만들고 ID를 저장한다. (요청 경로에서 매번 만들지 않는다.)"""
    import json
    import anthropic
    client = anthropic.Anthropic() # ANTHROPIC_API_KEY 환경변수를 읽어요
    environment = client.beta.environments.create(
    name="report-env",
    config={
    "type": "cloud",
    "networking": {"type": "limited", "allow_package_managers": True},
    },
    )
    agent = client.beta.agents.create(
    name="Report Writer",
    model="claude-sonnet-5-5",
    system="당신은 데이터 분석가입니다. 결과는 /mnt/session/outputs/ 에 마크다운 파일로 저장하세요.",
    tools=[
    {
    "type": "agent_toolset_20260401",
    "configs": [
    {"name": "web_fetch", "enabled": False},
    {"name": "web_search", "enabled": False},
    ],
    }
    ],
    )
    with open("managed-agent-ids.json", "w", encoding="utf-8") as f:
    json.dump({"agent_id": agent.id, "agent_version": agent.version, "environment_id": environment.id}, f)
    print(agent.id, agent.version, environment.id)
    터미널 창
    python ma_setup.py
  3. 세션을 만들고, 스트림을 먼저 열고, 메시지 보내기

    세션은 에이전트 ID와 환경 ID를 가리켜 만들어요. 이때 예산을 걸 수 있어요. 예산은 세션을 만들 때만 붙일 수 있고, 나중에 새로 붙일 수는 없어요.

    • max_list_cost.amount는 센트 단위 정수 문자열이에요. "200"이 2달러이고, "2.00"은 거부돼요. 통화는 USD만 돼요.
    • 스트림은 열린 뒤에 생긴 이벤트만 줘요. 그래서 스트림을 먼저 열고 메시지를 보내요.
    • session.status_idle이 오면 에이전트가 할 일을 마친 거예요. 단 stop_reason.type이 requires_action이면 내 응답(도구 승인, 사용자 정의 도구 결과)을 기다리는 중이니 계속 들어야 해요.

    ma_run.py로 저장해요.

    """실행 단계: 저장해 둔 ID로 세션을 만들고, 스트림을 먼저 연 뒤 메시지를 보낸다."""
    import json
    import anthropic
    client = anthropic.Anthropic()
    ids = json.load(open("managed-agent-ids.json", encoding="utf-8"))
    session = client.beta.sessions.create(
    agent={"type": "agent", "id": ids["agent_id"], "version": ids["agent_version"]},
    environment_id=ids["environment_id"],
    title="첫 보고서 세션",
    budget={ # 세션 하나의 비용 상한: 센트 단위 정수를 문자열로 ("200" = 2달러)
    "type": "limit",
    "max_list_cost": {"amount": "200", "currency": "USD"},
    },
    )
    print("세션:", session.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": "1부터 20까지 피보나치 수열을 계산하는 파이썬 스크립트를 만들어 실행하고, 결과를 /mnt/session/outputs/fib.md 로 저장해 줘.",
    }
    ],
    }
    ],
    )
    for event in stream:
    if event.type == "agent.message":
    for block in event.content:
    if block.type == "text":
    print(block.text, end="")
    elif event.type == "agent.tool_use":
    print(f"\n[도구: {event.name}]")
    elif event.type == "session.status_idle":
    # requires_action이면 내 응답을 기다리는 중이니 계속 듣는다
    if event.stop_reason.type != "requires_action":
    print(f"\n멈춘 이유: {event.stop_reason.type}")
    break
    elif event.type == "session.status_terminated":
    print("\n세션이 종료됐어요.")
    break
    done = client.beta.sessions.retrieve(session.id)
    print("누적 사용량:", done.usage) # list_cost 등이 들어 있어요
    터미널 창
    python ma_run.py
    TypeScript 버전
    import Anthropic from "@anthropic-ai/sdk";
    import { readFileSync } from "node:fs";
    const client = new Anthropic();
    const ids = JSON.parse(readFileSync("managed-agent-ids.json", "utf-8"));
    const session = await client.beta.sessions.create({
    agent: { type: "agent", id: ids.agent_id, version: ids.agent_version },
    environment_id: ids.environment_id,
    title: "첫 보고서 세션",
    budget: {
    type: "limit",
    max_list_cost: { amount: "200", currency: "USD" }, // 센트 단위 문자열: 2달러
    },
    });
    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: "1부터 20까지 피보나치 수열을 계산해 /mnt/session/outputs/fib.md 로 저장해 줘." }],
    },
    ],
    });
    loop: for await (const event of stream) {
    switch (event.type) {
    case "agent.message":
    for (const block of event.content) {
    if (block.type === "text") process.stdout.write(block.text);
    }
    break;
    case "agent.tool_use":
    console.log(`\n[도구: ${event.name}]`);
    break;
    case "session.status_idle":
    if (event.stop_reason.type !== "requires_action") break loop;
    break;
    case "session.status_terminated":
    break loop;
    }
    }
    • 에이전트가 만든 파일은 샌드박스의 /mnt/session/outputs/에 저장하게 하면 Files API로 조회하고 내려받을 수 있어요. 세션이 idle이 된 직후에는 목록 반영에 1~3초 걸릴 수 있어요.
    • 스트림이 끊기면 재연결한 뒤 이벤트 목록을 조회해 중복을 걸러야 해요. SSE는 놓친 이벤트를 다시 보내 주지 않아요.
    이 프롬프트를 복사해서 붙여넣으세요

    Claude Managed Agents(Python SDK)로 에이전트와 환경을 한 번 만들어 ID를 파일에 저장하고, 세션을 만들어 스트림을 먼저 연 뒤 메시지를 보내는 코드를 짜 주세요. 세션 예산을 2달러로 걸고, status_idle이어도 stop_reason이 requires_action이면 계속 듣게 해 주세요. 키는 환경변수에서만 읽게 해 주세요.

  4. 비용 확인하기

    세션 객체의 usage에 누적 사용량이 있어요. usage.list_cost는 공개 정가로 계산한 누적 비용이고, usage.active_seconds는 런타임 요금을 매기는 실행 시간이에요. 스트림에는 session.usage 이벤트가 있고, 세션이 idle이 되기 직전에 항상 한 번 와요.

  5. 동적 워크플로 켜기 (베타)

    일이 “문서 수백 개 검토”처럼 조각이 아주 많으면, 에이전트가 워크플로를 직접 짜서 여러 에이전트를 단계별로 돌리고 결과를 합칠 수 있어요. 서버가 이걸 백그라운드의 **워크플로 실행(run)**으로 돌려요. 켜는 방법은 에이전트의 multiagent 설정이에요.

    • 별도 API 호출로 run을 시작하지 않아요. 사용자 메시지로 일을 설명하면 에이전트가 run을 시작할지 스스로 판단해요.
    • 그래서 시스템 프롬프트에 “어떤 일에 run을 쓰고, 어떤 일은 직접 하라”를 적어 유도해요. 아래 예제가 공식 예제의 취지예요.
    • run의 에이전트는 모두 토큰을 써요. 작은 일에까지 run을 쓰게 하지 마세요.
    • 인라인 에이전트는 세션 에이전트와 같은 모델을 써요. 일부만 다른 모델로 돌리려면 에이전트를 따로 만들어 workflows.predefined_agents에 나열하세요(최대 20개).
    • multiagent가 설정된 에이전트는 다른 에이전트의 목록에 넣을 수 없어요(자기 자신 {"type": "self"}은 가능해요).

    ma_workflow.py로 저장해요.

    """동적 워크플로(베타)를 켠 에이전트. 시스템 프롬프트에 '언제 쓰는지'를 적어 두는 것이 핵심이에요."""
    import anthropic
    client = anthropic.Anthropic()
    agent = client.beta.agents.create(
    name="Contract Reviewer",
    model="claude-opus-5-5",
    system=(
    "당신은 계약서 검토자입니다. 계약서가 몇 건을 넘으면 워크플로 실행(run)을 시작해 "
    "병렬로 읽고 결과를 합치세요. 한두 건은 직접 검토하세요."
    ),
    tools=[{"type": "agent_toolset_20260401"}],
    multiagent={"type": "multiagent_20261001", "workflows": {"type": "enabled"}},
    )
    print(agent.id)
    # 끄려면: 에이전트를 갱신해서 {"workflows": {"type": "disabled"}} 로 보낸다.

    run이 도는 동안 스트림에 workflow_run.created, workflow_run.phase_started, workflow_run.status_ended 같은 이벤트가 와요. 끝났는지 판단하는 법이 중요해요.

    1. 지금까지 본 모든 run에 workflow_run.status_ended가 도착해요.
    2. 그 뒤에 내가 일으키지 않은 session.status_idle(stop_reason이 end_turn)이 와요.

    idle만으로는 끝이 아니에요. 예산 정지로 run이 멈춘 채 세션이 idle이 될 수 있고, 에이전트가 결과를 읽고 새 run을 또 시작할 수도 있어요. 끝내려면 user.interrupt가 아니라 **세션을 보관(archive)**해야 해요. 중단 이벤트는 run을 끝내지 않아요.

  6. 끝난 세션 정리하기

    세션과 샌드박스 상태는 서버에 남아요. 필요한 산출물을 먼저 내려받은 뒤 지우세요. 삭제하면 되돌릴 수 없어요. 또 에이전트, 환경, 세션을 **보관(archive)**하면 되돌릴 수 없고 새 세션에서 쓸 수 없으니, 쓰는 에이전트는 함부로 보관하지 마세요.

    ma_cleanup.py로 저장해요.

    """끝난 세션을 지워요. 대화 기록과 샌드박스 상태가 서버에서 삭제되니, 산출물은 먼저 내려받으세요."""
    import sys
    import anthropic
    client = anthropic.Anthropic()
    client.beta.sessions.delete(session_id=sys.argv[1]) # 예: python ma_cleanup.py sesn_...
    print("삭제했어요:", sys.argv[1])
    터미널 창
    python ma_cleanup.py sesn_여기에_세션_ID

요금 구조 (2026-10-11 기준, 미국 달러)

섹션 제목: “요금 구조 (2026-10-11 기준, 미국 달러)”
항목 내용
토큰 Messages API와 같은 모델 요금이에요. 프롬프트 캐시 배수도 같아요.
세션 런타임 $0.08 / 세션-시간. running 상태 시간만 밀리초 단위로 세요. idle(내 응답 대기), rescheduling, terminated 시간은 제외돼요.
웹 검색 1,000회당 $10
컨테이너 요금 세션 런타임이 코드 실행 컨테이너-시간 과금을 대체하므로 따로 더 내지 않아요.
Batch API 할인 적용되지 않아요. 세션이 상태가 있고 대화형이라 배치 모드가 없어요.
미국 내 추론 고정(inference_geo: us) 토큰이 1.1배예요.

Opus 5.5(입력 $4, 출력 $20, 캐시 읽기 $0.20 / 100만 토큰)로 1시간 동안 running이었고 입력 50,000토큰, 출력 15,000토큰을 쓴 세션이에요. 공식 문서의 계산 예시와 같은 방식에 이 사이트의 모델 가격(C16)을 넣어 다시 계산했어요.

항목 계산 비용
입력 토큰 50,000 × $4 / 1,000,000 $0.20
출력 토큰 15,000 × $20 / 1,000,000 $0.30
세션 런타임 1.0시간 × $0.08 $0.08
합계 $0.58

입력 중 40,000토큰이 캐시 읽기라면 입력이 $0.04(10,000토큰 × $4)와 $0.008(40,000토큰 × $0.20)로 줄어 합계는 약 $0.43이에요. 토큰이 비용 대부분이고 런타임은 작은 편이지만, run이 수십~수백 에이전트로 늘면 토큰이 곱절로 커져요.

한도와 속도 제한 (공식 문서, 베타라 바뀔 수 있어요)

섹션 제목: “한도와 속도 제한 (공식 문서, 베타라 바뀔 수 있어요)”
항목 값
생성 엔드포인트(에이전트, 세션, 환경 등) 조직당 분당 300 요청
읽기 엔드포인트(retrieve, list, stream) 조직당 분당 1,200 요청
한 run에서 동시에 일하는 스레드 64
run 하나가 시작할 수 있는 에이전트 1,000개 (넘으면 thread_limit_error)
run 수명 기본 24시간 (넘으면 timeout_error)
세션에서 동시에 열려 있는 run 기본 10 (넘으면 max_workflow_runs_error)
세션의 자식 스레드 25 (워크플로 run의 스레드는 별도)
multiagent의 predefined_agents 목록마다 최대 20

run의 모델 요청은 조직의 Messages API 속도 제한에도 합산돼요. 문서는 위 run 한도가 “API가 보장하는 수치가 아니라서 바뀔 수 있다”고 밝혀요.

이벤트 뜻
agent.message 에이전트의 응답
agent.tool_use / agent.tool_result 내장 도구(bash, 파일 작업 등) 호출과 결과
agent.custom_tool_use 내가 정의한 도구 호출. user.custom_tool_result로 답해요.
session.status_running / session.status_idle 일하는 중 / 한 차례 마치고 입력을 기다리는 중(stop_reason 포함)
session.status_terminated 세션 종료
session.usage 누적 사용량과 list_cost (idle 직전에 1회)
workflow_run.* 동적 워크플로 run의 생성·단계·종료·오류

메시지를 보냈는데 처음 이벤트가 한꺼번에 와요

섹션 제목: “메시지를 보냈는데 처음 이벤트가 한꺼번에 와요”
  • 원인: 스트림은 열린 뒤에 생긴 이벤트만 줘요. 메시지를 먼저 보내고 스트림을 나중에 열면 초반 이벤트가 몰려서 와요.
  • 해결: 스트림을 먼저 열고(with ... events.stream(...)) 그 안에서 메시지를 보내세요.

session.status_idle이 왔는데 일이 끝난 게 아니에요

섹션 제목: “session.status_idle이 왔는데 일이 끝난 게 아니에요”
  • 원인: stop_reason.type이 requires_action이면 도구 승인이나 사용자 정의 도구 결과를 기다리는 중이에요. 워크플로를 쓰면 run이 안 끝났거나 예산에 막혔을 수도 있어요.
  • 해결: requires_action이면 계속 듣고 응답하세요. 워크플로는 위 단계 5의 두 조건을 모두 확인하세요.

한도에 닿은 세션에 메시지를 보내면 400 오류예요

섹션 제목: “한도에 닿은 세션에 메시지를 보내면 400 오류예요”
  • 원인: budget_reached 상태의 세션은 새 일을 시작하는 이벤트(user.message)를 받지 않아요.
  • 해결: 세션의 usage.list_cost보다 1센트 이상 큰 값으로 예산을 갱신하거나 예산을 제거하세요. 새 값이 현재 소비한 비용보다 커야 해요.

웹 검색·가져오기가 아무 결과도 못 가져와요

섹션 제목: “웹 검색·가져오기가 아무 결과도 못 가져와요”
  • 원인: 환경이 limited 네트워킹인데 allowed_hosts에 호스트를 나열하지 않았어요. 이 목록은 웹 도구에도 적용돼요.
  • 해결: 필요한 사이트만 allowed_hosts에 넣거나, 웹 도구를 쓰지 않는 작업이라면 도구를 꺼 두세요. unrestricted는 샌드박스 전체의 외부 통신을 열기 때문에 비밀 값과 민감한 파일을 샌드박스에 두지 마세요.

에이전트 생성이나 ant__ 이름 오류가 나요

섹션 제목: “에이전트 생성이나 ant__ 이름 오류가 나요”
  • 원인: multiagent를 켤 때 ant__로 시작하는 사용자 정의 도구 이름은 예약돼 있어서, 있으면 갱신이 400 오류가 나요.
  • 해결: 도구 이름을 바꾸거나 지우고 같은 갱신에서 워크플로를 켜세요.

같은 코드를 다시 실행했더니 에이전트가 계속 늘어요

섹션 제목: “같은 코드를 다시 실행했더니 에이전트가 계속 늘어요”
  • 원인: 준비 코드(agents.create)를 실행 경로에 넣었어요.
  • 해결: 준비와 실행을 분리하세요. 저장한 에이전트 ID로 세션만 만들고, 설정을 바꿀 때는 에이전트를 갱신하세요.
  • 예산은 습관처럼. 세션마다 budget을 달고, 작은 값(예: 2달러)에서 시작해 usage.list_cost를 보며 늘리세요. 이 예제의 "200"은 2달러예요. 예산은 한 세션의 상한이고, 크론 배포(scheduled deployments)에서는 실행마다 따로 적용돼요.
  • 툴셋은 필요한 것만. 웹 도구가 필요 없으면 끄고, 필요하면 allowed_hosts와 allowed_domains로 사이트를 제한하세요.
  • 도구 권한도 설계하세요. 에이전트의 도구는 권한 정책(always_allow, always_ask, auto)으로 승인 방식을 정해요. 정책별 기본 동작은 공식 권한 정책 문서에서 확인하세요. 아무도 지켜보지 않는 실행이라면 승인이 필요한 호출을 거부하게 두는 쪽이 안전해요.
  • 문서·웹에 숨은 지시를 경계하세요. 에이전트가 읽는 문서나 웹 페이지에 “이 파일을 외부로 보내라” 같은 지시가 숨어 있을 수 있어요(프롬프트 인젝션). 샌드박스에 비밀 값을 두지 말고, MCP 서버 같은 외부 도구의 인증 정보는 공식 문서의 vault(보관소)에 맡기세요.
  • Agent SDK와 같이 쓰는 법. 개발 중에는 Agent SDK로 내 컴퓨터에서 빠르게 시험하고, 사람 없이 오래 도는 일이나 호스팅이 필요한 일은 Managed Agents로 옮기는 흐름이 자연스러워요(03편).
  • 모델 분담. 본 에이전트는 큰 모델, 단순 읽기 작업은 싼 모델 에이전트로 나누면 비용이 줄어요(C15).

출처

마지막 확인일: 2026년 10월 13일

가격·한도·메뉴 이름은 자주 바뀌어요. 중요한 결정 전에는 위 공식 페이지에서 최신 내용을 다시 확인하세요.