예약 디플로이먼트

예약 디플로이먼트 (Scheduled deployments)

**예약 디플로이먼트(scheduled deployment)**는 에이전트가 자율적으로 세션을 시작하게 해서, 예측 가능한 주기로 작업을 완료하게 해줘요. 디플로이먼트는 Claude API의 일부인 Deployments API로 만들고 관리해요. 팀이 어떤 작업을 스케줄로 돌리는지에 대한 배경과 사례는 블로그의 scheduled deployments and vaults in Claude Managed Agents를 참고하세요.

출처: 문서

본문

예약 디플로이먼트는 에이전트가 자율적으로 세션을 시작하게 해주어, 예측 가능한 주기로 작업을 완료하게 해줘요. 디플로이먼트는 Claude API의 일부인 Deployments API로 만들고 관리해요.

팀이 어떤 작업을 스케줄로 실행하는지에 대한 출시 배경과 사례는 블로그의 scheduled deployments and vaults in Claude Managed Agents를 참고하세요.

Create a scheduled deployment

디플로이먼트를 만들 때 schedule 외에 실행에 필요한 세션 구성도 전달해요.

  • Deployments require agent configuration and environment configuration, and optionally accept files, GitHub, memory stores, and vaults. A deployment that targets a self-hosted environment can attach memory stores; file and github_repository resources require a cloud environment. The Claude Console deployment form does not currently offer memory stores for self-hosted environments; attach them through the API or an SDK instead.
  • Deployments also require at least one initial event, a user.message or user.define_outcome, that starts each session's work. In a deployment file for ant apply, the text below the frontmatter becomes that user.message.
  • In the schedule, you define a cron expression and a timezone. Maximum granularity supported is at the minute level.
