---
title: 세션 이벤트 스트림
url: https://platform.claude.com/docs/ko/managed-agents/events-and-streaming
description: 이벤트를 전송하고, 응답을 스트리밍하며, 실행 도중 세션을 중단하거나 방향을 전환합니다.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

Claude Managed Agents와의 통신은 이벤트 기반입니다. 세션을 추적하려면 스트림을 열고, 세션을 지시하려면 이벤트를 전송하며, 세션이 입력을 기다리며 일시 중지되면 응답합니다.

## 이벤트 유형

이벤트는 두 방향으로 흐릅니다. 사용자가 에이전트에 이벤트를 전송하고, 세션은 사용자에게 이벤트를 다시 전송합니다.

* **전송하는 이벤트:** `user.*` 이벤트는 세션을 시작하고 진행 중인 세션을 조정합니다. [`system.message`](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#send-system-messages) 이벤트는 시스템 수준 컨텍스트를 추가합니다.
* **수신하는 이벤트:** 세션 이벤트, span 이벤트, 에이전트 이벤트는 세션의 상태와 에이전트의 진행 상황을 보고합니다.

이러한 이벤트 유형 문자열은 `{domain}.{action}` 명명 규칙을 따릅니다. 전체 목록은 레퍼런스의 [이벤트 유형](https://platform.claude.com/docs/ko/managed-agents/reference#event-types)을 참조하세요.

[Webhook 이벤트 유형](https://platform.claude.com/docs/ko/managed-agents/webhooks#supported-event-types)은 별개이며, 일부 이름은 스트림의 이름과 다릅니다. 예를 들어 webhook은 `session.status_idle` 대신 `session.status_idled`를 사용합니다.

## 이벤트 스트리밍

에이전트가 작업하는 동안 실시간 업데이트를 받으려면 세션에서 이벤트를 스트리밍하세요. 스트림은 열린 이후에 발생한 이벤트만 전달하므로, 경쟁 조건(race condition)을 피하려면 이벤트를 전송하기 전에 스트림을 여세요.

<CodeGroup>
  ```bash cURL
  # 첫 이벤트를 보내기 전에 스트림을 여세요. 이후 이벤트만 전달됩니다.
  curl --fail-with-body -sS -N \
    "https://api.anthropic.com/v1/sessions/$SESSION_ID/events/stream?beta=true" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -H "accept: text/event-stream"
  ```

  ```bash CLI
  # 첫 이벤트를 보내기 전에 스트림을 여세요. 이후 이벤트만 전달됩니다.
  ant beta:sessions:events stream --session-id "$SESSION_ID"
  ```

  ```python Python
  # 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  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": "Summarize the repo README"}],
              },
          ],
      )

      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.status_idle":
                  break
              case "session.error":
                  error_message = event.error.message if event.error else "unknown"
                  print(f"\n[Error: {error_message}]")
                  break
  ```

  ```typescript TypeScript
  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  const stream = await client.beta.sessions.events.stream(session.id);
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "user.message",
        content: [{ type: "text", text: "Summarize the repo README" }]
      }
    ]
  });

  events: 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.status_idle":
        break events;
      case "session.error":
        console.log(`\n[Error: ${event.error?.message ?? "unknown"}]`);
        break events;
    }
  }
  ```

  ```csharp C#
  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Summarize the repo README",
                  },
              ],
          },
      ],
  });

  await foreach (var streamEvent in stream.Enumerate())
  {
      if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
      {
          foreach (var block in message.Content)
          {
              if (block.Value is BetaManagedAgentsTextBlock textBlock)
              {
                  Console.Write(textBlock.Text);
              }
          }
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
      {
          break;
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionErrorEvent error)
      {
          Console.WriteLine($"\n[Error: {error.Error?.Message ?? "unknown"}]");
          break;
      }
  }
  ```

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

  	if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  		Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  			OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
  				Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  				Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  					OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  						Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  						Text: "Summarize the repo README",
  					},
  				}},
  			},
  		}},
  	}); err != nil {
  		panic(err)
  	}

  events:
  	for stream.Next() {
  		switch event := stream.Current().AsAny().(type) {
  		case anthropic.BetaManagedAgentsAgentMessageEvent:
  			// 구체 타입 목록: BetaManagedAgentsTextBlock
  			for _, block := range event.Content {
  				fmt.Print(block.Text)
  			}
  		case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
  			break events
  		case anthropic.BetaManagedAgentsSessionErrorEvent:
  			fmt.Printf("\n[Error: %s]\n", cmp.Or(event.Error.Message, "unknown"))
  			break events
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  ```

  ```java Java
  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
      client.beta().sessions().events().send(
          session.id(),
          EventSendParams.builder()
              .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
                  .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
                  .addTextContent("Summarize the repo README")
                  .build())
              .build()
      );

      Iterable<BetaManagedAgentsStreamSessionEvents> events = stream.stream()::iterator;
      events:
      for (var event : events) {
          switch (event.type().value()) {
              case AGENT_MESSAGE -> event.asAgentMessage().content().forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text())));
              case SESSION_STATUS_IDLE -> {
                  break events;
              }
              case SESSION_ERROR -> {
                  // `message` 필드는 모든 오류 변형에 걸쳐 있으므로 원시 JSON에서 읽습니다.
                  var errorMessage =
                      event.asSessionError().error()._json().orElse(null) instanceof JsonObject json
                          ? json.values().get("message").asStringOrThrow()
                          : "unknown";
                  IO.println("\n[Error: " + errorMessage + "]");
                  break events;
              }
          }
      }
  }
  ```

  ```php PHP
  use Anthropic\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsSessionErrorEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsTextBlock;

  // 먼저 스트림을 연 다음 사용자 메시지를 보냅니다
  $stream = $client->beta->sessions->events->streamStream($session->id);
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'user.message',
              'content' => [['type' => 'text', 'text' => 'Summarize the repo README']],
          ],
      ],
  );

  foreach ($stream as $event) {
      match (true) {
          $event instanceof ManagedAgentsAgentMessageEvent => array_walk(
              $event->content,
              static fn ($block) => $block instanceof ManagedAgentsTextBlock ? print($block->text) : null,
          ),
          $event instanceof ManagedAgentsSessionErrorEvent => printf("\n[Error: %s]", $event->error?->message ?? 'unknown'),
          default => null,
      };
      if ($event->type === 'session.status_idle' || $event->type === 'session.error') {
          break;
      }
  }
  $stream->close();
  ```

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

  client.beta.sessions.events.send_(
    session.id,
    events: [{
      type: "user.message",
      content: [{type: "text", text: "Summarize the repo README"}]
    }]
  )

  stream.each do |event|
    case event
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
      event.content.each { print it.text }
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      break
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionErrorEvent
      puts "\n[Error: #{event.error&.message || "unknown"}]"
      break
    else
      # 다른 이벤트 유형은 무시합니다
    end
  end
  ```
