---
title: 이벤트 델타로 응답 미리 보기
url: https://platform.claude.com/docs/ko/managed-agents/event-deltas
description: 모델이 아직 생성하는 동안 에이전트의 응답 텍스트를 실시간 미리 보기로 렌더링합니다.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

기본적으로 에이전트의 응답 텍스트는 버퍼링된 `agent.message` 이벤트로 [세션 이벤트 스트림](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming)에 도달합니다. 각 이벤트는 해당 이벤트를 생성한 모델 요청이 완료된 후에만 발행됩니다. "Event delta"(이벤트 델타)를 사용하면 모델이 아직 텍스트를 생성하는 동안 해당 텍스트를 실시간 미리 보기로 점진적으로 렌더링할 수 있습니다.

미리 보기는 최선의 노력(best-effort) 방식의 표시 보조 수단이며, 버퍼링된 `agent.message`가 항상 권위 있는 기록입니다. 미리 보기를 무시하는 클라이언트도 완전하고 올바른 스트림을 받습니다.

## 미리 보기 옵트인

미리 보기는 스트림 연결별로 옵트인합니다. 읽고 있는 스트림에 `event_deltas[]` 쿼리 매개변수를 추가하고, 미리 보려는 이벤트 유형마다 한 번씩 반복하세요. 허용되는 값은 `agent.message`와 `agent.thinking`입니다. 그 외의 값은 400 오류를 반환하며, 100개를 초과하는 값을 포함한 요청도 마찬가지입니다.

두 스트림 엔드포인트 모두 이 매개변수를 허용합니다:

* **세션 수준 스트림:** `GET /v1/sessions/{session_id}/events/stream`
* **세션 스레드 스트림:** `GET /v1/sessions/{session_id}/threads/{thread_id}/stream`