```bash cURL curl --fail-with-body -sS "https://api.anthropic.com/v1/deployments?beta=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" \ -H "content-type: application/json" \ -d @- < ```bash CLI ant apply deployment.md ```
<File filename="deployment.md">
  ```markdown
  ---
  name: Weekly compliance scan
  agent: agent_011CYm1BLqPXpQRk5khsSXrs
  environment_id: env_01595EKxaaTTGwwY3kyXdtbs
  schedule:
    type: cron
    expression: "0 20 * * 5"
    timezone: America/New_York
  ---

  Run the weekly compliance scan.
  ```
</File>
deployment = client.beta.deployments.create(
    name="Weekly compliance scan",
    agent=agent.id,
    environment_id=environment.id,
    initial_events=[
        {
            "type": "user.message",
            "content": [{"type": "text", "text": "Run the weekly compliance scan."}],
        },
    ],
    schedule={
        "type": "cron",
        "expression": "0 20 * * 5",
        "timezone": "America/New_York",
    },
)
const deployment = await client.beta.deployments.create({
  name: "Weekly compliance scan",
  agent: agent.id,
  environment_id: environment.id,
  initial_events: [
    {
      type: "user.message",
      content: [{ type: "text", text: "Run the weekly compliance scan." }],
    },
  ],
  schedule: {
    type: "cron",
    expression: "0 20 * * 5",
    timezone: "America/New_York",
  },
});
var deployment = await client.Beta.Deployments.Create(new()
{
    Name = "Weekly compliance scan",
    Agent = agent.ID,
    EnvironmentID = environment.ID,
    InitialEvents =
    [
        new BetaManagedAgentsUserMessageEventParams
        {
            Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
            Content =
            [
                new BetaManagedAgentsTextBlock
                {
                    Type = BetaManagedAgentsTextBlockType.Text,
                    Text = "Run the weekly compliance scan.",
                },
            ],
        },
    ],
    Schedule = new BetaManagedAgentsScheduleParams
    {
        Type = BetaManagedAgentsScheduleParamsType.Cron,
        Expression = "0 20 * * 5",
        Timezone = "America/New_York",
    },
});
deployment, err := client.Beta.Deployments.New(ctx, anthropic.BetaDeploymentNewParams{
	Name:          "Weekly compliance scan",
	Agent:         anthropic.BetaDeploymentNewParamsAgentUnion{OfString: anthropic.String(agent.ID)},
	EnvironmentID: environment.ID,
	InitialEvents: []anthropic.BetaManagedAgentsDeploymentInitialEventParamsUnion{{
		OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
			Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
			Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
				OfText: &anthropic.BetaManagedAgentsTextBlockParam{
					Type: anthropic.BetaManagedAgentsTextBlockTypeText,
					Text: "Run the weekly compliance scan.",
				},
			}},
		},
	}},
	Schedule: anthropic.BetaManagedAgentsScheduleParams{
		Type:       anthropic.BetaManagedAgentsScheduleParamsTypeCron,
		Expression: "0 20 * * 5",
		Timezone:   "America/New_York",
	},
})
if err != nil {
	panic(err)
}
var deployment = client.beta().deployments().create(
    DeploymentCreateParams.builder()
        .name("Weekly compliance scan")
        .agent(agent.id())
        .environmentId(environment.id())
        .addInitialEvent(
            BetaManagedAgentsUserMessageEventParams.builder()
                .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
                .addTextContent("Run the weekly compliance scan.")
                .build()
        )
        .schedule(
            BetaManagedAgentsScheduleParams.builder()
                .type(BetaManagedAgentsScheduleParams.Type.CRON)
                .expression("0 20 * * 5")
                .timezone("America/New_York")
                .build()
        )
        .build()
);
$deployment = $client->beta->deployments->create(
    name: 'Weekly compliance scan',
    agent: $agent->id,
    environmentID: $environment->id,
    initialEvents: [
        [
            'type' => 'user.message',
            'content' => [['type' => 'text', 'text' => 'Run the weekly compliance scan.']],
        ],
    ],
    schedule: [
        'type' => 'cron',
        'expression' => '0 20 * * 5',
        'timezone' => 'America/New_York',
    ],
);
deployment = client.beta.deployments.create(
  name: "Weekly compliance scan",
  agent: agent.id,
  environment_id: environment.id,
  initial_events: [
    {
      type: "user.message",
      content: [{type: "text", text: "Run the weekly compliance scan."}]
    }
  ],
  schedule: {
    type: "cron",
    expression: "0 20 * * 5",
    timezone: "America/New_York"
  }
)
[`ant apply`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/apply) prints the new deployment's ID and records it in `claude-lock.json`. To see the deployment object, run `ant beta:deployments retrieve`.

응답에는 다음 예정 실행 시각이 담긴 schedule.upcoming_runs_at이 채워진 디플로이먼트 객체가 포함되어, 스케줄이 올바르게 설정됐는지 확인할 수 있어요.

{
  "id": "depl_01xyz",
  "status": "active",
  "paused_reason": null,
  "schedule": {
    "type": "cron",
    "expression": "0 20 * * 5",
    "timezone": "America/New_York",
    "last_run_at": null,
    "upcoming_runs_at": [
      "2026-05-09T00:00:00Z",
      "2026-05-16T00:00:00Z",
      "2026-05-23T00:00:00Z"
    ]
  }
}

예정 실행 타임스탬프는 정확히 구성된 스케줄을 반영해요. 그러나 부하를 분산하기 위해 실제 실행은 실행 사이 간격의 최대 15%, 최소 5초, 최대 9분의 지터(jitter)를 적용해요.

조직당 최대 1,000개 예약 디플로이먼트가 지원돼요. 더 필요하면 Anthropic 지원에 연락하세요.

전체 파라미터와 응답 스키마는 Create Deployment reference를 참고하세요.

Cron and timezone semantics

  • Expression: Standard POSIX cron (minute hour day-of-month month day-of-week). You can generate and validate these cron expressions in the Claude Console.
  • Timezone: IANA timezone identifier (for example, "America/Los_Angeles").
  • DST: Cron schedules use literal wall-clock matching, so "0 20 * * *" in America/New_York fires at 8:00 PM local time regardless of whether EST or EDT is in effect.
Wall-clock times that do not exist on a spring-forward day (such as 2 AM) are not triggered. Wall-clock times that occur twice on a fall-back day fire twice. Schedule outside the 1–3 AM local window, or use UTC, when missed or duplicate executions are unacceptable.

Set a budget on each run

디플로이먼트를 만들거나 업데이트할 때 선택적 budget 객체를 전달하세요. 세션 예산과 같은 형태예요. 디플로이먼트는 상한을 시작하는 각 세션에 복사하므로, 예산은 여러 실행에 걸친 누적 상한이 아니라 각 실행을 개별적으로 제한해요. "2000" 상한의 디플로이먼트는 매 실행마다 약 $20까지 쓸 수 있어요.

디플로이먼트가 시작한 세션은 다른 예산 세션과 정확히 똑같이 동작해요: 자체 list cost가 상한에 도달하면 budget_reached로 일시 중지돼요. 디플로이먼트의 예산을 변경하면 이후에 시작하는 실행에 적용되며, 이미 실행 중인 세션은 시작할 때 가진 상한을 유지하고, 그 상한은 세션 자체를 통해 변경할 수 있어요. 세션 예산과 달리 디플로이먼트의 예산은 "budget": null로 제거하고 나중에 다시 설정할 수 있어요.

다음 예시는 기존 디플로이먼트에 예산을 설정해요:

curl --fail-with-body -sS "https://api.anthropic.com/v1/deployments/$DEPLOYMENT_ID?beta=true" \
  -H "x-api-key: $ANTHR...KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  -H "content-type: application/json" \
  -d @- <<'EOF'
{
  "budget": {
    "type": "limit",
    "max_list_cost": {"amount": "2000", "currency": "USD"}
  }
}
EOF

Deployment runs

디플로이먼트는 여러 이유로 트리거에 실패할 수 있어요: 예를 들어 environment 리소스가 보관됐거나, 세션 생성이 속도 제한에 걸렸을 수 있어요. 디플로이먼트 실행을 시도할 때마다 deployment run 레코드가 생성되어, 세션 수명주기와 무관하게 성공과 실패를 추적하게 해줘요.

성공적인 디플로이먼트는 활성 세션을 만들고, 성공한 deployment run에는 연결된 session_id가 포함돼요. 세션 수명주기를 따라가려면 이벤트 스트림이나 웹훅으로 세션 이벤트를 추적하세요. 디플로이먼트 수명주기 변경과 각 예약 실행의 결과도 웹훅 이벤트로 전달되며, Supported event types의 Deployment events와 Deployment run events 탭에 나열돼요.

디플로이먼트의 모든 deployment run을 다음과 같이 나열하세요:

```bash cURL curl --fail-with-body -sS "https://api.anthropic.com/v1/deployment_runs?beta=true&deployment_id=$DEPLOYMENT_ID" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployment-runs list --deployment-id "$DEPLOYMENT_ID"
for run in client.beta.deployment_runs.list(
    deployment_id=deployment.id,
):
    print(run.created_at, run.session_id or run.error.type)