</CodeGroup>

세션은 `session.error` 이벤트를 통해 오류를 보고합니다. 해당 필드는 [이벤트 유형](https://platform.claude.com/docs/ko/managed-agents/reference#event-types)을 참조하세요.

에이전트의 응답 텍스트는 버퍼링된 `agent.message` 이벤트로 도착하며, 각 이벤트는 이를 생성한 모델 요청이 완료된 후에만 발생합니다. 모델이 아직 생성 중인 텍스트를 렌더링하려면 [이벤트 델타로 응답 미리 보기](https://platform.claude.com/docs/ko/managed-agents/event-deltas)를 참조하세요.

### 이벤트 누락 없이 재연결하기

이벤트를 놓치지 않고 기존 세션에 재연결하려면 새 스트림과 이벤트 기록을 결합하세요.

1. 새 스트림을 엽니다.
2. [전체 이벤트 기록을 조회](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#list-past-events)하여 이미 확인한 이벤트 ID 집합을 초기화합니다.
3. 라이브 스트림을 추적하면서 기록 목록에서 이미 반환된 이벤트는 건너뜁니다.

<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
  with client.beta.sessions.events.stream(session.id) as stream:
      # 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 조회합니다.
      history = client.beta.sessions.events.list(session.id)
      seen_event_ids = {past_event.id for past_event in history}

      # 이미 본 이벤트는 건너뛰고 라이브 이벤트를 추적합니다
      for event in stream:
          if event.type == "event_start" or event.type == "event_delta":
              # 이 연결에서는 델타 미리보기가 활성화되어 있지 않습니다.
              continue
          if event.id in seen_event_ids:
              continue
          seen_event_ids.add(event.id)
          match event.type:
              case "agent.message":
                  for block in event.content:
                      if block.type == "text":
                          print(block.text, end="")
              case "session.status_idle":
                  break
  ```

  ```typescript TypeScript
  const seenEventIds = new Set<string>();
  const stream = await client.beta.sessions.events.stream(session.id);

  // 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 조회합니다.
  for await (const event of client.beta.sessions.events.list(session.id)) {
    seenEventIds.add(event.id);
  }

  // 이미 본 항목은 건너뛰고 라이브 이벤트를 추적합니다
  tail: for await (const event of stream) {
    // 미리보기 이벤트(event_start/event_delta)에는 최상위 id가 없습니다
    if (event.type === "event_start" || event.type === "event_delta") {
      continue;
    }
    if (seenEventIds.has(event.id)) {
      continue;
    }
    seenEventIds.add(event.id);
    switch (event.type) {
      case "agent.message":
        for (const block of event.content) {
          if (block.type === "text") {
            process.stdout.write(block.text);
          }
        }
        break;
      case "session.status_idle":
        break tail;
    }
  }
  ```

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

  // 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 조회합니다.
  HashSet<string> seenEventIds = [];
  var history = await client.Beta.Sessions.Events.List(session.ID);
  await foreach (var pastEvent in history.Paginate())
  {
      seenEventIds.Add(pastEvent.ID);
  }

  // 이미 본 이벤트는 건너뛰고 라이브 이벤트를 추적합니다
  await foreach (var streamEvent in stream.Enumerate())
  {
      if (!seenEventIds.Add(streamEvent.ID))
      {
          continue;
      }
      if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
      {
          foreach (var block in message.Content)
          {
              if (block.Value is BetaManagedAgentsTextBlock textBlock)
              {
                  Console.Write(textBlock.Text);
              }
          }
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
      {
          break;
      }
  }
  ```

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

  	// 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 조회합니다.
  	seenEventIDs := map[string]struct{}{}
  	history := client.Beta.Sessions.Events.ListAutoPaging(ctx, session.ID, anthropic.BetaSessionEventListParams{})
  	for history.Next() {
  		seenEventIDs[history.Current().ID] = struct{}{}
  	}
  	if err := history.Err(); err != nil {
  		panic(err)
  	}

  	// 이미 본 항목은 건너뛰고 라이브 이벤트를 추적합니다
  tail:
  	for stream.Next() {
  		event := stream.Current()
  		if _, seen := seenEventIDs[event.ID]; seen {
  			continue
  		}
  		seenEventIDs[event.ID] = struct{}{}
  		switch event := event.AsAny().(type) {
  		case anthropic.BetaManagedAgentsAgentMessageEvent:
  			// 구체 타입 목록: BetaManagedAgentsTextBlock
  			for _, block := range event.Content {
  				fmt.Print(block.Text)
  			}
  		case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
  			break tail
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  ```

  ```java Java
  try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
      // 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 나열합니다.
      // 모든 이벤트 변형에는 `id`가 있으므로 변형 간 중복 제거를 위해 원시 JSON에서 읽습니다.
      var seenEventIds = new HashSet<String>();
      for (var pastEvent : client.beta().sessions().events().list(session.id()).autoPager()) {
          if (pastEvent._json().orElseThrow() instanceof JsonObject json) {
              seenEventIds.add(json.values().get("id").asStringOrThrow());
          }
      }

      // 라이브 이벤트를 추적합니다. 이미 본 ID는 Set.add가 false를 반환하므로 재생을 건너뜁니다.
      stream.stream()
          .filter(event -> event._json().orElseThrow() instanceof JsonObject json
              && seenEventIds.add(json.values().get("id").asStringOrThrow()))
          .takeWhile(event -> !event.isSessionStatusIdle())
          .filter(BetaManagedAgentsStreamSessionEvents::isAgentMessage)
          .forEach(event -> event.asAgentMessage().content()
              .forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text()))));
  }
  ```

  ```php PHP
  use Anthropic\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsTextBlock;

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

  // 스트림이 열려 버퍼링 중입니다. 라이브 이벤트를 추적하기 전에 기록을 조회합니다.
  $seenEventIds = [];
  foreach ($client->beta->sessions->events->list($session->id)->pagingEachItem() as $event) {
      $seenEventIds[$event->id] = true;
  }

  // 이미 본 이벤트는 건너뛰고 라이브 이벤트를 추적합니다
  foreach ($stream as $event) {
      if (isset($seenEventIds[$event->id])) {
          continue;
      }
      $seenEventIds[$event->id] = true;
      match (true) {
          $event instanceof ManagedAgentsAgentMessageEvent => array_walk(
              $event->content,
              static fn ($block) => $block instanceof ManagedAgentsTextBlock ? print($block->text) : null,
          ),
          default => null,
      };
      if ($event->type === 'session.status_idle') {
          break;
      }
  }
  $stream->close();
  ```

  ```ruby Ruby
  stream = client.beta.sessions.events.stream_events(session.id)

  # 스트림이 열려 버퍼링 중입니다. 라이브 추적 전에 기록을 조회합니다.
  seen_event_ids = Set.new
  client.beta.sessions.events.list(session.id).auto_paging_each { seen_event_ids << it.id }

  # 이미 본 이벤트는 건너뛰며 라이브 이벤트를 추적합니다 — Set#add?는 중복 시 nil을 반환합니다
  stream.each do |event|
    next unless seen_event_ids.add?(event.id)
    case event
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
      event.content.each { print it.text }
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      break
    else
      # 다른 이벤트 유형은 무시합니다
    end
  end
  ```
</CodeGroup>

## 이벤트 전송

에이전트의 작업을 시작하거나 계속하려면 `user.message` 이벤트를 전송하세요.

<CodeGroup>
  ```bash cURL
  curl --fail-with-body -sS "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.message",
          "content": [
            {"type": "text", "text": "Analyze the performance of the sort function in utils.py"}
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
  events:
    - type: user.message
      content:
        - type: text
          text: Analyze the performance of the sort function in utils.py
  YAML
  ```

  ```python Python
  client.beta.sessions.events.send(
      session.id,
      events=[
          {
              "type": "user.message",
              "content": [
                  {
                      "type": "text",
                      "text": "Analyze the performance of the sort function in utils.py",
                  },
              ],
          },
      ],
  )
  ```

  ```typescript TypeScript
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Analyze the performance of the sort function in utils.py"
          }
        ]
      }
    ]
  });
  ```

  ```csharp C#
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Analyze the performance of the sort function in utils.py",
                  },
              ],
          },
      ],
  });
  ```

  ```go Go
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  		OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
  			Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  			Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  				OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  					Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  					Text: "Analyze the performance of the sort function in utils.py",
  				},
  			}},
  		},
  	}},
  }); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
              .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
              .addTextContent("Analyze the performance of the sort function in utils.py")
              .build())
          .build());
  ```

  ```php PHP
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'user.message',
              'content' => [
                  [
                      'type' => 'text',
                      'text' => 'Analyze the performance of the sort function in utils.py',
                  ],
              ],
          ],
      ],
  );
  ```

  ```ruby Ruby
  client.beta.sessions.events.send_(
    session.id,
    events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Analyze the performance of the sort function in utils.py"
          }
        ]
      }
    ]
  )
  ```
</CodeGroup>

세션 기록의 모든 이벤트에는 이벤트 처리가 완료될 때 설정되는 `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`) 타임스탬프가 포함됩니다. 사용자가 전송한 이벤트의 경우, 이벤트가 이전 이벤트 뒤에서 아직 대기 중인 동안 `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`)는 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`)입니다. 예외는 `user.define_outcome`, `user.custom_tool_result`, `user.tool_result`로, 이들은 수신 즉시 처리되며 `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`)가 이미 채워진 상태로 반환됩니다.

