---
title: 세션 스레드
url: https://platform.claude.com/docs/ko/managed-agents/session-threads
description: 멀티에이전트 세션의 스레드를 나열, 중단, 보관하고, 스레드의 이벤트를 읽고, 스레드 전반에서 도구 권한을 처리합니다.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

[멀티에이전트 세션](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration)에서 각 에이전트는 자체 **session thread**(세션 스레드)에서 작업합니다. 이 페이지에서는 스레드를 나열, 중단, 보관하는 방법, 스레드가 보내는 이벤트, 그리고 스레드 전반에서 도구 권한이 작동하는 방식을 다룹니다. [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)도 세션 스레드를 생성합니다.

## 기본 스레드와 세션 스레드

**세션 수준 이벤트 스트림**(`/v1/sessions/{session_id}/events/stream`)은 **primary thread**(기본 스레드)로 간주되며, 모든 스레드에 걸친 모든 활동의 요약된 보기를 포함합니다. [서브에이전트](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#delegate-to-subagents)의 전체 활동은 볼 수 없지만, 작업의 시작과 끝, 그리고 도구 권한 요청과 같은 차단 이벤트는 볼 수 있습니다.

**세션 스레드**는 특정 에이전트의 활동을 자세히 살펴보는 곳입니다.

세션 `status` (csharp, go: `Status`)는 모든 에이전트 활동의 집계입니다. 하나 이상의 스레드가 `running`이면 전체 세션 상태도 `running`입니다. 실행 중인 [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)도 해당 스레드 중 어느 것도 작업하지 않는 동안에도 세션을 `running` 상태로 유지할 수 있습니다. 작업 중인 스레드가 없고 스레드가 클라이언트를 기다리는 경우 세션은 `idle`입니다. [작업이 완료되는 시점 파악하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#know-when-the-work-is-done)를 참조하세요.

[세션 예산](https://platform.claude.com/docs/ko/managed-agents/budgets)은 세션의 모든 스레드에 걸쳐 공유되는 단일 한도입니다. 한도에 도달하면 스레드는 각각 독립적으로 일시 중지되며, 각 스레드의 비용은 해당 스레드가 실제로 사용한 모델 기준으로 책정됩니다.

<Note>
  세션은 한 번에 최대 25개의 하위 스레드를 가질 수 있습니다. 유휴 스레드는 보관할 때까지 개수에 포함되며, 기본 스레드는 포함되지 않습니다. 기본 스레드의 에이전트는 단일 서브에이전트의 여러 복사본을 호출하여 하나의 `agent`와 연결된 여러 스레드를 생성할 수 있습니다. [어드바이저](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#give-the-session-an-advisor) 자문 스레드는 이 제한에서 제외됩니다. [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)의 스레드도 마찬가지입니다.
</Note>

## 스레드 나열

다음과 같이 세션과 연결된 모든 스레드를 나열합니다:

<CodeGroup>
  ```bash cURL
  curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01"
  ```

  ```bash CLI
  ant beta:sessions:threads list --session-id "$SESSION_ID"
  ```

  ```python Python
  for thread in client.beta.sessions.threads.list(session.id):
      agent = thread.agent
      label = agent.type if agent.type == "advisor" else agent.name
      print(f"[{label}] {thread.status}")
  ```

  ```typescript TypeScript
  for await (const thread of client.beta.sessions.threads.list(session.id)) {
    const label = thread.agent.type === "advisor" ? thread.agent.type : thread.agent.name;
    console.log(`[${label}] ${thread.status}`);
  }
  ```

  ```csharp C#
  await foreach (var sessionThread in (await client.Beta.Sessions.Threads.List(session.ID)).Paginate())
  {
      var label = sessionThread.Agent.Name ?? sessionThread.Agent.Json.GetProperty("type").GetString();
      Console.WriteLine($"[{label}] {sessionThread.Status.Raw()}");
  }
  ```

  ```go Go
  threads := client.Beta.Sessions.Threads.ListAutoPaging(ctx, session.ID, anthropic.BetaSessionThreadListParams{})
  for threads.Next() {
  	thread := threads.Current()
  	label := cmp.Or(thread.Agent.Name, thread.Agent.Type)
  	fmt.Printf("[%s] %s\n", label, thread.Status)
  }
  if err := threads.Err(); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  for (var thread : client.beta().sessions().threads().list(session.id()).autoPager()) {
      var agent = thread.agent();
      var label = agent.isAgent() ? agent.asAgent().name() : agent.type().asString();
      IO.println("[" + label + "] " + thread.status());
  }
  ```

  ```php PHP
  foreach ($client->beta->sessions->threads->list($session->id)->pagingEachItem() as $thread) {
      $label = $thread->agent instanceof \Anthropic\Beta\Agents\BetaManagedAgentsAdvisor
          ? $thread->agent->type
          : $thread->agent->name;
      echo "[{$label}] {$thread->status}\n";
  }
  ```

  ```ruby Ruby
  client.beta.sessions.threads.list(session.id).auto_paging_each do |thread|
    agent = thread.agent
    label = agent.type == :advisor ? agent.type : agent.name
    puts "[#{label}] #{thread.status}"
  end
  ```
</CodeGroup>

전체 목록에는 기본 스레드가 포함됩니다. 기본 스레드의 경우 `parent_thread_id` (java: `parentThreadId`; php: `parentThreadID`; csharp, go: `ParentThreadID`)는 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)입니다. 다른 모든 스레드는 하위 스레드입니다. `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)는 [실행의 스레드](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#a-runs-threads)를 제외하고 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`)입니다.

특정 상태의 스레드만 나열하려면 요청에 `statuses[]`를 추가하고, 둘 이상의 상태를 지정하려면 `?statuses[]=running&statuses[]=idle`처럼 반복하세요. 생략하면 모든 상태의 스레드가 반환됩니다.

## 세션 스레드 중단

특정 스레드를 중지하려면 `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)와 함께 `user.interrupt`를 보내세요. `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)를 생략하면 기본 스레드를 포함하여 세션의 보관되지 않은 모든 스레드가 중단됩니다. 동적 워크플로가 있는 세션에서 중단은 어떤 실행도 종료하지 않으며, 실행의 스레드를 지정한 중단은 아무것도 중지하지 않습니다. 중단은 다른 하위 스레드의 대기 중인 도구 호출을 닫지만, 실행 스레드의 도구 호출을 닫는 데 의존하지 마세요. [실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open)를 참조하세요.

<CodeGroup>
  ```bash cURL
  curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    -d '{"events": [{"type": "user.interrupt", "session_thread_id": "'"$THREAD_ID"'"}]}'
  ```

  ```bash CLI
  ant beta:sessions:events send \
    --session-id "$SESSION_ID" \
    --event "{type: user.interrupt, session_thread_id: $THREAD_ID}"
  ```

  ```python Python
  client.beta.sessions.events.send(
      session.id,
      events=[{"type": "user.interrupt", "session_thread_id": thread.id}],
  )
  ```

  ```typescript TypeScript
  await client.beta.sessions.events.send(session.id, {
    events: [{ type: "user.interrupt", session_thread_id: thread.id }]
  });
  ```

  ```csharp C#
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserInterruptEventParams
          {
              Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
              SessionThreadID = thread.ID,
          },
      ],
  });
  ```

  ```go Go
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  		OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
  			Type:            anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
  			SessionThreadID: anthropic.String(thread.ID),
  		},
  	}},
  }); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
              .type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
              .sessionThreadId(thread.id())
              .build())
          .build());
  ```

  ```php PHP
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          ['type' => 'user.interrupt', 'session_thread_id' => $thread->id],
      ],
  );
  ```

  ```ruby Ruby
  client.beta.sessions.events.send_(
    session.id,
    events: [{type: "user.interrupt", session_thread_id: thread.id}]
  )
  ```
</CodeGroup>

`requires_action`에서 차단된 서브에이전트의 스레드에 대해 중단을 보내면, 대기 중인 각 도구 호출이 오류 도구 결과("Tool execution was interrupted before completion. Please retry.")로 닫히고 `stop_reason: end_turn`과 함께 `session.thread_status_idle`이 직접 다시 발생합니다. 이때 모델은 샘플링되지 않습니다. `end_turn` 또는 `budget_reached`로 유휴 상태인 하위 스레드에 대한 중단은 아무 작업도 하지 않습니다. 종료된 스레드를 지정한 중단은 400 오류를 반환합니다. 중단된 하위 스레드는 턴이 끝날 때 보내는 보고를 기본 스레드의 에이전트에게 보내지 않습니다. 해당 에이전트가 하위 스레드를 기다리는 동안에는 `user.message`나 다른 스레드의 보고와 같은 다른 무언가가 도달할 때까지 새 턴을 시작하지 않습니다.

## 세션 스레드 보관

세션 스레드가 작업을 완료하면 선택적으로 보관할 수 있습니다. 스레드를 보관하면 25개 하위 스레드 제한에서 해당 자리가 비워집니다. 서버는 [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)의 스레드를 직접 보관합니다. 이러한 스레드는 보관할 필요가 없으며, 실행이 열려 있는 동안에는 보관할 수 없습니다.

<CodeGroup>
  ```bash cURL
  curl -fsS -X POST "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/archive" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01"
  ```

  ```bash CLI
  ant beta:sessions:threads archive \
    --session-id "$SESSION_ID" \
    --thread-id "$THREAD_ID"
  ```

  ```python Python
  archived = client.beta.sessions.threads.archive(thread.id, session_id=session.id)
  print(archived.status, archived.archived_at)
  ```

  ```typescript TypeScript
  const archived = await client.beta.sessions.threads.archive(thread.id, {
    session_id: session.id
  });
  console.log(archived.status, archived.archived_at);
  ```

  ```csharp C#
  var archived = await client.Beta.Sessions.Threads.Archive(thread.ID, new() { SessionID = session.ID });
  Console.WriteLine($"{archived.Status} {archived.ArchivedAt}");
  ```

  ```go Go
  archived, err := client.Beta.Sessions.Threads.Archive(ctx, thread.ID, anthropic.BetaSessionThreadArchiveParams{
  	SessionID: session.ID,
  })
  if err != nil {
  	panic(err)
  }
  fmt.Println(archived.Status, archived.ArchivedAt)
  ```

  ```java Java
  var archived = client.beta().sessions().threads().archive(
      thread.id(),
      ThreadArchiveParams.builder()
          .sessionId(session.id())
          .build());
  IO.println(archived.status() + " " + archived.archivedAt().orElseThrow());
  ```

  ```php PHP
  $archived = $client->beta->sessions->threads->archive($thread->id, sessionID: $session->id);
  echo "{$archived->status} {$archived->archivedAt->format(DATE_ATOM)}\n";
  ```

  ```ruby Ruby
  archived = client.beta.sessions.threads.archive(thread.id, session_id: session.id)
  puts "#{archived.status} #{archived.archived_at}"
  ```
</CodeGroup>

보관은 스레드가 `idle`인 경우에만 성공합니다. `requires_action`에 머물러 있는 스레드는 유휴 상태로 간주되어 바로 보관할 수 있으며, 실행 중인 스레드만 먼저 중단해야 합니다:

<CodeGroup>
  ```bash cURL
  # 스레드를 중단한 다음 아카이브합니다
  curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "content-type: application/json" \
    -d '{"events": [{"type": "user.interrupt", "session_thread_id": "'"$THREAD_ID"'"}]}'

  curl -fsS -X POST "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/archive" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01"
  ```

  ```bash CLI
  ant beta:sessions:events send \
    --session-id "$SESSION_ID" \
    --event "{type: user.interrupt, session_thread_id: $THREAD_ID}"

  ant beta:sessions:threads archive \
    --session-id "$SESSION_ID" \
    --thread-id "$THREAD_ID"
  ```

  ```python Python
  client.beta.sessions.events.send(
      session.id,
      events=[{"type": "user.interrupt", "session_thread_id": thread.id}],
  )
  archived = client.beta.sessions.threads.archive(thread.id, session_id=session.id)
  print(archived.status, archived.archived_at)
  ```

  ```typescript TypeScript
  await client.beta.sessions.events.send(session.id, {
    events: [{ type: "user.interrupt", session_thread_id: thread.id }]
  });
  const archived = await client.beta.sessions.threads.archive(thread.id, {
    session_id: session.id
  });
  console.log(archived.status, archived.archived_at);
  ```

  ```csharp C#
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserInterruptEventParams
          {
              Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
              SessionThreadID = thread.ID,
          },
      ],
  });
  archived = await client.Beta.Sessions.Threads.Archive(thread.ID, new() { SessionID = session.ID });
  Console.WriteLine($"{archived.Status} {archived.ArchivedAt}");
  ```

  ```go Go
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  		OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
  			Type:            anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
  			SessionThreadID: anthropic.String(thread.ID),
  		},
  	}},
  }); err != nil {
  	panic(err)
  }

  archived, err := client.Beta.Sessions.Threads.Archive(ctx, thread.ID, anthropic.BetaSessionThreadArchiveParams{
  	SessionID: session.ID,
  })
  if err != nil {
  	panic(err)
  }
  fmt.Println(archived.Status, archived.ArchivedAt)
  ```

  ```java Java
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
              .type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
              .sessionThreadId(thread.id())
              .build())
          .build());

  archived = client.beta().sessions().threads().archive(
      thread.id(),
      ThreadArchiveParams.builder()
          .sessionId(session.id())
          .build());
  IO.println(archived.status() + " " + archived.archivedAt().orElseThrow());
  ```

  ```php PHP
  $client->beta->sessions->events->send(
      $session->id,
      events: [['type' => 'user.interrupt', 'session_thread_id' => $thread->id]],
  );
  $archived = $client->beta->sessions->threads->archive($thread->id, sessionID: $session->id);
  echo "{$archived->status} {$archived->archivedAt->format(DATE_ATOM)}\n";
  ```

  ```ruby Ruby
  client.beta.sessions.events.send_(
    session.id,
    events: [{type: "user.interrupt", session_thread_id: thread.id}]
  )
  archived = client.beta.sessions.threads.archive(thread.id, session_id: session.id)
  puts "#{archived.status} #{archived.archived_at}"
  ```
</CodeGroup>

## 기본 스레드 이벤트

이러한 이벤트는 `/v1/sessions/{session_id}/events/stream`의 기본 스레드에서 멀티에이전트 활동을 보여줍니다. 메시지 방향 이벤트는 해당 이벤트가 나타나는 스트림의 스레드를 기준으로 이름이 지정됩니다. `agent.thread_message_received`는 다른 스레드로부터 이 스레드에 메시지가 도착했음을 의미하고, `agent.thread_message_sent`는 이 스레드가 메시지를 보냈음을 의미합니다. 예를 들어 기본 스레드의 에이전트가 위임한 작업은 하위 스레드 자체의 스트림에 `agent.thread_message_received` 이벤트로 도착합니다.

| 유형                                 | 설명                                                                                                                                                                                                                                                                                           |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.thread_created`           | 스레드가 생성되었습니다. `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`)와 `agent_name` (java, php: `agentName`; csharp, go: `AgentName`)을 포함합니다.                                                                                                   |
| `session.thread_status_running`    | 스레드가 활동을 시작했습니다.                                                                                                                                                                                                                                                                             |
| `session.thread_status_idle`       | 스레드와 연결된 에이전트가 입력을 기다리고 있습니다. 에이전트가 중지된 이유를 나타내는 `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`)을 포함합니다.                                                                                                                                                                   |
| `session.thread_status_terminated` | 스레드가 종료되어 더 이상 입력을 받지 않습니다. 예를 들어 보관되었거나 복구할 수 없는 오류가 발생한 경우입니다. 어드바이저 스레드도 자문이 끝나면 종료됩니다.                                                                                                                                                                                                   |
| `agent.thread_message_received`    | 기본 스레드에서, 서브에이전트가 기본 스레드의 에이전트에 보고나 질문을 보냈습니다. `from_session_thread_id` (java: `fromSessionThreadId`; php: `fromSessionThreadID`; csharp, go: `FromSessionThreadID`), `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`), `content` (csharp, go: `Content`)를 포함합니다. |
| `agent.thread_message_sent`        | 기본 스레드에서, 기본 스레드의 에이전트가 서브에이전트에 작업이나 후속 메시지를 보냈습니다. `to_session_thread_id` (java: `toSessionThreadId`; php: `toSessionThreadID`; csharp, go: `ToSessionThreadID`), `to_agent_name` (java, php: `toAgentName`; csharp, go: `ToAgentName`), `content` (csharp, go: `Content`)를 포함합니다.          |

어드바이저 자문은 예약된 이름 `anthropic.advisor`로 이와 동일한 스레드 이벤트를 발생시킵니다(스레드 수명 주기 이벤트에서는 `agent_name` (java, php: `agentName`; csharp, go: `AgentName`)으로, 조언 전달에서는 `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`)으로). 순서는 [세션에 어드바이저 제공하기](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration#give-the-session-an-advisor)를 참조하세요.

[워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)의 스레드는 기본 스트림에 다음과 같이 표시됩니다:

* **수명 주기 이벤트:** 각 실행 스레드는 실행의 `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`)와 함께 `session.thread_created`를 보내고, `session.thread_status_running`, `session.thread_status_idle`, `session.thread_status_terminated` 이벤트를 보냅니다.
* **메시지 이벤트:** 실행 스레드의 프롬프트인 `agent.thread_message_received` 이벤트는 해당 스레드 자체의 스트림에만 남습니다.
* **실행 이벤트:** `workflow_run.*` 이벤트도 이 스트림에 도착합니다. [실행 이벤트](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#run-events)를 참조하세요.
* **사용자를 기다리는 도구 호출:** 클라이언트가 필요한 실행 스레드의 도구 호출은 다른 하위 스레드와 마찬가지로 이 스트림에 교차 게시됩니다. [도구 권한 및 사용자 정의 도구](https://platform.claude.com/docs/ko/managed-agents/session-threads#tool-permissions-and-custom-tools)를 참조하세요.

## 세션 스레드 이벤트

중요한 이벤트는 기본 스레드로 전달됩니다. 하지만 특정 에이전트의 추론과 도구 호출을 조사하고 싶을 수도 있습니다. 그렇게 하려면 연결된 세션 스레드의 이벤트를 스트리밍하거나 나열하세요.

각 세션 스레드에는 `/v1/sessions/{session_id}/threads/{thread_id}/stream`에 자체 이벤트 스트림이 있으며, 세션 수준 스트림과 동일한 `event_deltas[]` 매개변수를 받으므로 모델이 생성하는 대로 서브에이전트의 텍스트를 미리 볼 수 있습니다. 연결은 읽고 있는 스레드만 미리 봅니다. 하위 스레드의 미리보기는 세션 수준 스트림에 절대 나타나지 않으므로, 서브에이전트를 실시간으로 보려면 해당 스레드 자체의 스트림을 여세요. 옵트인, 누적, 미리보기 조정에 대해서는 [세션 스레드 이벤트 미리보기](https://platform.claude.com/docs/ko/managed-agents/event-deltas#preview-session-thread-events)를 참조하세요.

워크플로 실행에서 서버는 워크플로, 즉 기본 스레드의 에이전트가 작성한 프로그램을 실행합니다. 실행의 각 스레드에서 첫 번째 `agent.thread_message_received`는 워크플로가 작성한 프롬프트입니다. 해당 이벤트의 `from_session_thread_id` (java: `fromSessionThreadId`; php: `fromSessionThreadID`; csharp, go: `FromSessionThreadID`)는 기본 스레드의 ID이며, API는 `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`)을 설정하지 않습니다. API는 프롬프트의 텍스트를 보장하지 않으므로 이를 파싱하지 마세요. 기본 스레드의 스트림에 있는 스레드의 `session.thread_status_terminated` 이벤트는 스레드가 완료되었음을 알려줍니다. 스레드가 워크플로에 반환한 결과를 기록하는 이벤트는 없습니다.

스레드의 스트림은 이전 이벤트를 재생하지 않습니다. `session.thread_created` 직후에는 서버가 그 이후에 스레드의 첫 번째 이벤트를 기록하기 때문에 실행 스레드의 이벤트 목록이 비어 있을 수 있습니다. 따라서 먼저 스레드의 스트림을 연 다음 스레드의 이벤트를 나열하고, 목록이 반환한 `id` (csharp, go: `ID`)를 가진 스트리밍된 이벤트는 건너뛰세요.

<Tabs>
  <Tab title="세션 스레드 이벤트 스트리밍">
    <CodeGroup>
      ```bash cURL
      curl -fsSN "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/stream?beta=true" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "anthropic-beta: managed-agents-2026-04-01"
      ```

      ```bash CLI
      ant beta:sessions:threads:events stream \
        --session-id "$SESSION_ID" \
        --thread-id "$THREAD_ID"
      ```

      ```python Python
      with client.beta.sessions.threads.events.stream(
          thread.id,
          session_id=session.id,
      ) as stream:
          for event in stream:
              match event.type:
                  case "agent.message":
                      for block in event.content:
                          if block.type == "text":
                              print(block.text, end="")
                  case "session.thread_status_idle":
                      break
      ```

      ```typescript TypeScript
      const stream = await client.beta.sessions.threads.events.stream(thread.id, {
        session_id: session.id
      });

      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 "session.thread_status_idle":
            break loop;
        }
      }
      ```

      ```csharp C#
      await foreach (var evt in client.Beta.Sessions.Threads.Events.StreamStreaming(thread.ID, new() { SessionID = session.ID }))
      {
          if (evt.Value is BetaManagedAgentsAgentMessageEvent message)
          {
              foreach (var block in message.Content)
              {
                  if (block.Value is BetaManagedAgentsTextBlock textBlock)
                  {
                      Console.Write(textBlock.Text);
                  }
              }
          }
          else if (evt.Value is BetaManagedAgentsSessionThreadStatusIdleEvent)
          {
              break;
          }
      }
      ```

      ```go Go
      	stream := client.Beta.Sessions.Threads.Events.StreamEvents(ctx, thread.ID, anthropic.BetaSessionThreadEventStreamParams{
      		SessionID: session.ID,
      	})
      	defer stream.Close()

      loop:
      	for stream.Next() {
      		event := stream.Current()
      		switch event.Type {
      		case "agent.message":
      			for _, block := range event.AsAgentMessage().Content {
      				if block.Type == "text" {
      					fmt.Print(block.Text)
      				}
      			}
      		case "session.thread_status_idle":
      			break loop
      		}
      	}
      	if err := stream.Err(); err != nil {
      		panic(err)
      	}
      ```

      ```java Java
      try (var streamResponse = client.beta().sessions().threads().events().streamStreaming(
          thread.id(),
          EventStreamParams.builder().sessionId(session.id()).build()
      )) {
          loop:
          for (var event : (Iterable<BetaManagedAgentsStreamSessionThreadEvents>) streamResponse.stream()::iterator) {
              switch (event.type().value()) {
                  case AGENT_MESSAGE -> {
                      for (var block : event.asAgentMessage().content()) {
                          block.text().ifPresent(textBlock -> IO.print(textBlock.text()));
                      }
                  }
                  case SESSION_THREAD_STATUS_IDLE -> {
                      break loop;
                  }
              }
          }
      }
      ```

      ```php PHP
      $stream = $client->beta->sessions->threads->events->streamStream(
          $thread->id,
          sessionID: $session->id,
      );

      foreach ($stream as $event) {
          switch (true) {
              case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent:
                  foreach ($event->content as $block) {
                      if ($block instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsTextBlock) {
                          echo $block->text;
                      }
                  }
                  break;
              case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionThreadStatusIdleEvent:
                  break 2;
          }
      }
      ```

      ```ruby Ruby
      client.beta.sessions.threads.events.stream_events(thread.id, session_id: session.id).each do |event|
        case event
        when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
          event.content.each do |block|
            print block.text if block.is_a?(Anthropic::Beta::Sessions::BetaManagedAgentsTextBlock)
          end
        when Anthropic::Beta::Sessions::BetaManagedAgentsSessionThreadStatusIdleEvent
          break
        end
      end
      ```
    </CodeGroup>
  </Tab>

  <Tab title="세션 스레드 이벤트 나열">
    전체 기록을 가져오려면 과거의 모든 세션 스레드 이벤트를 나열하세요.

    <CodeGroup>
      ```bash cURL
      curl -fsS "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/events" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "anthropic-beta: managed-agents-2026-04-01"
      ```

      ```bash CLI
      ant beta:sessions:threads:events list \
        --session-id "$SESSION_ID" \
        --thread-id "$THREAD_ID"
      ```

      ```python Python
      for event in client.beta.sessions.threads.events.list(
          thread.id,
          session_id=session.id,
      ):
          print(f"[{event.type}] {event.processed_at}")
      ```

      ```typescript TypeScript
      for await (const event of client.beta.sessions.threads.events.list(thread.id, {
        session_id: session.id
      })) {
        console.log(`[${event.type}] ${event.processed_at}`);
      }
      ```

      ```csharp C#
      var page = await client.Beta.Sessions.Threads.Events.List(thread.ID, new() { SessionID = session.ID });
      await foreach (var evt in page.Paginate())
      {
          Console.WriteLine($"[{evt.Json.GetProperty("type").GetString()}] {evt.ProcessedAt}");
      }
      ```

      ```go Go
      pager := client.Beta.Sessions.Threads.Events.ListAutoPaging(ctx, thread.ID, anthropic.BetaSessionThreadEventListParams{
      	SessionID: session.ID,
      })
      for pager.Next() {
      	event := pager.Current()
      	fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
      }
      if err := pager.Err(); err != nil {
      	panic(err)
      }
      ```

      ```java Java
      for (var event : client.beta().sessions().threads().events().list(
              thread.id(),
              EventListParams.builder().sessionId(session.id()).build()
          ).autoPager()) {
          var type = event._json().orElseThrow() instanceof JsonObject json
              ? json.values().get("type").asStringOrThrow()
              : "unknown";
          var processedAt = event.processedAt().map(OffsetDateTime::toString).orElse("pending");
          IO.println("[" + type + "] " + processedAt);
      }
      ```

      ```php PHP
      foreach (
          $client->beta->sessions->threads->events->list(
              $thread->id,
              sessionID: $session->id,
          )->pagingEachItem() as $event
      ) {
          echo "[{$event->type}] {$event->processedAt->format(DATE_RFC3339)}\n";
      }
      ```

      ```ruby Ruby
      client.beta.sessions.threads.events.list(
        thread.id,
        session_id: session.id
      ).auto_paging_each do |event|
        puts "[#{event.type}] #{event.processed_at}"
      end
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## 도구 권한 및 사용자 정의 도구