for await (const run of client.beta.deploymentRuns.list({
  deployment_id: deployment.id,
})) {
  console.log(run.created_at, run.session_id ?? run.error?.type);
}
var runs = await client.Beta.DeploymentRuns.List(
    new() { DeploymentID = deployment.ID }
);
await foreach (var run in runs.Paginate())
{
    // The Error union exposes .Message directly; the discriminator is read
    // from .Json until a common .Type accessor is added.
    var outcome = run.SessionID ?? run.Error!.Json.GetProperty("type").GetString();
    Console.WriteLine($"{run.CreatedAt} {outcome}");
}
runs := client.Beta.DeploymentRuns.ListAutoPaging(ctx, anthropic.BetaDeploymentRunListParams{
	DeploymentID: anthropic.String(deployment.ID),
})
for runs.Next() {
	run := runs.Current()
	if run.SessionID != "" {
		fmt.Println(run.CreatedAt.Format(time.RFC3339), run.SessionID)
	} else {
		fmt.Println(run.CreatedAt.Format(time.RFC3339), run.Error.Type)
	}
}
if err := runs.Err(); err != nil {
	panic(err)
}
for (var run : client.beta().deploymentRuns().list(
        DeploymentRunListParams.builder()
            .deploymentId(deployment.id())
            .build()).autoPager()) {
    // The Error union does not yet expose common .type()/.message()
    // accessors; .toString() includes both.
    IO.println(run.createdAt() + " "
        + run.sessionId().orElseGet(() -> run.error().orElseThrow().toString()));
}
foreach ($client->beta->deploymentRuns->list(
    deploymentID: $deployment->id,
)->pagingEachItem() as $run) {
    $outcome = $run->sessionID ?? $run->error->type;
    echo "{$run->createdAt->format(DATE_ATOM)} {$outcome}\n";
}
client.beta.deployment_runs.list(
  deployment_id: deployment.id
).auto_paging_each do
  puts "#{it.created_at} #{it.session_id || it.error.type}"