## 세션이 유휴 상태가 되면 응답하기

`session.status_idle` 이벤트는 에이전트가 중지되어 입력을 기다리고 있음을 의미합니다. 해당 이벤트의 `stop_reason.type`이 그 이유를 알려줍니다.

| `stop_reason.type` | 세션이 중지된 이유                                                                                                                | 해야 할 일                                                                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `end_turn`         | 에이전트가 턴을 마쳤거나 [사용자가 중단했습니다](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#interrupt-the-agent). | 에이전트에게 맡길 작업이 더 있으면 [`user.message`를 전송하세요](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#resume-an-idle-session). |
| `requires_action`  | 커스텀 도구 호출이나 확인 요청 등 하나 이상의 도구 호출에 사용자의 응답이 필요합니다.                                                                         | [차단 중인 각 도구 호출에 응답하세요](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#answer-tool-calls-that-pause-the-session).    |
| `budget_reached`   | 세션의 추적된 정가 비용이 [예산](https://platform.claude.com/docs/ko/managed-agents/budgets)에 도달했습니다.                                  | [예산을 변경하거나 제거하세요](https://platform.claude.com/docs/ko/managed-agents/budgets#resume-a-session-at-its-budget).                                |

예산에 도달하여 일시 중지된 세션은 어떤 이벤트를 전송해도 재개되지 않습니다. 예산으로 인해 일시 중지된 작업은 예산을 소비된 정가 비용보다 높은 값으로 변경하거나 예산을 제거하면 자동으로 재개됩니다. 작업이 재개되면 세션은 [예산으로 인해 일시 중지된 워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#budgets-and-limits)마다 `workflow_run.status_running` 이벤트를 발생시킵니다. 중단(interrupt)으로 일시 중지된 실행은 일시 중지 상태로 유지됩니다. [예산에 도달한 세션 재개하기](https://platform.claude.com/docs/ko/managed-agents/budgets#resume-a-session-at-its-budget)를 참조하세요. 일시 중지를 나타내는 이벤트와 세션이 여전히 수락하는 이벤트는 [세션이 예산에 도달했을 때](https://platform.claude.com/docs/ko/managed-agents/budgets#when-a-session-reaches-its-budget)를 참조하세요.

## 세션을 일시 중지시키는 도구 호출에 응답하기

세션은 에이전트가 [커스텀 도구](https://platform.claude.com/docs/ko/managed-agents/tools#custom-tools)를 호출할 때, 그리고 [권한 정책](https://platform.claude.com/docs/ko/managed-agents/permission-policies)에 따라 도구 호출에 사용자의 확인이 필요할 때 일시 중지됩니다. 두 경우 모두 동일한 순서를 따릅니다.

1. 세션이 도구 호출을 `agent.custom_tool_use`, `agent.tool_use` 또는 `agent.mcp_tool_use` 이벤트로 발생시킵니다.
2. 세션이 `stop_reason.type`이 `requires_action`인 `session.status_idle` 이벤트와 함께 일시 중지됩니다. 차단 중인 이벤트 ID는 `stop_reason.event_ids` 배열에 있습니다.
3. 차단 중인 각 이벤트 ID에 대해 [`user.custom_tool_result`](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#return-a-custom-tool-result) 또는 [`user.tool_confirmation`](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#confirm-a-tool-call) 이벤트를 전송합니다. 커스텀 도구 결과는 2단계를 기다릴 필요가 없습니다.
4. 차단 중인 모든 이벤트가 해결되면 세션은 다시 `running` 상태로 전환됩니다.

멀티에이전트 세션에서는 서브에이전트의 차단 이벤트가 기본 스레드에도 게시됩니다. [도구 권한 및 커스텀 도구](https://platform.claude.com/docs/ko/managed-agents/session-threads#tool-permissions-and-custom-tools)를 참조하세요.

### 커스텀 도구 결과 반환하기

`agent.custom_tool_use` 이벤트에는 도구 이름과 입력이 포함되어 있습니다. 사용자의 시스템에서 도구를 실행하세요. 그런 다음 `custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`) 매개변수에 이벤트 ID를 전달하고 결과 콘텐츠와 함께 `user.custom_tool_result` 이벤트를 전송하세요.

`session.status_idle`을 기다리지 않고 `agent.custom_tool_use` 이벤트가 도착하는 즉시 결과를 전송할 수 있습니다. 세션은 해당 호출에 대해 여전히 `requires_action` 중지 이유와 함께 `session.status_idle`을 발생시키며, 클라이언트는 이를 무시해도 됩니다. 동일한 호출에 대한 두 번째 결과는 수락되지만 아무런 효과가 없습니다.

일시 중지된 세션에 [재연결](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#reconnect-without-missing-events)하면, 최신 `session.status_idle` 이벤트의 `stop_reason.event_ids`에 응답해야 할 호출이 나열됩니다.

다음 예제는 각 호출이 도착할 때마다 응답합니다.

<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
  with client.beta.sessions.events.stream(session.id) as stream:
      for event in stream:
          match event.type:
              case "agent.custom_tool_use":
                  # 도구를 실행합니다
                  result = call_tool(event.name, event.input)

                  # 결과를 다시 보냅니다
                  client.beta.sessions.events.send(
                      session.id,
                      events=[
                          {
                              "type": "user.custom_tool_result",
                              "custom_tool_use_id": event.id,
                              "content": [{"type": "text", "text": result}],
                          },
                      ],
                  )
              case "session.status_idle":
                  if event.stop_reason and event.stop_reason.type == "end_turn":
                      break
  ```

  ```typescript TypeScript
  const stream = await client.beta.sessions.events.stream(session.id);

  loop: for await (const event of stream) {
    switch (event.type) {
      case "agent.custom_tool_use": {
        // 도구를 실행합니다
        const result = await callTool(event.name, event.input);

        // 결과를 다시 보냅니다
        await client.beta.sessions.events.send(session.id, {
          events: [
            {
              type: "user.custom_tool_result",
              custom_tool_use_id: event.id,
              content: [{ type: "text", text: result }]
            }
          ]
        });
        break;
      }
      case "session.status_idle":
        if (event.stop_reason.type === "end_turn") {
          break loop;
        }
        break;
    }
  }
  ```

  ```csharp C#
  await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
  {
      if (streamEvent.Value is BetaManagedAgentsAgentCustomToolUseEvent toolUse)
      {
          // 도구를 실행합니다
          var result = await CallTool(toolUse.Name, toolUse.Input);

          // 결과를 다시 보냅니다
          await client.Beta.Sessions.Events.Send(session.ID, new()
          {
              Events =
              [
                  new BetaManagedAgentsUserCustomToolResultEventParams
                  {
                      Type = BetaManagedAgentsUserCustomToolResultEventParamsType.UserCustomToolResult,
                      CustomToolUseID = toolUse.ID,
                      Content =
                      [
                          new BetaManagedAgentsTextBlock
                          {
                              Type = BetaManagedAgentsTextBlockType.Text,
                              Text = result,
                          },
                      ],
                  },
              ],
          });
      }
      else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent idle
          && idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
      {
          break;
      }
  }
  ```

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

  loop:
  	for stream.Next() {
  		switch event := stream.Current().AsAny().(type) {
  		case anthropic.BetaManagedAgentsAgentCustomToolUseEvent:
  			// 도구를 실행합니다
  			result := callTool(event.Name, event.Input)
  			// 결과를 다시 보냅니다
  			if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, 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 {
  				panic(err)
  			}
  		case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
  			if _, ok := event.StopReason.AsAny().(anthropic.BetaManagedAgentsSessionEndTurn); ok {
  				break loop
  			}
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  ```

  ```java Java
  try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
      loop:
      for (var event : (Iterable<BetaManagedAgentsStreamSessionEvents>) stream.stream()::iterator) {
          switch (event.type().value()) {
              case AGENT_CUSTOM_TOOL_USE -> {
                  // 도구를 실행합니다
                  var toolUse = event.asAgentCustomToolUse();
                  var result = callTool(toolUse.name(), toolUse.input());

                  // 결과를 다시 보냅니다
                  client.beta().sessions().events().send(
                      session.id(),
                      EventSendParams.builder()
                          .addEvent(BetaManagedAgentsUserCustomToolResultEventParams.builder()
                              .type(BetaManagedAgentsUserCustomToolResultEventParams.Type.USER_CUSTOM_TOOL_RESULT)
                              .customToolUseId(toolUse.id())
                              .addTextContent(result)
                              .build())
                          .build());
              }
              case SESSION_STATUS_IDLE -> {
                  if (event.asSessionStatusIdle().stopReason().isEndTurn()) {
                      break loop;
                  }
              }
          }
      }
  }
  ```

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

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsAgentCustomToolUseEvent:
              // 도구를 실행합니다
              $result = callTool($event->name, $event->input);

              // 결과를 다시 보냅니다
              $client->beta->sessions->events->send(
                  $session->id,
                  events: [
                      [
                          'type' => 'user.custom_tool_result',
                          'custom_tool_use_id' => $event->id,
                          'content' => [['type' => 'text', 'text' => $result]],
                      ],
                  ],
              );
              break;
          case $event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent:
              if ($event->stopReason instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionEndTurn) {
                  break 2;
              }
              break;
      }
  }
  ```

  ```ruby Ruby
  client.beta.sessions.events.stream_events(session.id).each do |event|
    case event
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentCustomToolUseEvent
      # 도구를 실행합니다
      result = call_tool.call(event.name, event.input)
      # 결과를 다시 보냅니다
      client.beta.sessions.events.send_(
        session.id,
        events: [
          {
            type: "user.custom_tool_result",
            custom_tool_use_id: event.id,
            content: [{type: "text", text: result}]
          }
        ]
      )
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      break if event.stop_reason.is_a?(Anthropic::Beta::Sessions::BetaManagedAgentsSessionEndTurn)
    end
  end
  ```
</CodeGroup>

에이전트에 열려 있는 [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)이 있으면, 세션이 `running` 상태를 유지하는 동안 커스텀 도구 호출이 도착할 수 있습니다. `requires_action` 유휴 이벤트는 [세션의 스레드](https://platform.claude.com/docs/ko/managed-agents/session-threads) 중 어느 것도 작업 중이지 않을 때만 도착합니다. 이를 기다리지 말고, 각 호출의 `agent.custom_tool_use` 이벤트가 도착하면 응답하세요. [실행 추적하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#follow-a-run)의 샘플에서 방법을 확인할 수 있습니다.

### 도구 호출 확인하기

`tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`) 매개변수에 이벤트 ID를 전달하여 `user.tool_confirmation` 이벤트를 전송하세요. `result` (csharp, go: `Result`)를 `"allow"` 또는 `"deny"`로 설정하세요. 어떤 호출이 확인을 기다리는지, 그리고 거부 사유를 설명하는 방법은 [확인 요청에 응답하기](https://platform.claude.com/docs/ko/managed-agents/permission-policies#respond-to-confirmation-requests)를 참조하세요.

다음 예제는 대기 중인 모든 호출을 승인합니다.

<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
  with client.beta.sessions.events.stream(session.id) as stream:
      for event in stream:
          if event.type == "session.status_idle" and (stop_reason := event.stop_reason):
              match stop_reason.type:
                  case "requires_action":
                      for event_id in stop_reason.event_ids:
                          # 대기 중인 도구 호출을 승인합니다
                          client.beta.sessions.events.send(
                              session.id,
                              events=[
                                  {
                                      "type": "user.tool_confirmation",
                                      "tool_use_id": event_id,
                                      "result": "allow",
                                  },
                              ],
                          )
                  case "end_turn":
                      break
  ```

  ```typescript TypeScript
  const stream = await client.beta.sessions.events.stream(session.id);

  for await (const event of stream) {
    if (event.type !== "session.status_idle") {
      continue;
    }
    if (event.stop_reason.type === "end_turn") {
      break;
    }
    if (event.stop_reason.type !== "requires_action") {
      continue;
    }

    for (const eventId of event.stop_reason.event_ids) {
      // 대기 중인 도구 호출을 승인합니다
      await client.beta.sessions.events.send(session.id, {
        events: [
          {
            type: "user.tool_confirmation",
            tool_use_id: eventId,
            result: "allow"
          }
        ]
      });
    }
  }
  ```

  ```csharp C#
  await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
  {
      if (streamEvent.Value is not BetaManagedAgentsSessionStatusIdleEvent idle) continue;

      if (idle.StopReason?.Value is BetaManagedAgentsSessionRequiresAction requiresAction)
      {
          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,
                      },
                  ],
              });
          }
      }
      else if (idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
      {
          break;
      }
  }
  ```

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

  loop:
  	for stream.Next() {
  		event, ok := stream.Current().AsAny().(anthropic.BetaManagedAgentsSessionStatusIdleEvent)
  		if !ok {
  			continue
  		}
  		switch stopReason := event.StopReason.AsAny().(type) {
  		case anthropic.BetaManagedAgentsSessionRequiresAction:
  			for _, eventID := range stopReason.EventIDs {
  				// 대기 중인 도구 호출을 승인합니다
  				if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  					Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  						OfUserToolConfirmation: &anthropic.BetaManagedAgentsUserToolConfirmationEventParams{
  							Type:      anthropic.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
  							ToolUseID: eventID,
  							Result:    anthropic.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
  						},
  					}},
  				}); err != nil {
  					panic(err)
  				}
  			}
  		case anthropic.BetaManagedAgentsSessionEndTurn:
  			break loop
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  ```

  ```java Java
  try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
      stream.stream()
          .filter(BetaManagedAgentsStreamSessionEvents::isSessionStatusIdle)
          .map(idleEvent -> idleEvent.asSessionStatusIdle().stopReason())
          .takeWhile(stopReason -> !stopReason.isEndTurn())
          .filter(stopReason -> stopReason.isRequiresAction())
          .flatMap(stopReason -> stopReason.asRequiresAction().eventIds().stream())
          // 대기 중인 각 도구 호출을 승인합니다
          .forEach(toolUseId -> client.beta().sessions().events().send(
              session.id(),
              EventSendParams.builder()
                  .addEvent(BetaManagedAgentsUserToolConfirmationEventParams.builder()
                      .type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
                      .toolUseId(toolUseId)
                      .result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
                      .build())
                  .build()));
  }
  ```

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

  foreach ($stream as $event) {
      if ($event instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent && $event->stopReason) {
          switch (true) {
              case $event->stopReason instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionRequiresAction:
                  foreach ($event->stopReason->eventIDs as $eventId) {
                      // 대기 중인 도구 호출을 승인합니다
                      $client->beta->sessions->events->send(
                          $session->id,
                          events: [
                              [
                                  'type' => 'user.tool_confirmation',
                                  'tool_use_id' => $eventId,
                                  'result' => 'allow',
                              ],
                          ],
                      );
                  }
                  break;
              case $event->stopReason instanceof \Anthropic\Beta\Sessions\Events\ManagedAgentsSessionEndTurn:
                  break 2;
          }
      }
  }
  ```

  ```ruby Ruby
  client.beta.sessions.events.stream_events(session.id).each do |event|
    case event
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      stop_reason = event.stop_reason
      case stop_reason
      when Anthropic::Beta::Sessions::BetaManagedAgentsSessionRequiresAction
        stop_reason.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
      when Anthropic::Beta::Sessions::BetaManagedAgentsSessionEndTurn
        break
      end
    end
  end
  ```
</CodeGroup>

앞의 예제는 세션이 유휴 상태가 된 후 각 호출을 승인합니다. 에이전트에 열려 있는 [워크플로 실행](https://platform.claude.com/docs/ko/managed-agents/workflow-runs)이 있으면, 세션이 `running` 상태를 유지하는 동안 도구 호출이 사용자의 확인을 기다릴 수 있습니다. 유휴 이벤트를 기다리지 마세요. [`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`인 `agent.tool_use` 또는 `agent.mcp_tool_use` 이벤트가 도착하면 응답하세요. [실행 추적하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#follow-a-run)의 샘플은 이 방식으로 커스텀 도구 호출에 응답하며, 해당 소개 부분에서 확인을 추가하는 방법을 설명합니다.

## 유휴 세션 재개하기

세션은 상호작용 사이에도 유지됩니다. 세션을 재개하려면 평소처럼 `user.message` 이벤트를 전송하세요.

<CodeGroup>
  ```bash cURL
  # 프로덕션에서는 재개하려는 세션의 저장된 ID를 전달하세요.
  curl --fail-with-body -sS "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.message",
          "content": [
            {"type": "text", "text": "Now run the tests against the changes you made earlier."}
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  # 프로덕션에서는 재개하려는 세션의 저장된 ID를 전달하세요.
  ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
  events:
    - type: user.message
      content:
        - type: text
          text: Now run the tests against the changes you made earlier.
  YAML
  ```

  ```python Python
  # 새 user.message 이벤트를 전송하여 이전에 생성된 세션을 재개합니다.
  # 프로덕션에서는 재개하려는 세션의 저장된 ID를 전달하세요.
  client.beta.sessions.events.send(
      session.id,
      events=[
          {
              "type": "user.message",
              "content": [
                  {
                      "type": "text",
                      "text": "Now run the tests against the changes you made earlier.",
                  },
              ],
          },
      ],
  )
  ```

  ```typescript TypeScript
  // 이전에 생성된 세션에 새 사용자 이벤트를 보내 세션을 재개합니다.
  // 프로덕션에서는 재개하려는 세션의 저장된 ID를 전달하세요.
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Now run the tests against the changes you made earlier."
          }
        ]
      }
    ]
  });
  ```

  ```csharp C#
  // ID로 이전에 생성된 세션을 재개합니다. 프로덕션에서는 세션이
  // 생성될 때 저장해 둔 세션 ID를 전달하세요.
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Now run the tests against the changes you made earlier.",
                  },
              ],
          },
      ],
  });
  ```

  ```go Go
  // 새 user.message 이벤트를 전송하여 이전에 생성된 세션을 재개합니다.
  // 프로덕션에서는 재개할 세션의 저장된 ID를 전달하세요.
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  		OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
  			Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  			Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  				OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  					Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  					Text: "Now run the tests against the changes you made earlier.",
  				},
  			}},
  		},
  	}},
  }); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  // 이전에 생성된 세션을 ID로 재개합니다. 프로덕션에서는 세션이
  // 생성될 때 저장해 둔 세션 ID를 전달하세요.
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
              .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
              .addTextContent("Now run the tests against the changes you made earlier.")
              .build())
          .build());
  ```

  ```php PHP
  // 새 user.message 이벤트를 보내 이전에 생성한 세션을 재개합니다.
  // 프로덕션에서는 세션 생성 시 저장한 세션 ID를 전달하세요.
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'user.message',
              'content' => [
                  [
                      'type' => 'text',
                      'text' => 'Now run the tests against the changes you made earlier.',
                  ],
              ],
          ],
      ],
  );
  ```

  ```ruby Ruby
  # 세션 재개는 단순히 다음 이벤트를 세션에 전송하는 것입니다. 프로덕션에서는
  # 세션 생성 시 저장해 둔 세션 ID를 전달하세요.
  client.beta.sessions.events.send_(
    session.id,
    events: [
      {
        type: "user.message",
        content: [
          {type: "text", text: "Now run the tests against the changes you made earlier."}
        ]
      }
    ]
  )
  ```
</CodeGroup>

대화 기록은 세션이 명시적으로 삭제되지 않는 한 보존됩니다. 세션이 유휴 상태가 되면 해당 샌드박스의 체크포인트가 생성됩니다. 체크포인트는 파일 시스템, 설치된 패키지, 에이전트가 생성한 모든 파일을 포함한 전체 샌드박스 상태를 보존합니다.

<Note>
  샌드박스 상태는 샌드박스가 생성된 후 30일 동안만 보존되며, 활동이 있어도 이 기간은 연장되지 않습니다. 30일이 지나면 샌드박스 상태(파일, 설치된 도구 등)는 복구할 수 없으며, 재개된 세션은 새 샌드박스에서 시작됩니다. 워크플로가 샌드박스 내용에 의존한다면, 기간이 끝나기 전에 에이전트가 중요한 결과물을 [outputs](https://platform.claude.com/docs/ko/managed-agents/define-outcomes#retrieving-deliverables)에 기록하도록 하세요.
</Note>

## 에이전트 중단하기

실행 도중 에이전트를 중지하려면 `user.interrupt` 이벤트를 전송한 다음, 방향을 전환하기 위해 `user.message` 이벤트를 이어서 전송하세요.

<CodeGroup>
  ```bash cURL
  # 에이전트가 현재 파일을 분석 중입니다...
  # 새로운 지시로 중단합니다:
  curl --fail-with-body -sS "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"},
        {
          "type": "user.message",
          "content": [
            {"type": "text", "text": "Instead, focus on fixing the bug in line 42."}
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  # 에이전트가 현재 파일을 분석 중입니다...
  # 새로운 지시로 중단합니다:
  ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
  events:
    - type: user.interrupt
    - type: user.message
      content:
        - type: text
          text: Instead, focus on fixing the bug in line 42.
  YAML
  ```

  ```python Python
  # 에이전트가 현재 파일을 분석 중입니다...
  # 새로운 지시로 중단합니다:
  client.beta.sessions.events.send(
      session.id,
      events=[
          {"type": "user.interrupt"},
          {
              "type": "user.message",
              "content": [
                  {
                      "type": "text",
                      "text": "Instead, focus on fixing the bug in line 42.",
                  },
              ],
          },
      ],
  )
  ```

  ```typescript TypeScript
  // 에이전트가 현재 파일을 분석 중입니다...
  // 새로운 방향으로 중단(interrupt)합니다:
  await client.beta.sessions.events.send(session.id, {
    events: [
      { type: "user.interrupt" },
      {
        type: "user.message",
        content: [
          {
            type: "text",
            text: "Instead, focus on fixing the bug in line 42."
          }
        ]
      }
    ]
  });
  ```

  ```csharp C#
  // 에이전트가 현재 파일을 분석 중입니다...
  // 새로운 방향으로 인터럽트:
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserInterruptEventParams
          {
              Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
          },
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Instead, focus on fixing the bug in line 42.",
                  },
              ],
          },
      ],
  });
  ```

  ```go Go
  // 에이전트가 현재 파일을 분석 중입니다...
  // 새로운 지시로 중단합니다:
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{
  		{
  			OfUserInterrupt: &anthropic.BetaManagedAgentsUserInterruptEventParams{
  				Type: anthropic.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
  			},
  		},
  		{
  			OfUserMessage: &anthropic.BetaManagedAgentsUserMessageEventParams{
  				Type: anthropic.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
  				Content: []anthropic.BetaManagedAgentsUserMessageEventParamsContentUnion{{
  					OfText: &anthropic.BetaManagedAgentsTextBlockParam{
  						Type: anthropic.BetaManagedAgentsTextBlockTypeText,
  						Text: "Instead, focus on fixing the bug in line 42.",
  					},
  				}},
  			},
  		},
  	},
  }); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  // 에이전트가 현재 파일을 분석 중입니다...
  // 새로운 방향으로 인터럽트합니다:
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
              .type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
              .build())
          .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
              .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
              .addTextContent("Instead, focus on fixing the bug in line 42.")
              .build())
          .build());
  ```

  ```php PHP
  // 에이전트가 현재 파일을 분석하는 중입니다...
  // 새로운 지시로 중단합니다:
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          ['type' => 'user.interrupt'],
          [
              'type' => 'user.message',
              'content' => [
                  [
                      'type' => 'text',
                      'text' => 'Instead, focus on fixing the bug in line 42.',
                  ],
              ],
          ],
      ],
  );
  ```

  ```ruby Ruby
  # 에이전트가 현재 파일을 분석 중입니다...
  # 새로운 지시로 중단합니다:
  client.beta.sessions.events.send_(
    session.id,
    events: [
      {type: "user.interrupt"},
      {
        type: "user.message",
        content: [
          {type: "text", text: "Instead, focus on fixing the bug in line 42."}
        ]
      }
    ]
  )
  ```
</CodeGroup>

호출은 이벤트가 대기열에 추가되는 즉시 반환됩니다. 그런 다음 중단은 다음 순서로 적용됩니다.

1. 에이전트가 중단을 적용합니다. 그때까지 중단 이벤트의 `processed_at` (java, php: `processedAt`; csharp, go: `ProcessedAt`)는 `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `time.Time{}`)로 유지되며 세션은 `running` 상태를 유지합니다. 진행 중인 모델 응답은 즉시 중지되지만, 도구 호출이 실행 중인 동안에는 중단이 적용되는 데 더 오래 걸릴 수 있습니다.
2. `user.interrupt` 이벤트가 스트림에 나타나고, 중단된 턴은 `session.status_idle` 이벤트와 함께 종료됩니다.
3. 에이전트는 중단 후 전송한 `user.message`로 다음 턴을 시작합니다.

유휴 이벤트의 `stop_reason.type`은 `end_turn`으로, 스스로 완료된 턴과 동일한 값입니다. 열려 있는 워크플로 실행이 있는 경우, 중단은 그중 어느 것도 종료하지 않습니다. 실행 중인 실행은 세션을 `running` 상태로 유지할 수 있으므로, 2단계의 `session.status_idle` 이벤트가 도착하지 않을 수 있습니다. [실행이 열려 있는 세션 중단하기](https://platform.claude.com/docs/ko/managed-agents/workflow-runs#interrupt-a-session-with-runs-open)를 참조하세요.

## 과거 이벤트 조회하기

세션의 전체 이벤트 기록을 가져옵니다.

<CodeGroup>
  ```bash cURL
  curl --fail-with-body -sS "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"
  ```

  ```bash CLI
  ant beta:sessions:events list --session-id "$SESSION_ID" --format jsonl
  ```

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

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

  ```csharp C#
  var events = await client.Beta.Sessions.Events.List(session.ID);
  foreach (var sessionEvent in events.Items)
  {
      Console.WriteLine($"[{sessionEvent.Json.GetProperty("type").GetString()}] {sessionEvent.ProcessedAt}");
  }
  ```

  ```go Go
  events, err := client.Beta.Sessions.Events.List(ctx, session.ID, anthropic.BetaSessionEventListParams{})
  if err != nil {
  	panic(err)
  }
  for _, event := range events.Data {
  	fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
  }
  ```

  ```java Java
  var events = client.beta().sessions().events().list(session.id());
  for (var event : events.data()) {
      var eventJson = event._json().orElseThrow().convert(JsonNode.class);
      var processedAt = eventJson.path("processed_at");
      IO.println("[" + eventJson.get("type").asText() + "] "
          + (processedAt.isTextual() ? processedAt.asText() : "null"));
  }
  ```

  ```php PHP
  $events = $client->beta->sessions->events->list($session->id);
  foreach ($events->data as $event) {
      $processedAt = ($event->processedAt ?? null)?->format(DATE_RFC3339) ?? 'null';
      echo "[{$event->type}] {$processedAt}\n";
  }
  ```

  ```ruby Ruby
  events = client.beta.sessions.events.list(session.id)
  events.data.each { puts "[#{it.type}] #{it.processed_at}" }
  ```
</CodeGroup>

특정 이벤트 유형만 반환하려면 `types` (curl: `types[]`; cli: `--type`; csharp, go: `Types`) 필터를 전달하세요.

<CodeGroup>
  ```bash cURL
  curl --fail-with-body -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID/events?beta=true&types[]=agent.tool_use&types[]=agent.tool_result" \
    -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 list --session-id "$SESSION_ID" \
    --type agent.tool_use --type agent.tool_result \
    --format jsonl
  ```

  ```python Python
  events = client.beta.sessions.events.list(
      session.id,
      types=["agent.tool_use", "agent.tool_result"],
  )
  for event in events.data:
      print(f"[{event.type}] {event.processed_at}")
  ```

  ```typescript TypeScript
  const events = await client.beta.sessions.events.list(session.id, {
    types: ["agent.tool_use", "agent.tool_result"]
  });
  for (const event of events.data) {
    console.log(`[${event.type}] ${event.processed_at}`);
  }
  ```

  ```csharp C#
  var events = await client.Beta.Sessions.Events.List(session.ID, new()
  {
      Types = ["agent.tool_use", "agent.tool_result"],
  });
  foreach (var sessionEvent in events.Items)
  {
      Console.WriteLine($"[{sessionEvent.Json.GetProperty("type").GetString()}] {sessionEvent.ProcessedAt}");
  }
  ```

  ```go Go
  events, err := client.Beta.Sessions.Events.List(ctx, session.ID, anthropic.BetaSessionEventListParams{
  	Types: []anthropic.BetaManagedAgentsSessionEventType{
  		anthropic.BetaManagedAgentsSessionEventTypeAgentToolUse,
  		anthropic.BetaManagedAgentsSessionEventTypeAgentToolResult,
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, event := range events.Data {
  	fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
  }
  ```

  ```java Java
  var events = client.beta().sessions().events().list(
      session.id(),
      EventListParams.builder()
          .addType(BetaManagedAgentsSessionEventType.AGENT_TOOL_USE)
          .addType(BetaManagedAgentsSessionEventType.AGENT_TOOL_RESULT)
          .build());
  for (var event : events.data()) {
      event.agentToolUse().ifPresent(toolUse ->
          IO.println("[" + toolUse.type() + "] " + toolUse.processedAt()));
      event.agentToolResult().ifPresent(toolResult ->
          IO.println("[" + toolResult.type() + "] " + toolResult.processedAt()));
  }
  ```

  ```php PHP
  $events = $client->beta->sessions->events->list(
      $session->id,
      types: ['agent.tool_use', 'agent.tool_result'],
  );
  foreach ($events->data as $event) {
      $processedAt = ($event->processedAt ?? null)?->format(DATE_RFC3339) ?? 'null';
      echo "[{$event->type}] {$processedAt}\n";
  }
  ```

  ```ruby Ruby
  events = client.beta.sessions.events.list(
    session.id,
    types: ["agent.tool_use", "agent.tool_result"]
  )
  events.data.each { puts "[#{it.type}] #{it.processed_at}" }
  ```
</CodeGroup>

## 시스템 메시지 전송하기

[지원되는 모델](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#supported-models)에서는 `system.message` 이벤트를 전송하여 에이전트에 권한 있는 시스템 수준 컨텍스트를 제공할 수 있습니다. 이 컨텍스트는 함께 전송된 턴과 이후의 모든 턴에 적용됩니다. 세션 도중 에이전트에 업데이트된 시스템 수준 지침이 필요할 때 사용하세요. 예를 들어 다른 페르소나, 수정된 제약 조건, 런타임에 가져온 컨텍스트 등이 있습니다.

이벤트의 `content` (csharp, go: `Content`)는 1–1000개의 텍스트 항목을 허용합니다. 콘텐츠는 `role: "system"` 턴으로 세션의 시스템 컨텍스트에 추가됩니다. 이는 에이전트 정의의 `system` (csharp, go: `System`; ruby: `system_`) 필드로 설정되는 최상위 시스템 프롬프트를 대체하지 않습니다.

<CodeGroup>
  ```bash cURL
  curl --fail-with-body -sS "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": "system.message",
          "content": [
            {"type": "text", "text": "The current timezone of the user is America/New_York."}
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
  events:
    - type: system.message
      content:
        - type: text
          text: "The user's current timezone is America/New_York."
  YAML
  ```

  ```python Python
  client.beta.sessions.events.send(
      session.id,
      events=[
          {
              "type": "system.message",
              "content": [
                  {
                      "type": "text",
                      "text": "The user's current timezone is America/New_York.",
                  },
              ],
          },
      ],
  )
  ```

  ```typescript TypeScript
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "system.message",
        content: [
          {
            type: "text",
            text: "The user's current timezone is America/New_York."
          }
        ]
      }
    ]
  });
  ```

  ```csharp C#
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsSystemMessageEventParams
          {
              Type = BetaManagedAgentsSystemMessageEventParamsType.SystemMessage,
              Content =
              [
                  new BetaManagedAgentsSystemContentBlock
                  {
                      Type = BetaManagedAgentsSystemContentBlockType.Text,
                      Text = "The user's current timezone is America/New_York.",
                  },
              ],
          },
      ],
  });
  ```

  ```go Go
  if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
  	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
  		OfSystemMessage: &anthropic.BetaManagedAgentsSystemMessageEventParams{
  			Type: anthropic.BetaManagedAgentsSystemMessageEventParamsTypeSystemMessage,
  			Content: []anthropic.BetaManagedAgentsSystemContentBlockParam{{
  				Type: anthropic.BetaManagedAgentsSystemContentBlockTypeText,
  				Text: "The user's current timezone is America/New_York.",
  			}},
  		},
  	}},
  }); err != nil {
  	panic(err)
  }
  ```

  ```java Java
  client.beta().sessions().events().send(
      session.id(),
      EventSendParams.builder()
          .addEvent(BetaManagedAgentsSystemMessageEventParams.builder()
              .type(BetaManagedAgentsSystemMessageEventParams.Type.SYSTEM_MESSAGE)
              .addTextContent("The user's current timezone is America/New_York.")
              .build())
          .build());
  ```

  ```php PHP
  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'system.message',
              'content' => [
                  [
                      'type' => 'text',
                      'text' => "The user's current timezone is America/New_York.",
                  ],
              ],
          ],
      ],
  );
  ```

  ```ruby Ruby
  client.beta.sessions.events.send_(
    session.id,
    events: [
      {
        type: "system.message",
        content: [
          {type: "text", text: "The user's current timezone is America/New_York."}
        ]
      }
    ]
  )
  ```
</CodeGroup>

### 지원되는 모델

`system.message`는 다음 모델에서 지원됩니다.

* Claude Fable 5.1
* Claude Mythos 5.1
* Claude Fable 5
* Claude Mythos 5
* Claude Opus 5.5
* Claude Opus 5
* Claude Opus 4.8
* Claude Sonnet 5.5
* Claude Haiku 5.5

에이전트의 기본 모델이 대화 도중 시스템 주입을 지원하지 않으면, 이벤트는 `model_does_not_support_mid_conversation_system` 유효성 검사 오류와 함께 거부됩니다. `system.message`는 기본 스레드에만 전달되므로 서브에이전트 모델은 검사되지 않습니다.

### 도구 호출이 대기 중일 때의 시스템 메시지

세션이 `requires_action` 중지 이유로 유휴 상태인 동안에는 `system.message`가 동일한 요청에서 도구 결과 이벤트 뒤에 와야 합니다. 단독으로 전송하거나 `user.message`와 함께 전송하면, 대기 중인 도구 이벤트가 해결될 때까지 거부됩니다.

## 다음 단계

<CardGroup cols={2}>
  <Card title="이벤트 델타로 응답 미리 보기" icon="text" href="https://platform.claude.com/docs/ko/managed-agents/event-deltas">
    모델이 아직 생성 중인 에이전트의 응답 텍스트를 렌더링합니다.
  </Card>

  <Card title="세션 검사 및 사용량 추적" icon="magnifying-glass" href="https://platform.claude.com/docs/ko/managed-agents/session-observability">
    Claude Console에서 세션을 검사하고 토큰 사용량과 정가 비용을 확인합니다.
  </Card>

  <Card title="Webhook 구독하기" icon="link" href="https://platform.claude.com/docs/ko/managed-agents/webhooks">
    폴링 없이 주요 이벤트가 발생할 때 알림을 받습니다.
  </Card>

  <Card title="이벤트 유형" icon="book" href="https://platform.claude.com/docs/ko/managed-agents/reference#event-types">
    세션이 전송하거나 수신할 수 있는 모든 이벤트 유형을 찾아봅니다.
  </Card>
</CardGroup>