서브에이전트가 도구 호출을 실행하기 위한 [권한](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#tool-confirmation)이나 [사용자 정의 도구의 결과](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#handling-custom-tool-calls)처럼 클라이언트로부터 무언가가 필요한 경우, 해당 이벤트는 원래 세션 스레드를 식별하는 `session_thread_id`와 함께 **기본 스레드**에 교차 게시됩니다. 도구 호출은 `always_ask`에서, 또는 서버가 판단을 내리지 못한 경우 [`auto`](https://platform.claude.com/docs/ko/managed-agents/permission-policies#let-the-server-evaluate-each-call-with-auto)에서 사용자의 권한이 필요합니다.

```json
{
  "type": "session.thread_status_idle",
  "id": "sevt_01ABC...",
  "session_thread_id": "sthr_01DEF...",
  "agent_name": "code-reviewer",
  "stop_reason": {
    "type": "requires_action",
    "event_ids": ["sevt_01XYZ..."]
  }
}
```

`user.tool_confirmation`(`tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`) 포함) 또는 `user.custom_tool_result`(`custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`) 포함)를 게시하세요. 서버가 응답을 올바른 스레드로 자동 라우팅합니다. 응답은 기본 스레드와 서브에이전트의 스레드에 서로 다른 `id` (csharp, go: `ID`) 값으로 나타날 수 있습니다. 두 복사본을 대조하려면 `id` (csharp, go: `ID`)가 아니라 `type` (csharp, go: `Type`)과 `tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`)(또는 `custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`))를 비교하세요.