서브에이전트의 미리 보기는 [해당 서브에이전트 자체의 스레드 스트림](https://platform.claude.com/docs/ko/managed-agents/event-deltas#preview-session-thread-events)에 나타납니다.

`[]`는 셸 glob 패턴이므로 셸에서 요청을 구성할 때는 항상 URL을 따옴표로 묶으세요. 예제에서는 대괄호를 `%5B%5D`로 퍼센트 인코딩하며, 이 방법도 작동합니다.

### 미리 보기 이벤트

미리 보기 대상 이벤트가 시작되면 스트림은 다가올 이벤트의 유형과 `id`를 담은 `event_start`를 발행합니다:

```json
{
  "type": "event_start",
  "event": {
    "type": "agent.message",
    "id": "sevt_01abc..."
  }
}
```

`agent.message`의 경우 시작 이벤트 다음에 증분 텍스트를 담은 `event_delta` 이벤트가 이어집니다. 각 델타는 확장하는 이벤트를 `event_id`에, 확장하는 콘텐츠 블록을 `delta.index`에 지정합니다:

```json
{
  "type": "event_delta",
  "event_id": "sevt_01abc...",
  "delta": {
    "type": "content_delta",
    "index": 0,
    "content": {
      "type": "text",
      "text": "Here is the summary"
    }
  }
}
```

`agent.thinking`의 경우 사고 블록이 시작되었다는 신호로 `event_start`만 발행됩니다. 이후 `event_delta` 이벤트는 이어지지 않습니다. 미리 보기를 마무리하는 버퍼링된 `agent.thinking` 이벤트는 진행 신호이며 사고 내용을 담고 있지 않습니다.

영구 저장되는 이벤트와 달리 `event_start`와 `event_delta`에는 자체 `id`나 `processed_at`이 없습니다. 이들이 담고 있는 유일한 식별자는 미리 보기 대상 이벤트의 `id`입니다. 또한 이들의 유형 문자열은 영구 저장되는 이벤트의 `{domain}.{action}` 명명 규칙에 대한 예외입니다.

<Note>
  이벤트 델타는 [메시지 스트리밍](https://platform.claude.com/docs/ko/build-with-claude/streaming)과 다른 와이어 형식을 사용하며, 이 차이는 의도된 것입니다. 미리 보기 대상 `agent.message`는 단일 `event_start`를 받고 그 뒤에는 `event_delta` 이벤트만 이어집니다. 콘텐츠 블록별 시작 또는 중지 이벤트가 없으며, 미리 보기 대상 이벤트 자체에 대한 중지 이벤트도 없습니다. 델타 유형은 `content_block_delta`가 아니라 `content_delta`입니다. Messages API용으로 작성된 누적기 코드는 그대로 사용할 수 없습니다.
</Note>

## 누적 및 조정

이벤트 델타를 지원하는 모든 SDK에는 `index` 관리를 대신 처리하는 [누적기 헬퍼](https://platform.claude.com/docs/ko/managed-agents/event-deltas#sdk-accumulator-helpers)가 포함되어 있습니다. 사용자 지정 관리가 필요한 경우 이 섹션의 수동 패턴은 모든 언어에서 작동합니다. 생성된 이벤트 유형에 적용하세요.

수동 패턴에서는 미리 보기 텍스트를 `(event_id, index)`를 키로 하는 임시 맵에 보관하고, 버퍼링된 이벤트를 기록으로 취급합니다. 두 가지를 모델 요청별로 조정하세요.

턴은 단일 `session.status_running` 이벤트로 시작됩니다. 정상적으로 완료되는 턴에서 각 모델 요청은 다음 이벤트를 순서대로 생성합니다:

1. `span.model_request_start`
2. `event_start`
3. `event_delta` 이벤트
4. 버퍼링된 `agent.message`
5. [`span.model_request_end`](https://platform.claude.com/docs/ko/managed-agents/reference#event-types) (Span events 탭에 있음)

와이어상에서는 이 시퀀스 중 미리 보기 부분이 연결의 다른 버퍼링된 이벤트와 섞여 다음과 같이 나타납니다:

```text wrap
event_start     {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
event_delta     {"event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "..."}}}
...
agent.message   {"id": "sevt_01abc...", "content": [...]}
```

`event_delta` 줄은 텍스트 조각마다 한 번씩 반복됩니다. 각 이벤트가 도착하는 대로 처리하세요:

1. `event_start`에서 알려진 `id`를 기록합니다. 식별자는 항상 일치합니다: `event_start.event.id`, 모든 `event_delta.event_id`, 그리고 버퍼링된 `agent.message`의 `id`는 같은 값입니다.
2. 각 `event_delta`에서 `delta.content.text`를 `(event_id, delta.index)` 항목에 추가하고 누적 중인 텍스트를 렌더링합니다. 특정 `index`에 대한 첫 번째 델타가 해당 항목을 생성합니다.
3. 버퍼링된 `agent.message`가 도착하면 `id`로 매칭하고, 누적된 미리 보기를 폐기한 다음 대신 메시지의 콘텐츠를 렌더링합니다.
4. `span.model_request_end`에서 버퍼링된 이벤트로 조정되지 않은 미리 보기를 모두 닫습니다. 해당 미리 보기에 대한 델타는 더 이상 오지 않습니다. 턴에서 오류가 발생하거나 중단되면 버퍼링된 이벤트가 도착하지 않을 수 있지만, `span.model_request_end`는 여전히 도착합니다.

이 패턴은 두 가지 보장에 의존합니다:

* `(event_id, index)`를 키로 하여 미리 보기의 델타를 도착 순서대로 연결하면 버퍼링된 이벤트의 `content[index].text`의 접두사가 됩니다. 델타가 [부하 시 누락](https://platform.claude.com/docs/ko/managed-agents/event-deltas#limitations)될 수 있으므로 반드시 전체 텍스트인 것은 아닙니다.
* 하나의 연결은 `event_id`당 최대 하나의 `event_start`를 발행하며, 버퍼링된 이벤트는 해당 연결이 그 `id`에 대해 전달하는 마지막 항목입니다.

### SDK 누적기 헬퍼

각 SDK의 헬퍼는 `index` (csharp, go: `Index`) 관리를 처리합니다. Go, Java, Ruby, C# 헬퍼는 누적 중인 미리 보기를 이벤트의 `id` (csharp, go: `ID`)로 키 지정하는 작업도 처리합니다. Python, TypeScript, PHP 헬퍼를 사용하는 경우 해당 맵을 직접 유지하고 각 델타를 해당 `id` (csharp, go: `ID`)의 항목에 반영하세요.

다음 예제는 `agent.message` 미리 보기에 옵트인하고 이를 버퍼링된 이벤트와 조정합니다:

<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
  # 이벤트 id를 키로 하는 미리보기 스냅샷입니다. accumulate_managed_agents_event는 각
  # event_start / event_delta를 agent.message 스냅샷으로 합치며, 버퍼링된
  # agent.message가 이를 대체합니다.
  previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}

  # 이 연결에서 agent.message 미리보기를 사용하도록 설정합니다
  with client.beta.sessions.events.stream(
      session.id, event_deltas=["agent.message"]
  ) as stream:
      client.beta.sessions.events.send(
          session.id,
          events=[
              {
                  "type": "user.message",
                  "content": [
                      {"type": "text", "text": "Describe the repo in one sentence."}
                  ],
              },
          ],
      )

      for event in stream:
          match event.type:
              case "event_start":
                  snapshot = accumulate_managed_agents_event(None, event)
                  if snapshot is not None:
                      previews[event.event.id] = snapshot
                  print(f"event_start             {event.event.type} {event.event.id}")
              case "event_delta":
                  preview = accumulate_managed_agents_event(
                      previews.get(event.event_id), event
                  )
                  if preview is not None:
                      previews[event.event_id] = preview
                      text = "".join(block.text for block in preview.content)
                      print(f"event_delta             preview: {text!r}")
              case "agent.message":
                  # 버퍼링된 이벤트가 최종 기록입니다. 미리보기를 대체하고 닫습니다
                  preview = accumulate_managed_agents_event(
                      previews.pop(event.id, None), event
                  )
                  text = "".join(block.text for block in preview.content)
                  print(f"agent.message           {event.id} {text!r}")
              case "span.model_request_end":
                  # 더 이상 델타가 오지 않습니다. 버퍼링된 이벤트가
                  # 도착하지 않은 미리보기를 모두 닫습니다.
                  for event_id in previews:
                      print(f"span.model_request_end  closing preview for {event_id}")
                  previews.clear()
              case "session.status_idle":
                  break
  ```

  ```typescript TypeScript
  // 이벤트 id를 키로 하는 미리보기 스냅샷입니다. `accumulateManagedAgentsEvent`는
  // event_start / event_delta 미리보기를 agent.message 스냅샷으로 병합합니다.
  const previews = new Map<string, BetaManagedAgentsAgentMessageEvent>();

  // 이 연결에 대해서만 agent.message 미리보기를 활성화합니다
  const stream = await client.beta.sessions.events.stream(session.id, {
    event_deltas: ["agent.message"]
  });
  await client.beta.sessions.events.send(session.id, {
    events: [
      {
        type: "user.message",
        content: [{ type: "text", text: "Summarize the repo README" }]
      }
    ]
  });

  deltas: for await (const event of stream) {
    switch (event.type) {
      case "event_start": {
        // 1. 알려진 id를 기록하고 스냅샷을 엽니다. 델타와
        //    버퍼링된 이벤트는 동일한 id를 가집니다.
        const preview = accumulateManagedAgentsEvent(undefined, event);
        if (preview) {
          previews.set(event.event.id, preview);
        }
        console.log(`event_start             ${event.event.type} ${event.event.id}`);
        break;
      }
      case "event_delta": {
        // 2. 조각을 스냅샷에 병합하고 렌더링합니다
        const preview = accumulateManagedAgentsEvent(previews.get(event.event_id), event);
        if (preview) {
          previews.set(event.event_id, preview);
          const text = preview.content
            .map((block) => (block.type === "text" ? block.text : ""))
            .join("");
          console.log(`event_delta             preview: ${JSON.stringify(text)}`);
        }
        break;
      }
      case "agent.message": {
        // 3. 버퍼링된 이벤트가 기록입니다: 미리보기를 대체하고 닫습니다
        const message = accumulateManagedAgentsEvent(previews.get(event.id), event);
        previews.delete(event.id);
        const text = message.content
          .map((block) => (block.type === "text" ? block.text : ""))
          .join("");
        console.log(`agent.message           ${event.id} ${JSON.stringify(text)}`);
        break;
      }
      case "span.model_request_end":
        // 4. 더 이상 델타가 오지 않습니다. 조정되지 않은 미리보기를 모두 닫습니다.
        for (const eventId of previews.keys()) {
          console.log(`span.model_request_end  closing preview for ${eventId}`);
        }
        previews.clear();
        break;
      case "session.status_idle":
        break deltas;
    }
  }
  stream.controller.abort();
  ```

  ```csharp C#
  // 이벤트 델타를 활성화합니다: agent.message 이벤트가 생성되는 대로 미리 보기로 제공됩니다.
  using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(
      session.ID,
      new() { EventDeltas = [BetaManagedAgentsDeltaType.AgentMessage] }
  );
  await client.Beta.Sessions.Events.Send(session.ID, new()
  {
      Events =
      [
          new BetaManagedAgentsUserMessageEventParams
          {
              Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
              Content =
              [
                  new BetaManagedAgentsTextBlock
                  {
                      Type = BetaManagedAgentsTextBlockType.Text,
                      Text = "Write a haiku about event streams.",
                  },
              ],
          },
      ],
  });

  // (이벤트 id, 콘텐츠 인덱스)별로 미리 보기 조각을 누적합니다. 뒤이어 오는
  // 버퍼링된 agent.message에는 전체 콘텐츠가 담겨 있으므로, 누적된 미리 보기에
  // 덧붙이지 않고 이를 대체합니다.
  Dictionary<string, SortedDictionary<long, string>> previews = [];

  await foreach (var streamEvent in stream.Enumerate())
  {
      if (streamEvent.TryPickStartEvent(out var start))
      {
          // 이 id의 이벤트에 대해 열린 미리 보기입니다. 이 스트림은 agent.message
          // 델타만 활성화합니다. TryPick*은 예외 대신 false를 반환하므로,
          // 다른 미리 보기 유형(나중에 추가되는 유형 포함)은 건너뜁니다.
          if (start.Event.TryPickAgentMessage(out var preview))
          {
              Console.WriteLine($"event_start             {preview.Type.Raw()} {preview.ID}");
          }
      }
      else if (streamEvent.TryPickDeltaEvent(out var delta))
      {
          // 새 인덱스면 삽입하고, 기존 인덱스면 덧붙입니다
          if (!previews.TryGetValue(delta.EventID, out var fragments))
          {
              previews[delta.EventID] = fragments = [];
          }
          var index = delta.Delta.Index ?? 0;
          fragments[index] = fragments.GetValueOrDefault(index, "") + delta.Delta.Content.Text;
          Console.WriteLine($"event_delta             preview: {fragments[index]}");
      }
      else if (streamEvent.TryPickAgentMessageEvent(out var message))
      {
          // 델타는 최선 노력 방식입니다: 미리 보기를 버리고 버퍼링된 이벤트를 사용합니다
          previews.Remove(message.ID);
          var text = string.Concat(message.Content.Select(block =>
              block.TryPickBetaManagedAgentsTextBlock(out var textBlock) ? textBlock.Text : ""));
          Console.WriteLine($"agent.message           {message.ID} {text}");
      }
      else if (streamEvent.TryPickSpanModelRequestEndEvent(out _))
      {
          // 더 이상 델타가 오지 않습니다. 조정되지 않은 미리 보기를 모두 닫습니다.
          foreach (var eventId in previews.Keys)
          {
              Console.WriteLine($"span.model_request_end  closing preview for {eventId}");
          }
          previews.Clear();
      }
      else if (streamEvent.TryPickSessionStatusIdleEvent(out _))
      {
          break;
      }
  }
  ```

  ```go Go
  	// agent.message 이벤트의 증분 미리보기를 활성화합니다
  	stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, anthropic.BetaSessionEventStreamParams{
  		EventDeltas: []anthropic.BetaManagedAgentsDeltaType{
  			anthropic.BetaManagedAgentsDeltaTypeAgentMessage,
  		},
  	})

  	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: "Write a haiku about the ocean.",
  					},
  				}},
  			},
  		}},
  	}); err != nil {
  		panic(err)
  	}

  	// accumulator는 event_start / event_delta 조각을
  	// 이벤트 ID별 agent.message 스냅샷으로 합칩니다. 제로 값 그대로 사용할 수 있습니다.
  	var previews anthropic.BetaManagedAgentsEventAccumulator

  deltas:
  	for stream.Next() {
  		event := stream.Current()
  		previews.Accumulate(event)

  		switch event := event.AsAny().(type) {
  		case anthropic.BetaManagedAgentsStartEvent:
  			fmt.Printf("event_start             %s %s\n", event.Event.Type, event.Event.ID)
  		case anthropic.BetaManagedAgentsDeltaEvent:
  			fmt.Printf("event_delta             preview: %q\n", previews.AgentMessageText(event.EventID))
  		case anthropic.BetaManagedAgentsAgentMessageEvent:
  			// 버퍼링된 이벤트에는 전체 콘텐츠가 담겨 있으며, accumulator는
  			// 미리보기를 이것으로 대체합니다
  			fmt.Printf("agent.message           %s %q\n", event.ID, previews.AgentMessageText(event.ID))
  		case anthropic.BetaManagedAgentsSpanModelRequestEndEvent:
  			// 이 요청에 대해 더 이상 델타가 오지 않습니다. accumulator는
  			// 여기서 스냅샷을 삭제하여, 버퍼링된 agent.message로
  			// 조정되지 않은 미리보기를 닫습니다.
  			fmt.Println("span.model_request_end  no more deltas for this request")
  		case anthropic.BetaManagedAgentsSessionStatusIdleEvent:
  			break deltas
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  	stream.Close()
  ```

  ```java Java
  // 이벤트 ID와 콘텐츠 인덱스를 키로 하는 미리보기 텍스트입니다. 버퍼링된 agent.message가 이를 대체합니다.
  Map<String, Map<Long, StringBuilder>> previews = new HashMap<>();

  // 이 연결에서 agent.message 미리보기를 활성화합니다
  try (var stream = client.beta().sessions().events().streamStreaming(
          session.id(),
          EventStreamParams.builder()
              .addEventDelta(BetaManagedAgentsDeltaType.AGENT_MESSAGE)
              .build()
  )) {
      client.beta().sessions().events().send(
          session.id(),
          EventSendParams.builder()
              .addEvent(BetaManagedAgentsUserMessageEventParams.builder()
                  .type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
                  .addTextContent("Describe the repo in one sentence.")
                  .build())
              .build()
      );

      Iterable<BetaManagedAgentsStreamSessionEvents> events = stream.stream()::iterator;
      deltas:
      for (var event : events) {
          switch (event.type().value()) {
              case EVENT_START -> {
                  if (event.asEventStart().event().isAgentMessage()) {
                      var preview = event.asEventStart().event().asAgentMessage();
                      IO.println("event_start             " + preview.type().asString() + " " + preview.id());
                  }
              }
              case EVENT_DELTA -> {
                  var eventDelta = event.asEventDelta();
                  var fragment = eventDelta.delta();
                  var buffer = previews
                      .computeIfAbsent(eventDelta.eventId(), _ -> new HashMap<>())
                      .computeIfAbsent(fragment.index().orElse(0L), _ -> new StringBuilder());
                  buffer.append(fragment.content().text());
                  IO.println("event_delta             preview: " + buffer);
              }
              case AGENT_MESSAGE -> {
                  // 버퍼링된 이벤트가 기록입니다. 미리보기를 버리고 해당 콘텐츠를 렌더링합니다
                  var message = event.asAgentMessage();
                  previews.remove(message.id());
                  var text = message.content().stream()
                      .flatMap(block -> block.text().stream())
                      .map(textBlock -> textBlock.text())
                      .collect(Collectors.joining());
                  IO.println("agent.message           " + message.id() + " " + text);
              }
              case SPAN_MODEL_REQUEST_END -> {
                  // 더 이상 델타가 오지 않습니다. 버퍼링된 이벤트가 도착하지 않은 미리보기를 닫습니다.
                  previews.keySet().forEach(eventId ->
                      IO.println("span.model_request_end  closing preview for " + eventId));
                  previews.clear();
              }
              case SESSION_STATUS_IDLE -> {
                  break deltas;
              }
          }
      }
  }
  ```

  ```php PHP
  use Anthropic\Beta\Sessions\BetaManagedAgentsDeltaEvent;
  use Anthropic\Beta\Sessions\BetaManagedAgentsDeltaType;
  use Anthropic\Beta\Sessions\BetaManagedAgentsStartEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsSpanModelRequestEndEvent;
  use Anthropic\Beta\Sessions\Events\ManagedAgentsTextBlock;
  use Anthropic\Lib\Sessions\EventAccumulator;

  // 이벤트 id를 키로 하는 미리보기 스냅샷입니다. EventAccumulator::accumulate()는 각
  // event_start / event_delta를 agent.message 스냅샷에 추가하며, 버퍼링된
  // agent.message가 이를 대체합니다.
  $previews = [];

  // agent.message 스냅샷 또는 이벤트의 텍스트
  $textOf = fn (ManagedAgentsAgentMessageEvent $message): string => implode(
      '',
      array_map(
          fn ($block) => $block->text,
          array_filter(
              $message->content,
              fn ($block) => $block instanceof ManagedAgentsTextBlock,
          ),
      ),
  );

  // 이 연결에서 agent.message 미리보기를 사용하도록 설정합니다
  $stream = $client->beta->sessions->events->streamStream(
      $session->id,
      eventDeltas: [BetaManagedAgentsDeltaType::AGENT_MESSAGE],
  );

  $client->beta->sessions->events->send(
      $session->id,
      events: [
          [
              'type' => 'user.message',
              'content' => [['type' => 'text', 'text' => 'Describe the repo in one sentence.']],
          ],
      ],
  );

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof BetaManagedAgentsStartEvent:
              $snapshot = EventAccumulator::accumulate(null, $event);
              if ($snapshot !== null) {
                  $previews[$event->event->id] = $snapshot;
              }
              printf("event_start             %s %s\n", $event->event->type, $event->event->id);
              break;
          case $event instanceof BetaManagedAgentsDeltaEvent:
              // 이미 대체되었거나 닫힌 미리보기에 대한 늦은 델타는 건너뜁니다
              if (!isset($previews[$event->eventID])) {
                  break; // Break switch statement
              }
              $previews[$event->eventID] = EventAccumulator::accumulate($previews[$event->eventID], $event);
              printf("event_delta             preview: %s\n", json_encode($textOf($previews[$event->eventID])));
              break;
          case $event instanceof ManagedAgentsAgentMessageEvent:
              // 버퍼링된 이벤트가 최종 기록이며, 미리보기를 대체하고 닫습니다
              unset($previews[$event->id]);
              printf("agent.message           %s %s\n", $event->id, json_encode($textOf($event)));
              break;
          case $event instanceof ManagedAgentsSpanModelRequestEndEvent:
              // 더 이상 델타가 오지 않습니다. 버퍼링된 이벤트가
              // 도착하지 않은 미리보기를 모두 닫습니다.
              foreach (array_keys($previews) as $eventID) {
                  printf("span.model_request_end  closing preview for %s\n", $eventID);
              }
              $previews = [];
              break;
          case $event instanceof ManagedAgentsSessionStatusIdleEvent:
              break 2; // Break foreach loop
      }
  }
  $stream->close();
  ```

  ```ruby Ruby
  # 이벤트 델타를 활성화합니다: agent.message 미리보기가 증분 조각으로 스트리밍됩니다.
  stream = client.beta.sessions.events.stream_events(
    session.id,
    event_deltas: [Anthropic::Beta::BetaManagedAgentsDeltaType::AGENT_MESSAGE]
  )

  client.beta.sessions.events.send_(
    session.id,
    events: [{
      type: "user.message",
      content: [{type: "text", text: "Give a one-sentence project tagline."}]
    }]
  )

  # 미리보기 조각을 (event_id, index)별로 명시적으로 변경 가능한
  # (`+""`) 버퍼에 누적하여 `<<`로 제자리에 추가할 수 있게 합니다. 같은 id의 버퍼링된
  # agent.message가 권위 있는 기록이며 델타로 쌓은 내용을 대체합니다.
  buffers = Hash.new do |by_event, event_id|
    by_event[event_id] = Hash.new { |fragments, index| fragments[index] = +"" }
  end

  stream.each do |event|
    case event
    when Anthropic::Beta::BetaManagedAgentsStartEvent
      puts "event_start             #{event.event.type} #{event.event.id}"
    when Anthropic::Beta::BetaManagedAgentsDeltaEvent
      delta = event.delta
      fragment = delta.content.text
      buffers[event.event_id][delta.index || 0] << fragment
      puts "event_delta             preview: #{buffers[event.event_id][delta.index || 0].inspect}"
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
      # 대체: 누적된 미리보기를 버리고 완전한 이벤트를 렌더링합니다.
      buffers.delete(event.id)
      puts "agent.message           #{event.id} #{event.content.map(&:text).join.inspect}"
    when Anthropic::Beta::Sessions::BetaManagedAgentsSpanModelRequestEndEvent
      # 더 이상 델타가 오지 않습니다. 조정되지 않은 미리보기를 모두 닫습니다.
      buffers.each_key { |event_id| puts "span.model_request_end  closing preview for #{event_id}" }
      buffers.clear
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
      break
    else
      # 다른 이벤트 유형은 무시합니다
    end
  end
  ```
</CodeGroup>

## 세션 스레드 이벤트 미리 보기

[멀티에이전트](https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration) 세션에서는 모든 세션 스레드가 자체 이벤트 스트림을 가집니다. 이 스트림도 같은 값을 가진 동일한 `event_deltas[]` 매개변수를 받습니다.

하나의 연결은 읽고 있는 스레드만 미리 봅니다. 세션 수준 스트림은 기본 스레드를 미리 보며, 하위 스레드의 미리 보기는 세션 수준 스트림에 교차 게시되지 않습니다. 모델이 생성하는 서브에이전트의 텍스트를 보려면 해당 서브에이전트의 스레드 스트림을 여세요.

스레드 스트림의 경로는 `/threads/{thread_id}/stream`으로 끝납니다. `/events/stream`은 세션 수준에만 존재하므로 `/threads/{thread_id}/events/stream` 엔드포인트는 없습니다.

`event_start`와 `event_delta`는 스레드 스트림에서도 세션 수준 스트림과 같은 형태를 가지며, [누적 및 조정](https://platform.claude.com/docs/ko/managed-agents/event-deltas#accumulate-and-reconcile) 패턴이 그대로 적용됩니다. 스트림 연결마다 하나의 누적기 인스턴스를 실행하세요.

<CodeGroup>
  ```bash cURL
  # 스레드 스트림은 세션 스트림과 동일한 event_deltas[] 매개변수를 받습니다.
  # 대괄호를 퍼센트 인코딩(%5B%5D)하고 URL을 따옴표로 감싸세요.
  curl --fail-with-body -sS -N \
    "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/stream?beta=true&event_deltas%5B%5D=agent.message" \
    -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
  # 스레드 스트림은 세션 스트림과 동일한 event_deltas 매개변수를 받으며,
  # 미리 볼 이벤트 유형마다 --event-delta 플래그를 하나씩 지정합니다.
  ant beta:sessions:threads:events stream \
    --session-id "$SESSION_ID" \
    --thread-id "$THREAD_ID" \
    --event-delta agent.message
  ```

  ```python Python
  # 세션의 스레드를 조회하고 자식을 선택합니다: 자식 스레드는 parent_thread_id가 null이 아니며,
  # 기본 스레드의 parent_thread_id는 null입니다.
  child_thread = next(
      thread
      for thread in client.beta.sessions.threads.list(session.id)
      if thread.parent_thread_id is not None
  )

  # 자식 스레드의 스트림은 세션 스트림과 동일한 event_deltas
  # 매개변수를 받습니다.
  with client.beta.sessions.threads.events.stream(
      child_thread.id,
      session_id=session.id,
      event_deltas=["agent.message"],
  ) as stream:
      for event in stream:
          match event.type:
              case "event_delta":
                  print(event.delta.content.text, end="")
              case "agent.message":
                  # 버퍼링된 이벤트가 권위 있는 기록이므로 그 콘텐츠를 렌더링합니다
                  print()
                  for block in event.content:
                      if block.type == "text":
                          print(block.text, end="")
                  print()
              case "session.thread_status_idle":
                  break
  ```

  ```typescript TypeScript
  // 세션의 스레드를 조회하고 하위 스레드를 선택합니다: 하위 스레드는 null이 아닌
  // parent_thread_id를 가지며, 기본 스레드의 parent_thread_id는 null입니다.
  let childThreadId: string | undefined;
  for await (const thread of client.beta.sessions.threads.list(session.id)) {
    if (thread.parent_thread_id !== null) {
      childThreadId = thread.id;
      break;
    }
  }
  if (!childThreadId) {
    throw new Error("No child thread found");
  }

  // 하위 스레드의 스트림은 세션 스트림과 동일한 event_deltas 매개변수를
  // 받습니다.
  const stream = await client.beta.sessions.threads.events.stream(childThreadId, {
    session_id: session.id,
    event_deltas: ["agent.message"]
  });

  threadDeltas: for await (const event of stream) {
    switch (event.type) {
      case "event_delta":
        process.stdout.write(event.delta.content.text);
        break;
      case "agent.message": {
        // 버퍼링된 이벤트가 권위 있는 기록이므로 해당 콘텐츠를 렌더링합니다.
        process.stdout.write("\n");
        const text = event.content
          .map((block) => (block.type === "text" ? block.text : ""))
          .join("");
        console.log(text);
        break;
      }
      case "session.thread_status_idle":
        break threadDeltas;
    }
  }
  stream.controller.abort();
  ```

  ```csharp C#
  // 세션의 스레드를 조회해 하위 스레드를 선택합니다: 하위 스레드는 parent_thread_id가
  // null이 아니며, 기본 스레드의 parent_thread_id는 null입니다.
  var threads = await client.Beta.Sessions.Threads.List(session.ID);
  var childThread = threads.Items.First(thread => thread.ParentThreadID is not null);

  // 하위 스레드의 스트림은 세션 스트림과 동일한 event_deltas 매개변수를
  // 받습니다.
  using var stream = await client.Beta.Sessions.Threads.Events.WithRawResponse.StreamStreaming(
      childThread.ID,
      new() { SessionID = session.ID, EventDeltas = [BetaManagedAgentsDeltaType.AgentMessage] }
  );

  await foreach (var streamEvent in stream.Enumerate())
  {
      if (streamEvent.TryPickDeltaEvent(out var delta))
      {
          Console.Write(delta.Delta.Content.Text);
      }
      else if (streamEvent.TryPickAgentMessageEvent(out var message))
      {
          // 버퍼링된 이벤트가 권위 있는 기록이므로 해당 콘텐츠를 렌더링합니다.
          Console.WriteLine();
          var text = string.Concat(message.Content.Select(block =>
              block.TryPickBetaManagedAgentsTextBlock(out var textBlock) ? textBlock.Text : ""));
          Console.WriteLine(text);
      }
      else if (streamEvent.TryPickSessionThreadStatusIdleEvent(out _))
      {
          break;
      }
  }
  ```

  ```go Go
  	// 세션의 스레드를 조회하고 자식을 선택합니다: 자식 스레드는 parent_thread_id가 null이 아니며,
  	// 기본 스레드의 parent_thread_id는 null입니다.
  	var childThreadID string
  	threads := client.Beta.Sessions.Threads.ListAutoPaging(ctx, session.ID, anthropic.BetaSessionThreadListParams{})
  	for threads.Next() {
  		if thread := threads.Current(); thread.ParentThreadID != "" {
  			childThreadID = thread.ID
  			break
  		}
  	}
  	if err := threads.Err(); err != nil {
  		panic(err)
  	}

  	// 자식 스레드의 스트림은 세션 스트림과 동일한 event_deltas 매개변수를
  	// 받습니다. 스트림 연결마다 읽기 루프를 하나씩 실행하세요.
  	stream := client.Beta.Sessions.Threads.Events.StreamEvents(ctx, childThreadID, anthropic.BetaSessionThreadEventStreamParams{
  		SessionID: session.ID,
  		EventDeltas: []anthropic.BetaManagedAgentsDeltaType{
  			anthropic.BetaManagedAgentsDeltaTypeAgentMessage,
  		},
  	})

  threadDeltas:
  	for stream.Next() {
  		switch event := stream.Current().AsAny().(type) {
  		case anthropic.BetaManagedAgentsDeltaEvent:
  			fmt.Print(event.Delta.Content.Text)
  		case anthropic.BetaManagedAgentsAgentMessageEvent:
  			// 버퍼링된 이벤트가 권위 있는 기록이므로 해당 콘텐츠를 렌더링합니다.
  			fmt.Println()
  			// 구체 타입 목록: BetaManagedAgentsTextBlock
  			for _, block := range event.Content {
  				fmt.Print(block.Text)
  			}
  			fmt.Println()
  		case anthropic.BetaManagedAgentsSessionThreadStatusIdleEvent:
  			break threadDeltas
  		}
  	}
  	if err := stream.Err(); err != nil {
  		panic(err)
  	}
  	stream.Close()
  ```

  ```java Java
  // 세션의 스레드를 나열하고 자식을 선택합니다: 자식 스레드는 parent_thread_id가 null이 아니며,
  // 기본 스레드의 parent_thread_id는 null입니다.
  var childThread = client.beta().sessions().threads().list(session.id()).autoPager().stream()
      .filter(thread -> thread.parentThreadId().isPresent())
      .findFirst()
      .orElseThrow();

  // 자식 스레드의 스트림은 세션 스트림과 동일한 event_deltas 매개변수를 받습니다.
  // params 클래스가 세션 수준 클래스와 단순 이름이 같으므로 정규화된 이름을 사용합니다.
  try (var stream = client.beta().sessions().threads().events().streamStreaming(
          childThread.id(),
          com.anthropic.models.beta.sessions.threads.events.EventStreamParams.builder()
              .sessionId(session.id())
              .addEventDelta(BetaManagedAgentsDeltaType.AGENT_MESSAGE)
              .build()
  )) {
      Iterable<BetaManagedAgentsStreamSessionThreadEvents> events = stream.stream()::iterator;
      threadDeltas:
      for (var event : events) {
          switch (event.type().value()) {
              case EVENT_DELTA -> IO.print(event.asEventDelta().delta().content().text());
              case AGENT_MESSAGE -> {
                  // 버퍼링된 이벤트가 권위 있는 기록이므로 해당 콘텐츠를 렌더링합니다.
                  IO.println();
                  event.asAgentMessage().content().forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text())));
                  IO.println();
              }
              case SESSION_THREAD_STATUS_IDLE -> {
                  break threadDeltas;
              }
          }
      }
  }
  ```

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

  // 세션의 스레드를 조회하고 하위 스레드를 선택합니다. 하위 스레드는 parentThreadID가
  // null이 아니며, 기본 스레드의 parentThreadID는 null입니다.
  $childThread = null;
  foreach ($client->beta->sessions->threads->list($session->id)->pagingEachItem() as $thread) {
      if ($thread->parentThreadID !== null) {
          $childThread = $thread;
          break;
      }
  }

  // 하위 스레드의 스트림은 세션 스트림과 동일한
  // eventDeltas 매개변수를 받습니다.
  $stream = $client->beta->sessions->threads->events->streamStream(
      $childThread->id,
      sessionID: $session->id,
      eventDeltas: [BetaManagedAgentsDeltaType::AGENT_MESSAGE],
  );

  foreach ($stream as $event) {
      switch (true) {
          case $event instanceof BetaManagedAgentsDeltaEvent:
              echo $event->delta->content->text;
              break;
          case $event instanceof ManagedAgentsAgentMessageEvent:
              // 버퍼링된 이벤트가 신뢰할 수 있는 기록이므로 해당 콘텐츠를 렌더링합니다
              echo "\n";
              foreach ($event->content as $block) {
                  if ($block instanceof ManagedAgentsTextBlock) {
                      echo $block->text;
                  }
              }
              echo "\n";
              break;
          case $event instanceof ManagedAgentsSessionThreadStatusIdleEvent:
              break 2; // Break foreach loop
      }
  }
  $stream->close();
  ```

  ```ruby Ruby
  # 세션의 스레드를 조회하고 자식을 선택합니다: 자식 스레드는 parent_thread_id가 null이 아니며,
  # 기본 스레드의 parent_thread_id는 null입니다.
  child_thread = client.beta.sessions.threads.list(session.id).to_enum.find { it.parent_thread_id }

  # 자식 스레드의 스트림은 세션 스트림과 동일한 event_deltas 매개변수를
  # 받습니다.
  stream = client.beta.sessions.threads.events.stream_events(
    child_thread.id,
    session_id: session.id,
    event_deltas: [Anthropic::Beta::BetaManagedAgentsDeltaType::AGENT_MESSAGE]
  )

  stream.each do |event|
    case event
    when Anthropic::Beta::BetaManagedAgentsDeltaEvent
      print event.delta.content.text
    when Anthropic::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
      # 버퍼링된 이벤트가 권위 있는 기록이므로 그 내용을 렌더링합니다.
      puts
      event.content.each { print it.text }
      puts
    when Anthropic::Beta::Sessions::BetaManagedAgentsSessionThreadStatusIdleEvent
      break
    else
      # 다른 이벤트 유형은 무시합니다
    end
  end
  ```