end

오류가 있는 deployment run만 필터링할 수도 있어요:

```bash cURL curl --fail-with-body -sS "https://api.anthropic.com/v1/deployment_runs?beta=true&deployment_id=$DEPLOYMENT_ID&has_error=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployment-runs list --deployment-id "$DEPLOYMENT_ID" --has-error
for run in client.beta.deployment_runs.list(
    deployment_id=deployment.id,
    has_error=True,
):
    print(run.created_at, run.error.type, run.error.message)
for await (const run of client.beta.deploymentRuns.list({
  deployment_id: deployment.id,
  has_error: true,
})) {
  console.log(run.created_at, run.error?.type, run.error?.message);
}
var failedRuns = await client.Beta.DeploymentRuns.List(
    new() { DeploymentID = deployment.ID, HasError = true }
);
await foreach (var failedRun in failedRuns.Paginate())
{
    var error = failedRun.Error!;
    var errorType = error.Json.GetProperty("type").GetString();
    Console.WriteLine($"{failedRun.CreatedAt} {errorType} {error.Message}");
}
failedRuns := client.Beta.DeploymentRuns.ListAutoPaging(ctx, anthropic.BetaDeploymentRunListParams{
	DeploymentID: anthropic.String(deployment.ID),
	HasError:     anthropic.Bool(true),
})
for failedRuns.Next() {
	failedRun := failedRuns.Current()
	fmt.Println(failedRun.CreatedAt.Format(time.RFC3339), failedRun.Error.Type, failedRun.Error.Message)
}
if err := failedRuns.Err(); err != nil {
	panic(err)
}
for (var run : client.beta().deploymentRuns().list(
        DeploymentRunListParams.builder()
            .deploymentId(deployment.id())
            .hasError(true)
            .build()).autoPager()) {
    IO.println(run.createdAt() + " " + run.error().orElseThrow());
}
foreach ($client->beta->deploymentRuns->list(
    deploymentID: $deployment->id,
    hasError: true,
)->pagingEachItem() as $run) {
    echo "{$run->createdAt->format(DATE_ATOM)} {$run->error->type} {$run->error->message}\n";
}
client.beta.deployment_runs.list(
  deployment_id: deployment.id,
  has_error: true
).auto_paging_each do
  puts "#{it.created_at} #{it.error.type} #{it.error.message}"
end

실패한 run에는 왜 세션 생성이 거부됐는지 설명하는 type의 error가 포함돼요(예: environment_archived_error, agent_archived_error, session_rate_limited_error). 모든 필터 파라미터와 응답 스키마는 List Deployment Runs reference를 참고하세요.

{
  "type": "deployment_run",
  "id": "drun_01abc124",
  "deployment_id": "depl_01xyz",
  "trigger_context": { "type": "schedule", "scheduled_at": "2026-05-09T00:00:00Z" },
  "session_id": null,
  "error": {
    "type": "environment_archived_error",
    "message": "environment `env_01abc` is archived"
  },
  "agent": { "type": "agent", "id": "agent_01ghi789", "version": 3 },
  "created_at": "2026-05-09T00:00:01Z"
}

ID로 단일 run을 조회하려면 GET /v1/deployment_runs/{deployment_run_id}를 호출하세요. deployment_run 웹훅 이벤트는 run ID를 data.id로 전달해요.

Managing deployment lifecycle

각 수명주기 변경은 웹훅 이벤트를 발행하므로, 폴링 없이 일시 중지·재개·보관된 디플로이먼트에 반응할 수 있어요. Deployment events 탭을 참고하세요.

Pause는 앞으로 발생하는 예약 트리거를 억제해요. 이전 deployment run의 실행 중인 세션은 계속 실행됩니다. 일시 중지 중에도 run 엔드포인트를 통한 수동 실행은 여전히 허용돼요. 일시 중지는 paused_reason을 {"type": "manual"}로 설정하고, 재개는 그것을 지워요.

```bash cURL curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/deployments/$DEPLOYMENT_ID/pause?beta=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployments pause --deployment-id "$DEPLOYMENT_ID"
client.beta.deployments.pause(deployment.id)
await client.beta.deployments.pause(deployment.id);
await client.Beta.Deployments.Pause(deployment.ID);
if _, err := client.Beta.Deployments.Pause(ctx, deployment.ID, anthropic.BetaDeploymentPauseParams{}); err != nil {
	panic(err)
}
client.beta().deployments().pause(deployment.id());
$client->beta->deployments->pause($deployment->id);
client.beta.deployments.pause(deployment.id)

