---
title: 워크플로 실행
url: https://platform.claude.com/docs/ko/managed-agents/workflow-runs
description: 에이전트의 워크플로 실행을 추적하는 방법을 설명합니다. 실행의 상태와 이벤트, 작업 완료 시점, 실행이 열려 있는 동안의 요청, 예산, 제한을 다룹니다.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

**Workflow**(워크플로)는 에이전트가 여러 에이전트를 실행하고 그 에이전트들이 반환한 결과를 결합하기 위해 작성하는 프로그램입니다. **Workflow run**(워크플로 실행)은 하나의 워크플로를 실행하는 것입니다. **[Dynamic workflows](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#dynamic-workflows)**(동적 워크플로)는 에이전트가 워크플로를 작성하고 실행을 시작할 수 있게 하는 기능입니다. 에이전트의 `multiagent` (csharp, go: `Multiagent`) 블록에 있는 `workflows` (csharp, go: `Workflows`) 설정으로 이 기능을 켜거나 끕니다.

서버는 워크플로를 백그라운드에서 실행합니다. 워크플로의 에이전트는 워크플로에 필요할 때 서버가 생성하는 [세션 스레드](https://platform.claude.com/docs/ko/managed-agents/session-threads)에서 작업합니다. 실행은 세션의 [이벤트 스트림](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming)에서 추적합니다. 실행을 중지하려면 에이전트에게 중지를 요청하세요([실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open) 참조).

## 동적 워크플로의 작동 방식

세션이 실행하는 에이전트는 사용자가 설명한 작업에 맞춰 각 워크플로를 작성합니다. 워크플로는 프로그램입니다. 다른 에이전트를 실행하고, 각 에이전트가 반환한 결과를 수집하고, 그 결과를 결합합니다. 이를 통해 에이전트는 수백 개의 문서를 검토하는 것처럼 하나의 대화로 처리하기에는 너무 큰 작업을 맡을 수 있습니다. 실행 중에 에이전트는 계속 작업하거나 턴을 종료할 수 있으며, 실행 상태를 확인할 수도 있습니다.

<Frame>
  ![Workflow run(워크플로 실행)의 계층: 실행, 실행의 두 phase(단계), 그리고 각 단계의 agent thread(에이전트 스레드). 결과는 한 단계에서 다음 단계로 전달됩니다.](https://platform.claude.com/docs/ko/images/workflow-anatomy.svg)
</Frame>

다이어그램은 하나의 예시를 보여 줍니다. 에이전트가 작성하는 각 워크플로에는 고유한 단계와 에이전트가 있습니다. 실행에는 다음과 같은 계층이 있습니다:

* **워크플로 실행:** 서버는 워크플로를 하나의 워크플로 실행으로 백그라운드에서 실행합니다. 하나의 세션에서 여러 실행이 동시에 열려 있을 수 있습니다.
* **단계:** 워크플로는 작업을 "phase"(단계)로 나눌 수 있습니다. 단계는 "Read the contracts"와 같이 이름이 붙은 실행의 한 구간입니다. 실행의 진행 상황은 단계 이벤트로 추적합니다.
* **에이전트 스레드:** 각 단계에서 프로그램은 에이전트를 실행합니다. 각 에이전트는 프로그램이 작성한 프롬프트를 바탕으로 자체 [세션 스레드](https://platform.claude.com/docs/ko/managed-agents/session-threads)에서 작업합니다. 실행 안의 에이전트는 프로그램이 직접 정의하는 인라인 에이전트일 수도 있고, 사용자가 [`workflows` (csharp, go: `Workflows`)의 `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) 목록](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#predefined-and-inline-agents)에 나열하는 사전 정의 에이전트일 수도 있습니다. 각 스레드에 표시되는 내용은 [실행의 스레드](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#a-runs-threads)를 참조하세요.

프로그램은 다음을 수행할 수 있습니다:

* **에이전트를 동시에 실행:** 프로그램은 여러 에이전트를 동시에 실행할 수 있으며, 이를 "fanning out"(팬아웃)이라고 합니다. 다이어그램에서는 첫 번째 단계에서 세 에이전트가 계약서를 읽습니다.
* **한 에이전트의 결과를 다른 에이전트에 전달:** 각 에이전트는 결과를 프로그램에 반환합니다. 프로그램은 그 결과를 다른 에이전트에 전달할 수 있습니다. 다이어그램에서는 두 번째 단계의 에이전트가 처음 세 에이전트가 반환한 결과를 가지고 작업합니다. 실행의 에이전트들은 또한 세션의 샌드박스에서 동일한 파일을 가지고 작업합니다.
* **다음 작업을 스스로 진행:** 에이전트의 결과는 세션이 실행하는 에이전트가 아니라 프로그램으로 전달됩니다. 프로그램이 다음에 실행할 에이전트를 결정하고, 그 에이전트들의 프롬프트를 작성합니다.
* **반복 및 선택:** 단계 안에서 프로그램은 작업을 반복하고, 에이전트가 반환한 결과를 바탕으로 다음에 할 일을 선택할 수 있습니다. 예를 들어 검토를 통과하거나 정해진 횟수를 모두 사용할 때까지 초안을 수정하게 할 수 있습니다. 다이어그램에서는 프로그램이 두 번째 단계 안에서 한 작업을 반복할 수 있습니다.
* **실패한 에이전트 처리:** 에이전트 중 하나가 실패하면, 서버는 보통 실행의 다른 에이전트가 계속 작업하는 동안 해당 에이전트를 새 스레드에서 한 번 더 실행합니다. 다시 실패하면 프로그램은 그 실패를 처리하거나 실패로 인해 실행이 종료되도록 둘 수 있습니다. 프로그램이 실패를 처리하면 실행은 여전히 `completed`로 종료될 수 있으므로, 세션이 실행하는 에이전트에게 무엇이 실패했는지 보고하도록 지시하세요.

실행이 종료되면 세션이 실행하는 에이전트는 실행이 수행한 내용을 읽을 턴을 받습니다. 그런 다음 사용자에게 답변하거나 다른 실행을 시작할 수 있습니다. 그 턴이 나중에 오거나 오지 않는 경우는 [실행 이벤트](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#run-events)에 나와 있습니다.

실행이 작업을 수행하는 방식, 예를 들어 작업을 나누는 방식이나 에이전트가 실패했을 때 수행할 작업을 안내할 수 있습니다. [실행을 사용할 시점을 에이전트에 알려주기](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#tell-the-agent-when-to-use-a-run)를 참조하세요.

실행이 시작되기 전에 승인 절차는 없습니다. 에이전트는 워크플로를 작성하는 바로 그 턴에서 실행을 시작합니다. 계획을 먼저 검토하려면 에이전트에게 실행을 설명하고 사용자의 승인을 기다리도록 지시하세요. 실행이 시작되면 `workflow_run.created`에 실행의 이름, 설명, 단계가 표시됩니다. 승인하지 않은 실행이 시작되면 에이전트에게 중지를 요청하세요([실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open) 참조).

## 실행의 상태 전환 방식

<Frame>
  ![Workflow run(워크플로 실행)의 상태: running(실행 중), idle(유휴), ended(종료됨), 그리고 실행을 한 상태에서 다른 상태로 전환시키는 요인.](https://platform.claude.com/docs/ko/images/workflow-run-states.svg)
</Frame>

실행은 실행 중 상태 또는 유휴 상태로 시작합니다. 예를 들어 예산에 도달하면 실행 중인 실행이 일시 중지되어 유휴 상태가 되며, 이후 예산을 늘리거나 제거하면 중단으로도 일시 중지된 경우가 아닌 한 다시 실행됩니다. 실행 중인 실행은 워크플로가 완료되거나, 에이전트가 실행을 중지하거나, 실행이 실패하거나, 수명이 지나거나, 세션이 보관되면 종료됩니다. 유휴 상태의 실행도 종료될 수 있습니다. 예를 들어 에이전트가 실행을 중지하거나 세션이 보관되는 경우입니다.

실행은 실행 중이든 유휴 상태이든 `workflow_run.created` 이벤트부터 `workflow_run.status_ended` 이벤트까지 **열려 있는** 상태입니다. 실행은 예를 들어 세션의 예산에 도달하여 일시 중지된 동안 유휴 상태입니다. 실행에는 [수명](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#limits)이 있으며, 에이전트는 실행을 시작할 때 이를 설정할 수 있습니다. 실행이 클라이언트를 기다리며 보내는 시간도 수명에 포함됩니다. 일시 중지되어도 실행의 수명은 계속 흐르므로, 일시 중지 상태로 남아 있는 실행은 `timeout_error`로 종료될 수 있습니다. 다음 이벤트는 실행의 시작, 단계, 종료를 보고합니다. 예산에 도달하여 일시 중지될 때도 이벤트가 하나 전송됩니다. 중단 후의 일시 중지는 이벤트를 전송하지 않을 수 있습니다. 모든 `workflow_run.*` 이벤트에는 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)가 포함되며, 이 값은 실행이 생성되지 않은 경우의 `workflow_run.error`에서만 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)입니다.

## 실행 이벤트

실행 이벤트는 기본 스레드의 스트림인 세션의 이벤트 스트림으로 도착하며, 세션의 이벤트를 나열해도 반환됩니다. 실행 이벤트는 [웹훅](https://platform.claude.com/docs/ko/managed-agents/webhooks)을 트리거하지 않습니다. 실행 스레드의 상태 이벤트도 같은 스트림으로 도착합니다. 각 이벤트는 `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)에 해당 스레드를 명시하며, 실행의 스레드는 `session.thread_created` 이벤트에 해당 실행의 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)가 있었던 스레드입니다.

| 이벤트                                                      | 도착 시점                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | 수행할 작업                                                                                                                                                                                                                                                                                                                                                                       |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `workflow_run.created`                                   | 에이전트가 실행을 시작했습니다. `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)(`wrun_…`), 실행의 `name` (csharp, go: `Name`)과 `description` (csharp, go: `Description`), 그리고 워크플로가 선언한 단계인 `phases` (csharp, go: `Phases`)가 포함되며, 각 단계에는 `id` (csharp, go: `ID`), `name` (csharp, go: `Name`), `description` (csharp, go: `Description`)이 있습니다. 워크플로가 설명을 제공하지 않으면 `description` (csharp, go: `Description`)은 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)입니다. `phases` (csharp, go: `Phases`)는 항상 존재하며 비어 있을 수 있습니다. 실행과 단계의 `name` (csharp, go: `Name`)과 `description` (csharp, go: `Description`)은 모델이 작성한 텍스트이므로 사용자 요청의 단어를 반복할 수 있습니다. 실행의 `name` (csharp, go: `Name`)은 서버가 할당한 이름일 수도 있습니다. | 실행을 열린 상태로 추적합니다. 실행의 `name` (csharp, go: `Name`)과 `phases` (csharp, go: `Phases`) 대비 진행 상황을 표시합니다.                                                                                                                                                                                                                                                                          |
| `workflow_run.status_running`                            | 실행이 실제로 실행되기 시작할 때(`created` 이후 한참 뒤일 수 있음), 그리고 예산으로 인한 일시 중지 후 재개될 때마다 도착합니다. 중단 후의 재개는 이 이벤트를 전송하지 않을 수 있습니다. 유휴 상태로 시작하는 실행은 `workflow_run.status_idle`을 먼저 받을 수 있습니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | 실행을 실행 중으로 표시합니다.                                                                                                                                                                                                                                                                                                                                                            |
| `workflow_run.status_idle`                               | 예를 들어 세션의 예산에 도달하여 실행이 일시 중지되었습니다. 이벤트는 그 이유를 알려 주지 않습니다. 중단 후의 일시 중지는 이 이벤트를 전송하지 않을 수 있습니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | 계속하려면 [비용 및 예산](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#cost-and-budgets) 또는 [실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open)를 참조하세요.                                                                                                                                       |
| `workflow_run.phase_started`, `workflow_run.phase_ended` | 워크플로가 단계에 진입하거나 단계를 벗어났거나, 실행이 종료되면서 아직 열려 있던 단계가 닫혔습니다. 종료 이벤트는 단계의 작업이 완료되었는지 알려 주지 않습니다. 두 이벤트 모두 `workflow_run_phase_id` (java: `workflowRunPhaseId`; php: `workflowRunPhaseID`; csharp, go: `WorkflowRunPhaseID`)를 포함합니다. 종료 이벤트에는 자신이 닫는 시작 이벤트의 `id` (csharp, go: `ID`)인 `phase_started_id` (java: `phaseStartedId`; php: `phaseStartedID`; csharp, go: `PhaseStartedID`)도 있습니다. 두 이벤트 모두 단계의 이름은 없습니다. `workflow_run.created`의 `phases` (csharp, go: `Phases`)에서 `workflow_run_phase_id` (java: `workflowRunPhaseId`; php: `workflowRunPhaseID`; csharp, go: `WorkflowRunPhaseID`)로 조회하세요.                                                                                                                                                          | 진행 상황을 업데이트합니다. 단계는 `phases` (csharp, go: `Phases`)의 순서대로 한 번에 하나씩, 각각 최대 한 번 실행되지만 API가 이를 보장하지는 않습니다. 단계의 종료는 `phase_started_id` (java: `phaseStartedId`; php: `phaseStartedID`; csharp, go: `PhaseStartedID`)로 시작과 짝지으세요. 완료되는 실행에서도 둘 이상의 열린 단계, `phases` (csharp, go: `Phases`)에 없는 단계, 나열되었지만 시작되지 않는 단계를 처리하세요. 시작되는 모든 단계는 실행의 `workflow_run.status_ended` 전에 종료됩니다. |
| `workflow_run.status_ended`                              | 실행이 종료되었습니다. 항상 실행의 `workflow_run.*` 이벤트 중 마지막입니다. `result` (csharp, go: `Result`)를 포함합니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `result` (csharp, go: `Result`)를 읽으세요(다음 표 참조). 그런 다음 에이전트는 실행이 어떻게 종료되었는지 읽을 턴을 받습니다. 예산에 도달했거나 기본 스레드가 클라이언트를 기다리는 동안에는 그 턴이 나중에 옵니다. 중단 후에는 그 턴이 오지 않을 수 있습니다. `user.message`를 보내거나 `result` (csharp, go: `Result`)를 직접 읽으세요. 보관 또는 종료(termination) 후에는 그 턴이 오지 않습니다.                                                                                                     |
| `workflow_run.error`                                     | 서버가 실행의 오류 또는 거부한 시작을 보고합니다. `error`로 종료되는 실행은 `workflow_run.status_ended` 전에 같은 오류와 함께 이 이벤트를 받습니다. `error` (csharp, go: `Error`)를 포함하며, 여기에는 `type` (csharp, go: `Type`)과 로그에 기록해도 안전한 `message` (csharp, go: `Message`)가 있습니다. 실행이 생성되지 않은 경우 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)는 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)입니다.                                                                                                                                                                                                                                                                                                                                 | 로그에 기록하되, 실행의 종료로 간주하지 마세요. `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)가 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)이면 실행이 시작되지 않은 것입니다. 그렇지 않으면 `workflow_run.status_ended`가 올 때까지 실행을 계속 추적하세요.                                                                                              |

| `result`                          | 의미                                                                                                                                                                                                                                |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{"type": "completed"}`           | 워크플로가 실행을 마쳤습니다. 결과는 작업이 통과했는지 여부를 알려 주지 않습니다. 스레드의 작업이 실패했거나 스레드를 생성할 수 없었더라도 실행은 `completed`로 종료될 수 있습니다. 실패한 작업을 찾으려면 각 [실행의 스레드](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#a-runs-threads)의 이벤트를 읽으세요. |
| `{"type": "stopped"}`             | 에이전트가 실행을 중지했거나 세션이 보관되었습니다. 이벤트는 어느 쪽인지 알려 주지 않습니다. 이 두 가지를 유일한 원인으로 간주하지 마세요.                                                                                                                                                   |
| `timeout_error`가 포함된 `error`      | 실행이 에이전트가 설정할 수 있는 [수명](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#limits)에 도달했습니다.                                                                                                                         |
| `program_error`가 포함된 `error`      | 워크플로가 실패했습니다. 워크플로의 코드가 실패했거나, 제한 이외의 워크플로 규칙을 위반했습니다. 또는 실행의 스레드 중 하나가 실패했거나 생성될 수 없었고, 워크플로가 그로 인해 실행이 종료되도록 두었습니다.                                                                                                             |
| `thread_limit_error`가 포함된 `error` | 실행이 [워크플로가 시작하는 에이전트 수 제한](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#limits)을 초과했습니다.                                                                                                                      |
| `unknown_error`가 포함된 `error`      | 서버가 실행을 계속할 수 없었거나, 실행이 워크플로에 대한 서버의 다른 제한 중 하나를 초과했습니다.                                                                                                                                                                          |

오류 결과는 `{"type": "error", "error": {"type": "timeout_error", "message": "..."}}`와 같은 형태이며, 여기서 `message`는 로그에 기록해도 안전합니다. 인식할 수 없는 `result.type`은 다른 방식으로 종료된 실행으로, 인식할 수 없는 `error.type`은 오류로 처리하세요. 모델, MCP 서버, 자격 증명, 결제 등 세션이 의존하는 요소가 실패하면 실패한 스레드의 스트림이 `session.error`를 받습니다. 그것만으로는 실행이 종료되지 않습니다. 하지만 그로 인해 실행의 스레드 중 하나가 실패하고 워크플로가 그로 인해 실행이 종료되도록 두면, 실행은 `program_error`로 종료됩니다.

예를 들어 [계약 검토 에이전트](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#turn-on-dynamic-workflows)에게 300개의 계약서 중 어느 계약서에 지배권 변경 조항이 있는지 물으면, 에이전트가 실행을 시작합니다:

1. `workflow_run.created`는 실행의 이름을 "Find change-of-control clauses"로 지정하고 `phases` (csharp, go: `Phases`)에 "Read the contracts"와 "Reconcile the findings" 단계를 나열합니다. 그다음 `workflow_run.status_running`이 이어집니다.
2. 단계 이벤트가 각 단계를 표시하고, 실행이 생성하는 각 스레드는 실행의 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)와 함께 `session.thread_created`를 전송합니다.
3. `workflow_run.status_ended`가 `result: {"type": "completed"}`와 함께 도착합니다.
4. 에이전트가 "300개의 계약서 중 41개에 해당 조항이 있습니다"라고 답변하고, `session.status_idle`이 `end_turn`과 함께 도착합니다.

실행의 첫 번째 이벤트는 단계를 나열합니다:

```json
{
  "type": "workflow_run.created",
  "id": "sevt_01abc...",
  "workflow_run_id": "wrun_01J8XkN5uT3vHpLqRfWdY2",
  "name": "Find change-of-control clauses",
  "description": "Reads each contract and lists those that have the clause.",
  "phases": [
    {
      "id": "wrph_01Kd3a1f3",
      "name": "Read the contracts",
      "description": "Reads each contract for the clause."
    },
    { "id": "wrph_01Kd3b7c9", "name": "Reconcile the findings", "description": null }
  ],
  "processed_at": "2026-10-09T14:01:45Z"
}
```

각 단계 이벤트는 `workflow_run_phase_id`로 단계를 명시합니다. 이 값은 `phases`에 있는 `id`이지만, API가 이를 보장하지는 않습니다:

```json
{
  "type": "workflow_run.phase_started",
  "id": "sevt_01def...",
  "workflow_run_id": "wrun_01J8XkN5uT3vHpLqRfWdY2",
  "workflow_run_phase_id": "wrph_01Kd3a1f3",
  "processed_at": "2026-10-09T14:01:46Z"
}
```

실행의 마지막 이벤트는 실행이 어떻게 종료되었는지 보고합니다:

```json
{
  "type": "workflow_run.status_ended",
  "id": "sevt_01ghi...",
  "workflow_run_id": "wrun_01J8XkN5uT3vHpLqRfWdY2",
  "result": { "type": "completed" },
  "processed_at": "2026-10-09T14:09:12Z"
}
```

## 실행의 스레드

실행의 각 에이전트는 워크플로에 필요할 때 서버가 생성하는 자체 [세션 스레드](https://platform.claude.com/docs/ko/managed-agents/session-threads)에서 작업합니다. 실행의 스레드는 다른 하위 스레드처럼 나열하고, 읽고, 스트리밍할 수 있으며, 기본 스트림에서 해당 스레드의 도구 호출에 응답할 수 있습니다. 스레드를 중지하려면 에이전트에게 실행을 중지하도록 요청하세요([실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open) 참조).

* **그룹화:** 실행의 스레드에는 해당 실행의 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)가 있으며, 스레드를 알리는 `session.thread_created` 이벤트에도 있습니다. 다른 스레드와 그 스레드를 알리는 `session.thread_created` 이벤트에서는 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)가 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)로 설정됩니다.
* **에이전트:** `agent` (csharp, go: `Agent`)는 스레드가 실행하는 에이전트를 보여 줍니다. `multiagent` (csharp, go: `Multiagent`)의 [`workflows` (csharp, go: `Workflows`)의 `predefined_agents` (java, php: `predefinedAgents`; csharp, go: `PredefinedAgents`) 목록](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#turn-on-dynamic-workflows)에 나열한 에이전트의 경우, 나열한 서브에이전트의 스레드와 마찬가지로 `agent` (csharp, go: `Agent`)에 해당 에이전트의 `id` (csharp, go: `ID`)와 `version` (csharp, go: `Version`)이 있습니다. 워크플로가 정의하는 에이전트(인라인 에이전트)의 경우, `agent` (csharp, go: `Agent`)의 `type`은 `inline`이며 `id` (csharp, go: `ID`)나 `version` (csharp, go: `Version`)이 없습니다. 이 에이전트에는 세션 에이전트의 시스템 프롬프트가 아니라 워크플로가 작성한 시스템 프롬프트가 있습니다. 또한 워크플로가 부여한 이름과 설명이 있습니다. 워크플로가 이름을 부여하지 않으면 서버가 이름을 할당합니다. 이 에이전트는 세션이 실행하는 에이전트인 세션 에이전트의 모델을 사용합니다. 도구, MCP 서버, 스킬은 세션 에이전트가 가진 것의 부분 집합입니다. 모두를 받지만 API가 이를 보장하지는 않습니다. 도구는 권한 정책을 그대로 유지합니다.
* **스레드가 공유하는 것:** 실행의 스레드는 세션의 샌드박스에서 작업하므로 모든 스레드가 같은 파일로 작업합니다. 여기에는 세션이 마운트하는 메모리 스토어의 파일도 포함됩니다. 워크플로가 정의하는 에이전트는 세션이 MCP 서버에 대해 확인한 자격 증명으로 MCP 서버를 사용합니다. 각 스레드에는 자체 대화 기록이 있습니다.
* **이벤트:** 실행 스레드의 `session.thread_created`, `session.thread_status_running`, `session.thread_status_idle`, `session.thread_status_terminated` 이벤트는 기본 스트림으로도 도착합니다([실행 이벤트](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#run-events) 참조). 메시지 이벤트는 스레드 자체 스트림에만 남습니다. 스레드 웹훅은 다른 하위 스레드와 마찬가지로 전송됩니다. 스레드 자체 스트림이 기록하는 내용은 [세션 스레드 이벤트](https://platform.claude.com/docs/ko/managed-agents/session-threads#session-thread-events)를 참조하세요.
* **단계:** 스레드가 어느 단계에서 작업하는지 알려 주는 이벤트나 필드는 없으며, 한 실행의 스레드들이 같은 `agent_name`을 가질 수 있습니다. 실행의 진행 상황은 단계 이벤트로 추적하고, 스레드는 `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)로 구분하세요.
* **보관:** 서버는 늦어도 실행이 종료될 때까지 각 스레드를 보관합니다. 스레드가 결과를 반환하거나 실행이 해당 스레드를 더 이상 사용하지 않으면 더 일찍 보관할 수도 있습니다. 그 시점에 스레드가 아직 실행 중이거나 클라이언트를 기다리고 있으면 서버가 먼저 스레드를 중지합니다. 보관된 스레드는 `terminated` 상태로 스레드 목록에 남습니다. 실행의 스레드를 직접 보관할 필요는 없습니다.
* **도구:** 실행은 같은 작업에 대해 둘 이상의 스레드를 생성할 수 있으므로, 에이전트가 호출하는 도구는 두 번 호출해도 안전하도록 만드세요.

<Tip>
  에이전트에게 사용자의 요청을 위해 작성한 워크플로를 요청할 수 있습니다. 실행이 종료되고 세션이 `idle` 상태가 될 때까지 기다리세요. 그런 다음 에이전트에게 실행을 시작할 때 사용한 워크플로를 한 글자도 빠짐없이 출력하고, 다른 실행은 시작하지 말라고 요청하는 `user.message`를 보내세요.

  ```text wrap
  Print the workflow that you started the run with, word for word, in one code block. Do not start another run.
  ```
</Tip>

## 작업이 완료되는 시점 파악하기

실행이 실행 중인 동안에는 스레드가 하나도 작업하지 않더라도 세션이 `running` 상태로 유지된다고 예상하세요. 작업 중인 스레드가 없고 스레드가 클라이언트를 기다리면 세션은 `requires_action`과 함께 `idle` 상태가 됩니다. 유휴 상태 자체가 작업이 완료되었음을 의미하지는 않습니다. 작업은 다음 두 조건이 모두 참일 때 완료됩니다:

1. 생성된 것을 확인한 모든 실행에 `workflow_run.status_ended`가 있습니다.
2. 그 이후 `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`)이 `end_turn`인 `session.status_idle`이 도착하며, 이것이 중단과 같은 사용자 자신의 요청으로 인한 것이 아닙니다. 중단한 후에는 다음 `user.message` 또는 `user.define_outcome` 이후에 오는 유휴 상태만 인정하세요.

* **일시 중지된 실행:** 일시 중지된 실행은 세션을 `running` 상태로 유지하지 않으므로, 실행이 아직 열려 있는 동안 세션이 유휴 상태가 될 수 있습니다. 예를 들어 예산에 도달하면 세션은 `budget_reached`와 함께 유휴 상태가 됩니다. 실행이 종료될 때까지 작업은 완료되지 않습니다.
* **다른 실행:** 에이전트는 결과를 읽을 때 새 실행을 시작할 수 있으므로 다시 확인하세요.
* **결과(outcome):** [결과(outcome)를 정의한](https://platform.claude.com/docs/ko/managed-agents/define-outcomes) 경우, 실행이 열려 있는 동안에는 실행 중이든 유휴 상태이든 평가가 시작되지 않습니다. 에이전트가 실행의 결과를 읽는 턴에서 평가가 시작될 수 있습니다.
* **`retries_exhausted`:** 에이전트의 턴이 오류로 실패했습니다. 재시도 횟수가 소진되었거나, 결제 실패처럼 재시도할 수 없는 오류입니다. 이 유휴 상태가 올 때 실행이 아직 실행 중일 수 있습니다. 실행이 종료되었고 에이전트가 아직 그 결과를 읽지 않았다면, 서버는 사용자 입력 없이 새 턴을 시작합니다. 세션이 다시 `running`이 되므로 다음 유휴 상태를 기다리세요. 세션이 유휴 상태로 유지되면 그 전에 온 `session.error`를 읽고 원인을 해결하세요. 그런 다음 `user.message`를 보내거나 각 실행의 `result` (csharp, go: `Result`)를 직접 읽으세요.

## 실행 추적하기

이 샘플은 사용자의 메시지부터 에이전트의 답변까지 세션을 추적합니다. 스트림을 열고 메시지를 보냅니다. 이어서 다음을 수행합니다:

* **각 실행을 추적**합니다. `workflow_run.created`부터 `workflow_run.status_ended`까지 추적하며, 각 단계가 시작될 때 출력합니다.
* 실행의 스레드는 세션이 `running` 상태로 유지되는 동안 클라이언트를 기다릴 수 있으므로, 각 `agent.custom_tool_use`가 도착하면 **커스텀 도구 호출에 응답**합니다. 에이전트의 도구가 [확인을 요청](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#tool-confirmation)하는 경우, `evaluated_permission` (java, php: `evaluatedPermission`; csharp, go: `EvaluatedPermission`)이 `ask`인 각 `agent.tool_use` 또는 `agent.mcp_tool_use`에 응답하는 분기를 추가하세요. 모든 호출을 허용하는 분기는 `always_ask`를 항상 허용으로 바꿔 버리므로 샘플에는 이러한 분기가 없습니다.
* [작업이 완료](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#know-when-the-work-is-done)되면 **중지**합니다. 즉, 열린 실행이 없고 세션이 `end_turn`과 함께 유휴 상태가 될 때입니다. 세션이 종료되는 경우에도 중지합니다. `requires_action`을 제외한 다른 중지 사유(예: `budget_reached`, `retries_exhausted`, `refusal`)로 유휴 상태가 되면 이유를 출력하고 중지하므로, 이러한 경우는 자체 코드에서 처리하세요. 서버가 곧 스스로 새 턴을 시작하려는 경우에도 `retries_exhausted`에서 중지합니다. `requires_action`에서, 그리고 실행이 열려 있는 동안의 `end_turn`에서는 계속 기다립니다.

<CodeGroup exclude="shell">
  <CodeGroupItem language="cURL">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 [Claude SDK](https://platform.claude.com/docs/ko/cli-sdks-libraries/overview)를 사용하세요.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    이 워크플로에는 단일 셸 명령보다 더 많은 로직이 필요합니다. 실행하려면 [Claude SDK](https://platform.claude.com/docs/ko/cli-sdks-libraries/overview)를 사용하세요.
  </CodeGroupItem>

  ```python Python
  open_runs: dict[str, str] = {}  # workflow_run_id -> run name
  phase_names: dict[tuple[str, str], str] = {}  # (run ID, phase ID) -> phase name

  # 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  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": "Which contracts in /contracts have a change-of-control clause?",
                      },
                  ],
              },
          ],
      )

      for event in stream:
          match event.type:
              case "workflow_run.created":
                  open_runs[event.workflow_run_id] = event.name
                  for phase in event.phases:
                      phase_names[event.workflow_run_id, phase.id] = phase.name
                  print(f"Run started: {event.name}")
              case "workflow_run.phase_started":
                  phase_id = event.workflow_run_phase_id
                  key = (event.workflow_run_id, phase_id)
                  print(f"  Phase: {phase_names.get(key, phase_id)}")
              case "workflow_run.status_ended":
                  name = open_runs.pop(event.workflow_run_id, event.workflow_run_id)
                  print(f"Run ended: {name} ({event.result.type})")
              case "agent.custom_tool_use":
                  # 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
                  # 클라이언트를 기다릴 수 있습니다.
                  result = call_tool(event.name, event.input)
                  try:
                      client.beta.sessions.events.send(
                          session_id,
                          events=[
                              {
                                  "type": "user.custom_tool_result",
                                  "custom_tool_use_id": event.id,
                                  "content": [{"type": "text", "text": result}],
                              },
                          ],
                      )
                  except anthropic.BadRequestError as error:
                      # 서버가 호출의 스레드를 보관한 후 너무 늦게 도착한 결과는
                      # 서버가 거부합니다. 실행을 계속 추적합니다.
                      print(f"  Answer to {event.name} refused: {error.message}")
              case "session.status_idle":
                  # 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
                  if not open_runs and event.stop_reason.type == "end_turn":
                      break
                  # requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
                  # 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
                  if event.stop_reason.type not in ("end_turn", "requires_action"):
                      print(f"Session idle: {event.stop_reason.type}")
                      break
              case "session.status_terminated":
                  break
  ```

  ```typescript TypeScript
  const openRuns = new Map<string, string>(); // workflow_run_id -> run name
  const phaseNames = new Map<string, string>(); // "run ID:phase ID" -> phase name

  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  const stream = await client.beta.sessions.events.stream(sessionId);
  await client.beta.sessions.events.send(sessionId, {
    events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Which contracts in /contracts have a change-of-control clause?"
          }
        ]
      }
    ]
  });

  events: for await (const event of stream) {
    switch (event.type) {
      case "workflow_run.created":
        openRuns.set(event.workflow_run_id, event.name);
        for (const phase of event.phases) {
          phaseNames.set(`${event.workflow_run_id}:${phase.id}`, phase.name);
        }
        console.log(`Run started: ${event.name}`);
        break;
      case "workflow_run.phase_started": {
        const phaseId = event.workflow_run_phase_id;
        const phaseName = phaseNames.get(`${event.workflow_run_id}:${phaseId}`);
        console.log(`  Phase: ${phaseName ?? phaseId}`);
        break;
      }
      case "workflow_run.status_ended": {
        const name = openRuns.get(event.workflow_run_id) ?? event.workflow_run_id;
        openRuns.delete(event.workflow_run_id);
        console.log(`Run ended: ${name} (${event.result.type})`);
        break;
      }
      case "agent.custom_tool_use": {
        // 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
        // 클라이언트를 기다릴 수 있습니다.
        const result = await callTool(event.name, event.input);
        try {
          await client.beta.sessions.events.send(sessionId, {
            events: [
              {
                type: "user.custom_tool_result",
                custom_tool_use_id: event.id,
                content: [{ type: "text", text: result }]
              }
            ]
          });
        } catch (error) {
          // 서버가 호출의 스레드를 보관한 후 너무 늦게 도착한 결과는
          // 서버가 거부합니다. 실행을 계속 추적합니다.
          if (!(error instanceof Anthropic.BadRequestError)) {
            throw error;
          }
          console.log(`  Answer to ${event.name} refused: ${error.message}`);
        }
        break;
      }
      case "session.status_idle": {
        // 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
        const reason = event.stop_reason.type;
        if (openRuns.size === 0 && reason === "end_turn") {
          break events;
        }
        // requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
        // 다른 중지 사유인 경우 이를 출력하고 중지합니다.
        if (reason !== "end_turn" && reason !== "requires_action") {
          console.log(`Session idle: ${reason}`);
          break events;
        }
        break;
      }
      case "session.status_terminated":
        break events;
    }
  }
  ```

  ```csharp C#
  var openRuns = new Dictionary<string, string>(); // workflow_run_id -> run name
  var phaseNames = new Dictionary<(string, string), string>(); // (run ID, phase ID) -> phase name

  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다. raw-response 호출은
  // 스트림을 즉시 엽니다. 일반 호출은 첫 번째 읽기까지 기다립니다.
  using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(sessionId);
  await client.Beta.Sessions.Events.Send(sessionId, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Which contracts in /contracts have a change-of-control clause?",
                  },
              ],
          },
      ],
  });

  await foreach (var streamEvent in stream.Enumerate())
  {
      if (streamEvent.Value is BetaManagedAgentsWorkflowRunCreatedEvent created)
      {
          openRuns[created.WorkflowRunID] = created.Name;
          foreach (var listed in created.Phases)
          {
              phaseNames[(created.WorkflowRunID, listed.ID)] = listed.Name;
          }
          Console.WriteLine($"Run started: {created.Name}");
      }
      else if (streamEvent.Value is BetaManagedAgentsWorkflowRunPhaseStartedEvent phase)
      {
          var phaseId = phase.WorkflowRunPhaseID;
          var key = (phase.WorkflowRunID, phaseId);
          Console.WriteLine($"  Phase: {phaseNames.GetValueOrDefault(key, phaseId)}");
      }
      else if (streamEvent.Value is BetaManagedAgentsWorkflowRunStatusEndedEvent ended)
      {
          var name = openRuns.GetValueOrDefault(ended.WorkflowRunID, ended.WorkflowRunID);
          openRuns.Remove(ended.WorkflowRunID);
          var outcome = ended.Result.Value switch
          {
              BetaManagedAgentsWorkflowRunResultCompleted => "completed",
              BetaManagedAgentsWorkflowRunResultStopped => "stopped",
              BetaManagedAgentsWorkflowRunResultError => "error",
              _ => "ended",
          };
          Console.WriteLine($"Run ended: {name} ({outcome})");
      }
      else if (streamEvent.Value is BetaManagedAgentsAgentCustomToolUseEvent toolUse)
      {
          // 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
          // 클라이언트를 기다릴 수 있습니다.
          var result = await CallTool(toolUse.Name, toolUse.Input);
          try
          {
              await client.Beta.Sessions.Events.Send(sessionId, new()
              {
                  Events =
                  [
                      new BetaManagedAgentsUserCustomToolResultEventParams
                      {
                          Type = BetaManagedAgentsUserCustomToolResultEventParamsType.UserCustomToolResult,
                          CustomToolUseID = toolUse.ID,
                          Content =
                          [
                              new BetaManagedAgentsTextBlock
                              {
                                  Type = BetaManagedAgentsTextBlockType.Text,
                                  Text = result,
                              },
                          ],
                      },
                  ],
              });
          }
          catch (AnthropicBadRequestException error)
          {
              // 서버는 호출의 스레드를 보관 처리한 후 너무 늦게 도착한 결과를
              // 거부합니다. 실행을 계속 추적하세요.
              Console.WriteLine($"  Answer to {toolUse.Name} refused: {error.Message}");
          }
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent idle)
      {
          // 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
          var finished = idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn;
          if (openRuns.Count == 0 && finished)
          {
              break;
          }
          // requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽으세요.
          // 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
          if (!finished && idle.StopReason?.Value is not BetaManagedAgentsSessionRequiresAction)
          {
              var reason = idle.StopReason?.Json.GetProperty("type").GetString();
              Console.WriteLine($"Session idle: {reason}");
              break;
          }
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionStatusTerminatedEvent)
      {
          break;
      }
  }
  ```

  ```go Go
  	openRuns := map[string]string{}      // workflow_run_id -> run name
  	phaseNames := map[[2]string]string{} // {run ID, phase ID} -> phase name

  	// 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  	stream := client.Beta.Sessions.Events.StreamEvents(ctx, sessionID, anthropic.BetaSessionEventStreamParams{})
  	defer stream.Close()

  	if _, err := client.Beta.Sessions.Events.Send(ctx, sessionID, anthropic.BetaSessionEventSendParams{
  		Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  			OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
  				Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  				Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  					OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  						Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  						Text: "Which contracts in /contracts have a change-of-control clause?",
  					},
  				}},
  			},
  		}},
  	}); err != nil {
  		panic(err)
  	}

  events:
  	for stream.Next() {
  		switch event := stream.Current().AsAny().(type) {
  		case anthropic.BetaManagedAgentsWorkflowRunCreatedEvent:
  			openRuns[event.WorkflowRunID] = event.Name
  			for _, phase := range event.Phases {
  				phaseNames[[2]string{event.WorkflowRunID, phase.ID}] = phase.Name
  			}
  			fmt.Printf("Run started: %s\n", event.Name)
  		case anthropic.BetaManagedAgentsWorkflowRunPhaseStartedEvent:
  			phaseName, ok := phaseNames[[2]string{event.WorkflowRunID, event.WorkflowRunPhaseID}]
  			if !ok {
  				phaseName = event.WorkflowRunPhaseID
  			}
  			fmt.Printf("  Phase: %s\n", phaseName)
  		case anthropic.BetaManagedAgentsWorkflowRunStatusEndedEvent:
  			name, ok := openRuns[event.WorkflowRunID]
  			if !ok {
  				name = event.WorkflowRunID
  			}
  			delete(openRuns, event.WorkflowRunID)
  			fmt.Printf("Run ended: %s (%s)\n", name, event.Result.Type)
  		case anthropic.BetaManagedAgentsAgentCustomToolUseEvent:
  			// 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
  			// 클라이언트를 기다릴 수 있습니다.
  			result := callTool(event.Name, event.Input)
  			if _, err := client.Beta.Sessions.Events.Send(ctx, sessionID, anthropic.BetaSessionEventSendParams{
  				Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  					OfUserCustomToolResult: &anthropic.BetaManagedAgentsUserCustomToolResultEventParams{
  						Type:            anthropic.BetaManagedAgentsUserCustomToolResultEventParamsTypeUserCustomToolResult,
  						CustomToolUseID: event.ID,
  						Content: []anthropic.BetaManagedAgentsUserCustomToolResultEventParamsContentUnion{{
  							OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  								Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  								Text: result,
  							},
  						}},
  					},
  				}},
  			}); err != nil {
  				// 서버는 호출의 스레드를 보관 처리한 후 너무 늦게 도착한 결과를
  				// 거부합니다. 실행을 계속 추적하세요.
  				var apiErr *anthropic.Error
  				if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusBadRequest {
  					panic(err)
  				}
  				fmt.Printf("  Answer to %s refused: %v\n", event.Name, err)
  			}
  		case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
  			// 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
  			switch event.StopReason.AsAny().(type) {
  			case anthropic.BetaManagedAgentsSessionEndTurn:
  				if len(openRuns) == 0 {
  					break events
  				}
  			// requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
  			// 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
  			case anthropic.BetaManagedAgentsSessionRequiresAction:
  			default:
  				fmt.Printf("Session idle: %s\n", event.StopReason.Type)
  				break events
  			}
  		case anthropic.BetaManagedAgentsSessionStatusTerminatedEvent:
  			break events
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  ```

  ```java Java
  var openRuns = new HashMap<String, String>(); // workflow_run_id -> run name
  var phaseNames = new HashMap<String, String>(); // "run ID:phase ID" -> phase name

  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  try (var stream = client.beta().sessions().events().streamStreaming(sessionId)) {
      client.beta().sessions().events().send(
          sessionId,
          EventSendParams.builder()
              .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
                  .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
                  .addTextContent("Which contracts in /contracts have a change-of-control clause?")
                  .build())
              .build()
      );

      Iterable<BetaManagedAgentsStreamSessionEvents> events = stream.stream()::iterator;
      events:
      for (var event : events) {
          switch (event.type().value()) {
              case WORKFLOW_RUN_CREATED -> {
                  var created = event.asWorkflowRunCreated();
                  openRuns.put(created.workflowRunId(), created.name());
                  created.phases().forEach(phase ->
                      phaseNames.put(created.workflowRunId() + ":" + phase.id(), phase.name()));
                  IO.println("Run started: " + created.name());
              }
              case WORKFLOW_RUN_PHASE_STARTED -> {
                  var started = event.asWorkflowRunPhaseStarted();
                  var phaseId = started.workflowRunPhaseId();
                  var key = started.workflowRunId() + ":" + phaseId;
                  IO.println("  Phase: " + phaseNames.getOrDefault(key, phaseId));
              }
              case WORKFLOW_RUN_STATUS_ENDED -> {
                  var ended = event.asWorkflowRunStatusEnded();
                  var name = openRuns.getOrDefault(ended.workflowRunId(), ended.workflowRunId());
                  openRuns.remove(ended.workflowRunId());
                  var result = ended.result();
                  var outcome = result.isCompleted() ? "completed"
                      : result.isStopped() ? "stopped"
                      : result.isError() ? "error"
                      : "ended";
                  IO.println("Run ended: " + name + " (" + outcome + ")");
              }
              case AGENT_CUSTOM_TOOL_USE -> {
                  // 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
                  // 클라이언트를 기다릴 수 있습니다.
                  var toolUse = event.asAgentCustomToolUse();
                  var result = callTool(toolUse.name(), toolUse.input());
                  try {
                      client.beta().sessions().events().send(
                          sessionId,
                          EventSendParams.builder()
                              .addEvent(BetaManagedAgentsUserCustomToolResultEventParams.builder()
                                  .type(BetaManagedAgentsUserCustomToolResultEventParams.Type.USER_CUSTOM_TOOL_RESULT)
                                  .customToolUseId(toolUse.id())
                                  .addTextContent(result)
                                  .build())
                              .build());
                  } catch (BadRequestException e) {
                      // 서버는 호출의 스레드를 보관 처리한 후 너무 늦게 도착한 결과를
                      // 거부합니다. 실행을 계속 추적하세요.
                      IO.println("  Answer to " + toolUse.name() + " refused: " + e.getMessage());
                  }
              }
              case SESSION_STATUS_IDLE -> {
                  // 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
                  var stopReason = event.asSessionStatusIdle().stopReason();
                  if (openRuns.isEmpty() && stopReason.isEndTurn()) {
                      break events;
                  }
                  // requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
                  // 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
                  if (!stopReason.isEndTurn() && !stopReason.isRequiresAction()) {
                      IO.println("Session idle: " + stopReason.type().asString());
                      break events;
                  }
              }
              case SESSION_STATUS_TERMINATED -> {
                  break events;
              }
              default -> {}
          }
      }
  }
  ```

  ```php PHP
  $openRuns = []; // workflow_run_id => run name
  $phaseNames = []; // run ID => [phase ID => phase name]

  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  $stream = $client->beta->sessions->events->streamStream($sessionId);
  $client->beta->sessions->events->send(
      $sessionId,
      events: [
          [
              'type' => 'user.message',
              'content' => [
                  ['type' => 'text', 'text' => 'Which contracts in /contracts have a change-of-control clause?'],
              ],
          ],
      ],
  );

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsWorkflowRunCreatedEvent:
              $openRuns[$event->workflowRunID] = $event->name;
              foreach ($event->phases as $phase) {
                  $phaseNames[$event->workflowRunID][$phase->id] = $phase->name;
              }
              echo "Run started: {$event->name}", PHP_EOL;
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsWorkflowRunPhaseStartedEvent:
              $phaseId = $event->workflowRunPhaseID;
              echo '  Phase: ', $phaseNames[$event->workflowRunID][$phaseId] ?? $phaseId, PHP_EOL;
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsWorkflowRunStatusEndedEvent:
              $name = $openRuns[$event->workflowRunID] ?? $event->workflowRunID;
              unset($openRuns[$event->workflowRunID]);
              echo "Run ended: {$name} ({$event->result->type})", PHP_EOL;
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsAgentCustomToolUseEvent:
              // 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
              // 클라이언트를 기다릴 수 있습니다.
              $result = callTool($event->name, $event->input);
              try {
                  $client->beta->sessions->events->send(
                      $sessionId,
                      events: [
                          [
                              'type' => 'user.custom_tool_result',
                              'custom_tool_use_id' => $event->id,
                              'content' => [['type' => 'text', 'text' => $result]],
                          ],
                      ],
                  );
              } catch (\Anthropic\Core\Exceptions\BadRequestException $error) {
                  // 서버는 호출의 스레드를 보관 처리한 후 너무 늦게 도착한 결과를
                  // 거부합니다. 실행을 계속 추적합니다.
                  echo "  Answer to {$event->name} refused: {$error->getMessage()}", PHP_EOL;
              }
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent:
              // 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
              $finished = $event->stopReason->type === 'end_turn';
              if (!$openRuns && $finished) {
                  break 2;
              }
              // requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
              // 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
              if (!$finished && $event->stopReason->type !== 'requires_action') {
                  echo "Session idle: {$event->stopReason->type}", PHP_EOL;
                  break 2;
              }
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusTerminatedEvent:
              break 2;
      }
  }
  $stream->close();
  ```

  ```ruby Ruby
  open_runs = {} # workflow_run_id => run name
  phase_names = {} # [run ID, phase ID] => phase name

  # 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  stream = client.beta.sessions.events.stream_events(session_id)

  client.beta.sessions.events.send_(
    session_id,
    events: [{
      type: "user.message",
      content: [{type: "text", text: "Which contracts in /contracts have a change-of-control clause?"}]
    }]
  )

  stream.each do |event|
    case event
    when Anthropic::Beta::Sessions::BetaManagedAgentsWorkflowRunCreatedEvent
      open_runs[event.workflow_run_id] = event.name
      event.phases.each { |phase| phase_names[[event.workflow_run_id, phase.id]] = phase.name }
      puts "Run started: #{event.name}"
    when Anthropic::Beta::Sessions::BetaManagedAgentsWorkflowRunPhaseStartedEvent
      phase_id = event.workflow_run_phase_id
      puts "  Phase: #{phase_names.fetch([event.workflow_run_id, phase_id], phase_id)}"
    when Anthropic::Beta::Sessions::BetaManagedAgentsWorkflowRunStatusEndedEvent
      name = open_runs.delete(event.workflow_run_id) || event.workflow_run_id
      puts "Run ended: #{name} (#{event.result.type})"
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentCustomToolUseEvent
      # 이벤트가 도착하면 응답합니다. 실행의 스레드는 세션이 실행 중인 동안
      # 클라이언트를 기다릴 수 있습니다.
      result = call_tool.call(event.name, event.input)
      begin
        client.beta.sessions.events.send_(
          session_id,
          events: [{
            type: "user.custom_tool_result",
            custom_tool_use_id: event.id,
            content: [{type: "text", text: result}]
          }]
        )
      rescue Anthropic::Errors::BadRequestError => error
        # 서버는 호출의 스레드를 보관 처리한 후 너무 늦게 도착한 결과를
        # 거부합니다. 실행을 계속 추적합니다.
        puts "  Answer to #{event.name} refused: #{error.message}"
      end
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      reason = event.stop_reason.type.to_sym
      # 모든 실행이 종료되고 에이전트가 턴을 마치면 완료됩니다
      break if open_runs.empty? && reason == :end_turn
      # requires_action이 있는 idle은 클라이언트를 기다리므로 계속 읽습니다.
      # 그 외의 중지 사유인 경우 이를 출력하고 중지합니다.
      unless %i[end_turn requires_action].include?(reason)
        puts "Session idle: #{reason}"
        break
      end
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusTerminatedEvent
      break
    end
  end
  ```
</CodeGroup>

## 실행이 열려 있는 세션 중단하기

`session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) 없이, 또는 기본 스레드의 ID와 함께 [`user.interrupt`](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#interrupt-the-agent)를 보내세요. 이는 에이전트의 턴을 중지합니다. 실행은 종료하지 않습니다. 세션의 실행은 일시 중지되거나 계속 실행될 수 있으며, 이벤트가 어느 쪽인지 보여 주지 않을 수 있습니다. 일시 중지된 실행의 수명은 계속 흐르므로, 실행이 일시 중지된 동안 `timeout_error`로 종료될 수 있습니다.

* **대기 중인 도구 호출:** 중단 후에도 실행 스레드의 도구 호출이 여전히 클라이언트를 기다릴 수 있습니다. 각 호출에 응답하세요. 확인을 요청하는 호출을 취소하려면 거부하세요. 커스텀 도구 호출을 취소하려면 `is_error` (java, php: `isError`; csharp, go: `IsError`)를 `true`로 설정하고 `content` (csharp, go: `Content`)에 이유를 설명하는 텍스트를 넣은 결과를 보내세요. 세션이 `requires_action`과 함께 `idle` 상태인 동안에는 `user.message`가 400을 반환하므로 먼저 호출에 응답하세요.
* **실행을 중지하려면:** 에이전트에게 실행을 중지하도록 요청하는 `user.message`를 보내세요. 중지된 실행은 `result` `{"type": "stopped"}`로 종료됩니다. 세션이 `budget_reached`와 함께 `idle` 상태인 동안에는 예산을 늘리거나 제거할 때까지 `user.message`가 400을 반환합니다. 예산을 늘리거나 제거하면 중단으로도 일시 중지된 경우가 아닌 한 예산으로 인해 일시 중지된 실행도 재개됩니다.
* **계속하려면:** 에이전트에게 실행을 계속하도록 요청하는 `user.message`를 보내세요. 중단 후에는 실행이 이 메시지를 기다릴 수 있습니다. 세션이 `budget_reached`와 함께 `idle` 상태이면 먼저 예산을 늘리거나 제거하세요.
* **실행 결과:** 중단 후에 종료되는 실행도 `workflow_run.status_ended`를 전송합니다.

## 실행이 열려 있는 동안

| 요청                                     | 실행이 열려 있는 동안                                                                                                                                                                                                    | 수행할 작업                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 세션 보관 또는 삭제                            | 세션의 상태와 관계없이 실행이 열려 있는 동안 400을 반환할 수 있습니다. 오류의 `error.details.error_code`는 `"workflow_run_open"`일 수 있습니다. 성공할 수도 있습니다.                                                                                          | 에이전트에게 실행을 중지하도록 요청하거나, 각 실행이 종료될 때까지 기다리세요. 일시 중지된 실행은 수명이 지나야만 스스로 종료됩니다. 그런 다음 세션이 `idle` 상태가 되면 요청을 보내세요. 성공한 보관은 열려 있는 각 실행을 `{"type": "stopped"}`로 종료합니다. 보관 후에는 실행의 `workflow_run.status_ended`와 아직 열려 있던 단계의 `workflow_run.phase_ended`가 스트림으로 도착하지 않습니다. 이를 읽으려면 세션의 이벤트를 나열하세요. 삭제가 성공한 후에는 어떤 `workflow_run` 이벤트도 세션 실행의 종료를 보고하지 않습니다.                                                                                                                                                                                                                                                      |
| 실행의 스레드 중 하나 보관                        | 실행이 실행 중이든 유휴 상태이든 열려 있는 동안에는, 서버가 이미 스레드를 보관한 경우가 아니면 `error.details.error_code: "workflow_run_open"`과 함께 400을 반환합니다.                                                                                          | 아무것도 하지 않아도 됩니다. 서버가 실행의 스레드를 보관합니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| 세션의 `agent` (csharp, go: `Agent`) 업데이트 | 일시 중지된 실행을 포함해 실행이 하나라도 열려 있는 동안 `error.details.error_code: "workflow_run_open"`과 함께 400을 반환합니다. 기반 에이전트를 업데이트하는 것은 여전히 허용되며, 세션은 자체 사본을 유지합니다. `budget` (csharp, go: `Budget`) 같은 다른 필드도 함께 보내는 요청은 전체가 거부됩니다. | 모든 실행에 `workflow_run.status_ended`가 올 때까지 기다리거나, 에이전트에게 실행을 중지하도록 요청하세요.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 실행의 스레드에서 온 도구 호출 또는 도구 확인에 응답         | 허용됩니다. 기본 스트림으로 도착하며, `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)가 스레드를 지정합니다.                                                                         | 이벤트가 도착하는 즉시 이벤트의 `id` (csharp, go: `ID`)를 `tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`) 또는 `custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`)로 전달하여 응답하세요. `session.status_idle`을 기다리지 마세요. 실행의 다른 스레드가 작업하는 동안 세션은 `running` 상태로 유지될 수 있습니다. 서버가 스레드를 보관한 후에는 해당 스레드의 호출에 대한 도구 결과가 아무 효과가 없으며 400을 반환할 수 있습니다. 도구 결과가 400을 반환하면 스레드 목록에서 호출의 스레드를 찾으세요. 상태가 `terminated`이면 결과가 너무 늦게 온 것이므로 버리세요. 서버는 요청의 이벤트 중 하나를 거부하면 요청 전체를 거부하므로, 각 도구 결과는 별도의 요청으로 보내세요. 너무 늦게 온 도구 확인은 200을 반환하지만, 이것이 도구가 실행되었다는 의미는 아닙니다. |

### 재연결 후 실행 상태 재구성하기

세션의 이벤트로부터 각 실행의 상태를 재구성하세요. 스트림은 놓친 내용을 다시 재생하지 않습니다. 새 연결은 연결이 열린 이후에 발생한 이벤트만 전달합니다. 따라서 [과거 이벤트 조회하기](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#list-past-events)에서처럼 이벤트 유형마다 하나의 항목을 지정한 `types` (curl: `types[]`; cli: `--type`; csharp, go: `Types`) 필터로 이벤트를 나열하세요. `next_page` (ruby: `next_page_`; java, php: `nextPage`; csharp, go: `NextPage`)가 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)이거나 없을 때까지 각 응답의 `next_page` (ruby: `next_page_`; java, php: `nextPage`; csharp, go: `NextPage`)를 `page` (cli: `--page`; csharp, go: `Page`)로 전달하세요. `workflow_run.created`, `workflow_run.status_running`, `workflow_run.status_idle`, `workflow_run.status_ended`는 각 실행의 상태를 알려 주지만, 중단 후 일시 중지된 실행은 여전히 실행 중으로 표시될 수 있습니다. `workflow_run.phase_started`와 `workflow_run.phase_ended`로 진행 상황을 재구성할 수 있습니다. 아직 상태 이벤트가 없는 실행은 아직 실행을 시작하지 않은 것입니다. 실행을 나열하는 엔드포인트는 없습니다.

## 비용 및 예산

실행의 모델 요청은 [세션의 예산](https://platform.claude.com/docs/ko/managed-agents/budgets)에 포함됩니다. 실행 자체에는 별도의 가격이 없습니다. 실행의 에이전트가 사용하는 토큰은 세션의 다른 토큰과 마찬가지로 각 모델의 요금으로 청구됩니다. 세션의 모든 요금은 [Claude Managed Agents 가격](https://platform.claude.com/docs/ko/about-claude/pricing#claude-managed-agents-pricing)을 참조하세요.

* **한 실행의 사용량:** 세션의 스레드를 나열하고, 해당 실행의 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)를 가진 스레드의 `usage` (csharp, go: `Usage`)에 있는 토큰 수를 합산하세요. 목록에는 상태가 `terminated`인 보관된 스레드도 포함되므로, 완료된 실행의 스레드도 집계됩니다. `next_page` (ruby: `next_page_`; java, php: `nextPage`; csharp, go: `NextPage`)가 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)이거나 없을 때까지 각 응답의 `next_page` (ruby: `next_page_`; java, php: `nextPage`; csharp, go: `NextPage`)를 `page` (cli: `--page`; csharp, go: `Page`)로 전달하고, `usage` (csharp, go: `Usage`)가 없는 스레드는 건너뛰세요. 대신 스레드의 `list_cost` (java, php: `listCost`; csharp, go: `ListCost`)를 합산하면, 합계에서 세션 런타임이 빠지고 각 수치가 개별적으로 반올림됩니다.
* **예산 도달 시:** 열려 있는 모든 실행이 일시 중지되고, 세션은 `budget_reached`와 함께 `idle`을 보고하거나, 도구 호출도 대기 중인 경우 `requires_action`을 보고합니다. 각 스레드는 이미 시작한 모델 요청을 완료하므로, 실행은 작업 중인 스레드마다 요청 하나만큼 예산을 초과할 수 있습니다. 예산을 늘리거나 제거하면 예산으로 인해 일시 중지된 실행이 재개되지만, 중단으로도 일시 중지된 경우는 예외입니다. 세션의 사용량에 정가가 없는 모델이 포함된 경우에는 예산을 제거해야만 재개됩니다. [정가가 없는 모델](https://platform.claude.com/docs/ko/managed-agents/budgets#models-without-a-list-price)을 참조하세요. 일시 중지된 실행의 수명은 계속 흘러가므로, 실행이 일시 중지된 동안 `timeout_error`로 종료될 수 있습니다.

## 제한

| 제한                           | 값                           | 제한에 도달했을 때                                                                                                                                 |
| ---------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| 한 실행에서 동시에 작업하는 스레드          | 64                          | 하나가 완료될 때까지 실행이 더 이상 스레드를 생성하지 않습니다. API는 이 수치를 보장하지 않습니다.                                                                                 |
| 실행의 전체 수명 동안 워크플로가 시작하는 에이전트 | 1,000                       | 워크플로가 더 많이 요청하면 서버는 다른 에이전트를 시작하지 않으며, 실행은 `thread_limit_error`로 종료됩니다. 서버는 실패한 에이전트를 새 스레드에서 다시 실행할 수 있으므로, 실행의 스레드가 1,000개를 넘을 수 있습니다.   |
| 실행 수명                        | 기본적으로 24시간, 또는 에이전트가 설정한 수명 | 실행이 `timeout_error`로 종료됩니다. 에이전트가 설정한 수명을 알려 주는 이벤트는 없습니다.                                                                                 |
| 한 세션에서 동시에 열려 있는 실행          | 기본적으로 10                    | 서버가 다른 실행의 시작을 거부합니다. 에이전트의 도구 호출은 오류를 받고, 사용자는 `error.type`이 `max_workflow_runs_error`인 `workflow_run.error`를 받습니다. 유휴 상태의 실행도 제한에 포함됩니다. |

서버는 실행 또는 단계의 `name` (csharp, go: `Name`)을 64자로, `description` (csharp, go: `Description`)을 256자로 줄입니다. 서버에는 여기에 나열되지 않은 워크플로에 대한 다른 제한과 규칙이 있습니다. 표시되는 내용은 서버가 문제를 발견하는 시점에 따라 다릅니다:

| 발생하는 상황                                        | 표시되는 내용                                                       |
| ---------------------------------------------- | ------------------------------------------------------------- |
| 에이전트가 실행을 시작할 때 워크플로가 다른 제한 중 하나를 초과한 경우       | 시작이 거부됩니다. `workflow_run.error`를 받으며, 실행은 생성되지 않습니다.          |
| 실행이 나중에 다른 제한 중 하나를 초과한 경우                     | `workflow_run.error`를 받은 다음, 실행이 `unknown_error`로 종료될 수 있습니다. |
| 시작 후 서버가 워크플로가 제한 이외의 워크플로 규칙을 위반한다는 것을 발견한 경우 | `workflow_run.error`를 받은 다음, 실행이 `program_error`로 종료될 수 있습니다. |

세션은 수명 동안 원하는 수만큼 실행을 시작할 수 있습니다.

* **실행 시작:** 세션의 기본 스레드에 있는 에이전트만 실행을 시작합니다. 실행의 스레드에서 작업하는 에이전트는 자체 실행을 시작할 수 없으므로, 실행은 중첩되지 않습니다.
* **실행 종료:** 사용자가 보내는 어떤 이벤트도 실행을 종료하지 않습니다. 세션을 보관하면 종료될 수 있습니다.
* **실행의 스레드:** [ID로 스레드를 중지](https://platform.claude.com/docs/ko/managed-agents/session-threads#interrupt-a-session-thread)하거나, 실행이 열려 있는 동안 스레드를 보관할 수는 없습니다.
* **스레드 제한:** 실행의 스레드는 세션의 [하위 스레드 제한](https://platform.claude.com/docs/ko/managed-agents/session-threads#primary-thread-and-session-threads)에서 제외됩니다.
* **가시성:** 워크플로의 코드는 볼 수 없지만, [실행의 스레드](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#a-runs-threads)의 팁에서 설명하는 것처럼 에이전트에게 워크플로를 요청할 수 있습니다. 또한 에이전트가 실행을 시작하고 관리하기 위해 수행하는 도구 호출이나, 각 스레드가 워크플로에 반환하는 결과도 볼 수 없습니다.

### 속도 제한

실행의 작업은 조직에 이미 적용된 "rate limit"(속도 제한)에 포함됩니다.

| 항목                                           | 포함되는 제한                                                                                                  | 수행할 작업                                                                                                                    |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 세션, 세션의 스레드, 그리고 그 이벤트를 조회하거나 나열하는 클라이언트의 요청 | [Managed Agents 엔드포인트](https://platform.claude.com/docs/ko/managed-agents/reference#rate-limits)의 읽기 제한  | 폴링하는 대신 세션의 이벤트 스트림에서 실행을 추적하세요.                                                                                          |
| 실행의 스레드에서 보내는 모델 요청                          | 각 스레드가 사용하는 모델에 대한 [Messages API 속도 제한](https://platform.claude.com/docs/ko/api/rate-limits)(다른 트래픽과 합산) | 해당 제한에 실행을 위한 여유를 남겨 두거나, [더 높은 제한을 요청](https://platform.claude.com/docs/ko/api/rate-limits#requesting-higher-limits)하세요. |

실행의 스레드 중 하나에서 보낸 모델 요청이 속도 제한에 걸리거나 모델이 과부하 상태이면, 해당 스레드 자체 스트림이 `model_rate_limited_error` 또는 `model_overloaded_error` 유형의 `session.error`를 받을 수 있습니다:

* `retry_status.type`이 `retrying`이면 서버가 요청을 재시도하고 있으며, 스레드는 여전히 작업 중입니다.
* `exhausted`이면 스레드가 실패한 것입니다. 워크플로가 그 실패로 인해 실행이 종료되도록 두면, 실행은 원인을 명시하지 않는 `program_error`로 종료됩니다. 원인을 찾으려면 실패한 스레드의 이벤트를 읽으세요.

서버는 조직의 모든 세션이 분당 수행하는 작업량도 제한합니다. 이 제한에 걸린 스레드는 중지되며, 자체 스트림에 속도 제한을 명시하는 메시지가 담긴 `session.error`가 전송됩니다. 에이전트에게 계속하도록 요청하기 전에 1분 동안 기다리세요.