</CodeGroup>

읽기 루프는 세션 스레드의 턴이 끝나고 스레드가 유휴 상태가 될 때 발행되는 이벤트인 [`session.thread_status_idle`](https://platform.claude.com/docs/ko/managed-agents/reference#event-types)에서 종료됩니다.

## 제한 사항

* **최선의 노력:** 부하가 걸리면 서버가 이벤트의 델타를 누락시킬 수 있습니다. 이 경우 텍스트의 연속된 접두사를 받은 후 해당 이벤트에 대한 델타를 더 이상 받지 않습니다. 버퍼링된 `agent.message`는 여전히 완전한 상태로 도착합니다. 누적된 미리 보기를 최종 결과로 취급하지 마세요.
* **재연결 시 재전송 없음:** 델타는 옵트인한 연결이 열려 있는 동안에만 해당 연결로 전달됩니다. 이는 세션 수준 스트림과 각 세션 스레드 스트림 모두에 동일하게 적용됩니다. 모델 요청이 시작된 후에 열린 연결은 진행 중인 해당 이벤트에 대한 델타를 받지 않습니다. 놓친 델타를 다시 요청할 방법은 없습니다.
* **하나의 스레드, 텍스트만:** 미리 보기는 [연결이 읽고 있는 스레드](https://platform.claude.com/docs/ko/managed-agents/event-deltas#preview-session-thread-events)의 어시스턴트 텍스트만 다룹니다. 도구 사용, 도구 결과, MCP 결과는 미리 보기되지 않습니다.
* **영구 저장되지 않음:** `event_start`와 `event_delta`는 실시간 스트림에만 존재합니다. 세션의 이벤트 기록(`GET /v1/sessions/{session_id}/events`)이나 어떤 세션 스레드의 이벤트 기록에도 나타나지 않습니다.

## 미리 보기 문제 해결

| 보이는 현상                                             | 의미                                                                                                                                                                                                                                              |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 버퍼링된 이벤트는 있지만 `event_start`나 `event_delta`가 없는 스트림 | 읽고 있는 연결이 옵트인하지 않았거나, 턴이 스트리밍 중인 스레드를 전혀 거치지 않았습니다. `event_deltas[]`는 세션별이 아니라 연결별로 적용됩니다. 어떤 스레드가 실행되었는지 확인하려면 세션의 스레드 목록을 조회하세요(`GET /v1/sessions/{session_id}/threads`).                                                                     |
| 미리 보기 도중 끊기는 스트림                                   | 델타는 재전송되지 않습니다. [재연결 절차](https://platform.claude.com/docs/ko/managed-agents/events-and-streaming#reconnect-without-missing-events)를 따르세요: 스트림을 다시 열고 이벤트 기록을 조회합니다. 기록에는 연결이 끊긴 동안 발행된 버퍼링된 이벤트가 포함되며, 여기에는 미리 보기가 기다리던 `agent.message`도 포함됩니다. |
| 스트림 URL에서 404                                      | 경로나 ID가 잘못되었거나, 요청에 managed-agents 베타 헤더가 전혀 없습니다. 스레드 엔드포인트는 베타로 제한되어 있으므로 헤더가 없으면 존재하지 않습니다.                                                                                                                                                  |
| `event_deltas`를 언급하는 400                           | `agent.message`와 `agent.thinking`만 허용됩니다.                                                                                                                                                                                                       |

## 다음 단계

<CardGroup cols={2}>
  <Card title="세션 이벤트 스트림" icon="lightning" href="https://platform.claude.com/docs/ko/managed-agents/events-and-streaming">
    이벤트를 보내고, 응답을 스트리밍하고, 실행 도중 세션을 중단하거나 방향을 전환합니다.
  </Card>

  <Card title="멀티에이전트 오케스트레이션" icon="stack" href="https://platform.claude.com/docs/ko/managed-agents/multiagent-orchestration">
    단일 세션 내에서 여러 에이전트를 조율합니다.
  </Card>
</CardGroup>