Unpause는 다음 예약 발생부터 스케줄을 재개해요. 놓친 트리거는 소급해서 채워지지 않아요.

```bash cURL curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/deployments/$DEPLOYMENT_ID/unpause?beta=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployments unpause --deployment-id "$DEPLOYMENT_ID"
client.beta.deployments.unpause(deployment.id)
await client.beta.deployments.unpause(deployment.id);
await client.Beta.Deployments.Unpause(deployment.ID);
if _, err := client.Beta.Deployments.Unpause(ctx, deployment.ID, anthropic.BetaDeploymentUnpauseParams{}); err != nil {
	panic(err)
}
client.beta().deployments().unpause(deployment.id());
$client->beta->deployments->unpause($deployment->id);
client.beta.deployments.unpause(deployment.id)

Archive는 pause와 달리 종결적이에요: 스케줄이 종료되고 디플로이먼트를 수정할 수 없어요.

```bash cURL curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/deployments/$DEPLOYMENT_ID/archive?beta=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployments archive --deployment-id "$DEPLOYMENT_ID"
client.beta.deployments.archive(deployment.id)
await client.beta.deployments.archive(deployment.id);
await client.Beta.Deployments.Archive(deployment.ID);
if _, err := client.Beta.Deployments.Archive(ctx, deployment.ID, anthropic.BetaDeploymentArchiveParams{}); err != nil {
	panic(err)
}
client.beta().deployments().archive(deployment.id());
$client->beta->deployments->archive($deployment->id);
client.beta.deployments.archive(deployment.id)

Failure behavior

세션 생성 속도 제한 응답은 재시도 없이 즉시 session_rate_limited_error run으로 기록되며, 스케줄은 다음 예약 발생에 다시 시도해요. 세션 내 기본 API 호출의 속도 제한은 세션 자체가 처리해요.

디플로이먼트의 에이전트가 보관됐으면 디플로이먼트는 같은 작업에서 자동으로 보관돼요. 에이전트가 삭제됐으면 다음 예약 트리거가 누락된 에이전트를 감지하고 디플로이먼트를 자동으로 보관해요. 두 경우 모두 deployment run이 기록되지 않아요. 에이전트가 참조하는 서브에이전트가 보관됐으면 다음 트리거는 error.type: "agent_archived_error"로 실패한 run을 기록하고 디플로이먼트가 자동으로 일시 중지되므로 에이전트를 업데이트하고 재개할 수 있어요. 보관된 환경·볼트 같은 다른 복구 불가능한 세션 생성 오류도 똑같이 동작해요: 트리거가 실패한 run을 기록하고 디플로이먼트가 자동으로 일시 중지됩니다. 디플로이먼트의 paused_reason.error.type은 실패한 run의 error.type을 반영해요.

Trigger a manual run

스케줄 밖에서 디플로이먼트를 실행하려면 run 엔드포인트를 호출하세요. 이 호출은 즉시 세션을 만들고 trigger_context.type: "manual"인 deployment run을 씁니다. 이렇게 하면 스케줄에 확정하기 전에 디플로이먼트를 테스트할 수 있어요.

```bash cURL curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/deployments/$DEPLOYMENT_ID/run?beta=true" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" ```
ant beta:deployments run --deployment-id "$DEPLOYMENT_ID"
run = client.beta.deployments.run(deployment.id)
const run = await client.beta.deployments.run(deployment.id);
var manualRun = await client.Beta.Deployments.Run(deployment.ID);
manualRun, err := client.Beta.Deployments.Run(ctx, deployment.ID, anthropic.BetaDeploymentRunParams{})
if err != nil {
	panic(err)
}
var run = client.beta().deployments().run(deployment.id());
$run = $client->beta->deployments->run($deployment->id);
run = client.beta.deployments.run(deployment.id)

더 알아보기 (Learn more)