세션은 `running` 상태인 스레드가 없을 때만 `idle`이 되므로, `session.status_idle`은 서브에이전트의 호출보다 훨씬 나중에 도착할 수 있습니다. 이를 기다릴 필요는 없습니다. 교차 게시된 `agent.custom_tool_use` 이벤트가 도착하는 즉시 `user.custom_tool_result`를 보내세요.

`auto`에서는 사용자의 `user.message` 이벤트로 인해 서버가 원래라면 거부했을 호출을 허용할 수 있습니다. 서브에이전트의 스레드에 있는 어떤 것도 사용자의 의도로 간주되지 않습니다. 클라이언트는 그곳에 메시지를 게시하지 않으며, 기본 스레드의 에이전트가 서브에이전트에게 보내는 메시지도 사용자의 의도로 간주되지 않습니다. 서버가 `auto`에서 호출을 거부하면 아무것도 교차 게시되지 않습니다. 이벤트와 오류 도구 결과는 서브에이전트 자체의 [스레드 스트림](https://platform.claude.com/docs/ko/managed-agents/session-threads#session-thread-events)에만 나타나며, 서브에이전트는 계속 실행됩니다.

다음 예제는 [도구 확인 핸들러](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#tool-confirmation)의 이벤트 루프 안에 들어갑니다. `stop_reason.event_ids`의 각 ID에 대해 호출을 허용하는 `user.tool_confirmation`을 보냅니다. 동일한 패턴이 `user.custom_tool_result`에도 적용되며, 이는 해당 `agent.custom_tool_use` 이벤트가 도착하는 즉시 보낼 수도 있습니다.

<CodeGroup exclude="shell:cURL, shell:CLI">
  <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
  for event_id in stop.event_ids:
      client.beta.sessions.events.send(
          session.id,
          events=[
              {
                  "type": "user.tool_confirmation",
                  "tool_use_id": event_id,
                  "result": "allow",
              }
          ],
      )
  ```

  ```typescript TypeScript
  for (const eventId of stop.event_ids) {
    await client.beta.sessions.events.send(session.id, {
      events: [
        {
          type: "user.tool_confirmation",
          tool_use_id: eventId,
          result: "allow"
        }
      ]
    });
  }
  ```

  ```csharp C#
  foreach (var eventId in requiresAction.EventIds)
  {
      await client.Beta.Sessions.Events.Send(session.ID, new()
      {
          Events =
          [
              new BetaManagedAgentsUserToolConfirmationEventParams
              {
                  Type = BetaManagedAgentsUserToolConfirmationEventParamsType.UserToolConfirmation,
                  ToolUseID = eventId,
                  Result = BetaManagedAgentsUserToolConfirmationEventParamsResult.Allow,
              },
          ],
      });
  }
  ```

  ```go Go
  for _, eventID := range stopReason.EventIDs {
  	params := anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
  		Type:      anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
  		ToolUseID: eventID,
  		Result:    anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
  	}
  	if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  		Events: []anthropic.BetaManagedAgentsEventParamsUnion{{OfUserToolConfirmation: &params}},
  	}); err != nil {
  		panic(err)
  	}
  }
  ```

  ```java Java
  for (var eventId : pendingToolUseIds) {
      client.beta().sessions().events().send(
          session.id(),
          EventSendParams.builder()
              .addEvent(BetaManagedAgentsUserToolConfirmationEventParams.builder()
                  .type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
                  .toolUseId(eventId)
                  .result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
                  .build())
              .build()
      );
  }
  ```

  ```php PHP
  foreach ($event->stopReason->eventIDs as $eventId) {
      $client->beta->sessions->events->send($session->id, events: [[
          'type' => 'user.tool_confirmation',
          'tool_use_id' => $eventId,
          'result' => 'allow',
      ]]);
  }
  ```

  ```ruby Ruby
  event_ids.each do |event_id|
    client.beta.sessions.events.send_(session.id, events: [{
      type: "user.tool_confirmation",
      tool_use_id: event_id,
      result: "allow"
    }])
  end
  ```
</CodeGroup>

앞의 패턴은 유휴 이벤트가 나열하는 호출에 응답합니다. 기본 스트림에서 서브에이전트의 `session.thread_status_idle` 이벤트는 해당 `stop_reason.event_ids`가 나열하는 `agent.tool_use` 또는 `agent.mcp_tool_use` 이벤트보다 먼저 도착할 수 있습니다. 아직 이벤트가 도착하지 않은 호출에 대한 `user.tool_confirmation`은 400을 반환할 수 있습니다. 이를 방지하려면 [`evaluated_permission` (java, php: `evaluatedPermission`; csharp, go: `EvaluatedPermission`)](https://platform.claude.com/docs/ko/managed-agents/permission-policies#see-how-each-call-was-evaluated)이 `ask`인 각 호출에 대해, 해당 호출 자체의 이벤트가 기본 스트림에 도착할 때 응답하세요.
