---
title: Hilos de sesión
url: https://platform.claude.com/docs/es/managed-agents/session-threads
description: Lista, interrumpe y archiva los hilos de una sesión multiagente, lee sus eventos y gestiona los permisos de herramientas entre ellos.
featureMetadata:
  topic:
    title: Managed Agents
    url: https://platform.claude.com/docs/en/managed-agents/overview
  status: beta
  betaHeader: managed-agents-2026-04-01
---

En una [sesión multiagente](https://platform.claude.com/docs/es/managed-agents/multiagent-orchestration), cada agente trabaja en su propio "session thread" (**hilo de sesión**). Esta página explica cómo listar, interrumpir y archivar hilos, los eventos que envían y cómo funcionan los permisos de herramientas entre ellos. Una ["workflow run" (ejecución de flujo de trabajo)](https://platform.claude.com/docs/es/managed-agents/workflow-runs) también crea hilos de sesión.

## Hilo principal e hilos de sesión

El "session-level event stream" (**flujo de eventos a nivel de sesión**) (`/v1/sessions/{session_id}/events/stream`) se considera el "primary thread" (**hilo principal**), y contiene una vista condensada de toda la actividad en todos los hilos. No ves la actividad completa de los [subagentes](https://platform.claude.com/docs/es/managed-agents/multiagent-orchestration#delegate-to-subagents), pero sí ves el inicio y el final de su trabajo, así como los eventos bloqueantes, como las solicitudes de permiso de herramientas.

Los **hilos de sesión** te permiten examinar en detalle la actividad de un agente específico.

El `status` (csharp, go: `Status`) de la sesión es una agregación de toda la actividad de los agentes; si al menos un hilo está en `running`, el estado general de la sesión también es `running`. Una [ejecución de flujo de trabajo](https://platform.claude.com/docs/es/managed-agents/workflow-runs) que está en curso también puede mantener la sesión en `running`, incluso mientras ninguno de sus hilos está trabajando. Cuando ningún hilo está trabajando y un hilo espera a tu cliente, la sesión está en `idle`; consulta [Saber cuándo termina el trabajo](https://platform.claude.com/docs/es/managed-agents/workflow-runs#know-when-the-work-is-done).

Un [presupuesto de sesión](https://platform.claude.com/docs/es/managed-agents/budgets) es un único límite compartido entre todos los hilos de una sesión. Cuando se alcanza el límite, los hilos se pausan de forma independiente, y el costo de cada hilo se calcula según el modelo que atiende a ese hilo.

<Note>
  Una sesión puede tener como máximo 25 hilos hijos a la vez. Los hilos inactivos cuentan hasta que los archives, y el hilo principal no cuenta. El agente del hilo principal puede llamar a varias copias de un mismo subagente, creando varios hilos asociados a un solo `agent`. Los hilos de consulta del [asesor](https://platform.claude.com/docs/es/managed-agents/multiagent-orchestration#give-the-session-an-advisor) están exentos de este límite. También lo están los hilos de una [ejecución de flujo de trabajo](https://platform.claude.com/docs/es/managed-agents/workflow-runs).
</Note>

## Listar hilos

Lista todos los hilos asociados a una sesión de la siguiente manera:

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

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

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

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

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

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

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

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

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

La lista completa incluye el hilo principal. `parent_thread_id` (java: `parentThreadId`; php: `parentThreadID`; csharp, go: `ParentThreadID`) es `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`) para el hilo principal. Todos los demás hilos son hilos hijos. `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`) es `null` (python: `None`; ruby: `nil`; java: `Optional.empty()`; go: `""`) excepto en [los hilos de una ejecución](https://platform.claude.com/docs/es/managed-agents/workflow-runs#a-runs-threads).

Para listar solo los hilos que tienen ciertos estados, agrega `statuses[]` a la solicitud y repítelo para indicar más de un estado, como en `?statuses[]=running&statuses[]=idle`. Omítelo para devolver hilos de todos los estados.

## Interrumpir un hilo de sesión

Envía `user.interrupt` con `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) para detener un hilo específico. Omitir `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) interrumpe todos los hilos no archivados de la sesión, incluido el principal. En una sesión con flujos de trabajo dinámicos, una interrupción no finaliza ninguna ejecución, y una que nombra un hilo de una ejecución no detiene nada. Una interrupción cierra las llamadas a herramientas pendientes de otros hilos hijos, pero no confíes en ella para cerrar las de un hilo de ejecución. Consulta [Interrumpir una sesión con ejecuciones abiertas](https://platform.claude.com/docs/es/managed-agents/workflow-runs#interrupt-a-session-with-runs-open).

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

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

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

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

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

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

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

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

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

Si el hilo de un subagente está bloqueado en `requires_action`, la interrupción cierra cada llamada a herramienta pendiente con un resultado de herramienta de error ("Tool execution was interrupted before completion. Please retry.") y vuelve a emitir `session.thread_status_idle` con `stop_reason: end_turn` directamente; no se muestrea el modelo. Si el hilo hijo está inactivo con `end_turn` o `budget_reached`, la interrupción no tiene efecto. Una interrupción que nombra un hilo terminado devuelve un error 400. Un hijo interrumpido no envía al agente del hilo principal el informe que envía cuando termina un turno. Mientras ese agente espera al hijo, no inicia otro turno hasta que le llegue algo más, como un `user.message` o el informe de otro hilo.

## Archivar un hilo de sesión

Opcionalmente, archiva un hilo de sesión cuando haya completado su trabajo. Archivar un hilo libera su lugar dentro del límite de 25 hilos hijos. El servidor archiva por sí mismo los hilos de una [ejecución de flujo de trabajo](https://platform.claude.com/docs/es/managed-agents/workflow-runs). No necesitas archivarlos, y no puedes hacerlo mientras la ejecución esté abierta.

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

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

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

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

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

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

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

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

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

Archivar solo tiene éxito si el hilo está `idle`. Un hilo detenido en `requires_action` cuenta como inactivo y se puede archivar directamente; solo un hilo en ejecución debe interrumpirse primero:

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

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

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

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

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

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

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

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

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

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

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

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

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

## Eventos del hilo principal

Estos eventos muestran la actividad multiagente en el hilo principal en `/v1/sessions/{session_id}/events/stream`. Los eventos de mensajes se nombran según el hilo en cuyo flujo aparecen: `agent.thread_message_received` significa que llegó un mensaje a este hilo desde otro hilo, y `agent.thread_message_sent` significa que este hilo envió uno. La tarea que delega el agente del hilo principal, por ejemplo, llega al flujo propio del hijo como un evento `agent.thread_message_received`.

| Tipo                               | Descripción                                                                                                                                                                                                                                                                                                                                      |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `session.thread_created`           | Se creó un hilo. Incluye `session_thread_id` (java: `sessionThreadId`; php: `sessionThreadID`; csharp, go: `SessionThreadID`) y `agent_name` (java, php: `agentName`; csharp, go: `AgentName`).                                                                                                                                                  |
| `session.thread_status_running`    | Un hilo inició actividad.                                                                                                                                                                                                                                                                                                                        |
| `session.thread_status_idle`       | El agente asociado al hilo está esperando una entrada. Incluye un `stop_reason` (java, php: `stopReason`; csharp, go: `StopReason`) que indica por qué se detuvo el agente.                                                                                                                                                                      |
| `session.thread_status_terminated` | Un hilo terminó y no acepta más entradas, por ejemplo porque se archivó o encontró un error irrecuperable. Un hilo de asesor también termina cuando finaliza su consulta.                                                                                                                                                                        |
| `agent.thread_message_received`    | En el hilo principal, un subagente envió al agente del hilo principal un informe o una pregunta. Incluye `from_session_thread_id` (java: `fromSessionThreadId`; php: `fromSessionThreadID`; csharp, go: `FromSessionThreadID`), `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`) y `content` (csharp, go: `Content`). |
| `agent.thread_message_sent`        | En el hilo principal, el agente del hilo principal envió a un subagente una tarea o un mensaje de seguimiento. Incluye `to_session_thread_id` (java: `toSessionThreadId`; php: `toSessionThreadID`; csharp, go: `ToSessionThreadID`), `to_agent_name` (java, php: `toAgentName`; csharp, go: `ToAgentName`) y `content` (csharp, go: `Content`). |

Las consultas al asesor emiten estos mismos eventos de hilo bajo el nombre reservado `anthropic.advisor` (como `agent_name` (java, php: `agentName`; csharp, go: `AgentName`) en los eventos del ciclo de vida del hilo y como `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`) en la entrega del consejo); consulta [Darle un asesor a la sesión](https://platform.claude.com/docs/es/managed-agents/multiagent-orchestration#give-the-session-an-advisor) para ver la secuencia.

Los hilos de una [ejecución de flujo de trabajo](https://platform.claude.com/docs/es/managed-agents/workflow-runs) se muestran en el flujo principal de la siguiente manera:

* **Eventos del ciclo de vida:** Cada hilo de ejecución envía `session.thread_created`, con el `workflow_run_id` (java: `workflowRunId`; php: `workflowRunID`; csharp, go: `WorkflowRunID`) de la ejecución, y sus eventos `session.thread_status_running`, `session.thread_status_idle` y `session.thread_status_terminated`.
* **Eventos de mensajes:** El prompt de un hilo de ejecución, un evento `agent.thread_message_received`, permanece en su propio flujo.
* **Eventos de ejecución:** Los eventos `workflow_run.*` también llegan a este flujo; consulta [Eventos de ejecución](https://platform.claude.com/docs/es/managed-agents/workflow-runs#run-events).
* **Llamadas a herramientas que esperan tu respuesta:** Las llamadas a herramientas de un hilo de ejecución que necesitan a tu cliente se publican también en este flujo, como ocurre con cualquier hilo hijo. Consulta [Permisos de herramientas y herramientas personalizadas](https://platform.claude.com/docs/es/managed-agents/session-threads#tool-permissions-and-custom-tools).

## Eventos del hilo de sesión

Los eventos críticos se reenvían al hilo principal. Sin embargo, es posible que aún quieras investigar el razonamiento y las llamadas a herramientas de un agente específico. Para hacerlo, haz streaming o lista los eventos del hilo de sesión asociado.

Cada hilo de sesión tiene su propio flujo de eventos en `/v1/sessions/{session_id}/threads/{thread_id}/stream`, y acepta el mismo parámetro `event_deltas[]` que el flujo a nivel de sesión, por lo que puedes previsualizar el texto de un subagente a medida que el modelo lo genera. Una conexión solo previsualiza el hilo que está leyendo: las previsualizaciones de un hilo hijo nunca aparecen en el flujo a nivel de sesión, así que para observar a un subagente en vivo, abre el flujo de su propio hilo. Consulta [Previsualizar eventos de hilos de sesión](https://platform.claude.com/docs/es/managed-agents/event-deltas#preview-session-thread-events) para saber cómo activar, acumular y conciliar las previsualizaciones.

En una ejecución de flujo de trabajo, el servidor ejecuta un flujo de trabajo: un programa que escribe el agente del hilo principal. En cada uno de los hilos de la ejecución, el primer `agent.thread_message_received` es el prompt que escribió el flujo de trabajo. Su `from_session_thread_id` (java: `fromSessionThreadId`; php: `fromSessionThreadID`; csharp, go: `FromSessionThreadID`) es el ID del hilo principal, y la API no establece su `from_agent_name` (java, php: `fromAgentName`; csharp, go: `FromAgentName`). La API no garantiza el texto del prompt, así que no lo analices. El evento `session.thread_status_terminated` del hilo, en el flujo del hilo principal, te indica que el hilo terminó. Ningún evento registra el resultado que devolvió al flujo de trabajo.

El flujo de un hilo no reproduce eventos anteriores. Justo después de `session.thread_created`, la lista de eventos del hilo de una ejecución puede estar vacía, porque el servidor escribe el primer evento del hilo después de ese. Así que abre primero el flujo del hilo, luego lista los eventos del hilo y omite cada evento recibido por streaming cuyo `id` (csharp, go: `ID`) haya devuelto la lista.

<Tabs>
  <Tab title="Hacer streaming de eventos del hilo de sesión">
    <CodeGroup>
      ```bash cURL
      curl -fsSN "https://api.anthropic.com/v1/sessions/$SESSION_ID/threads/$THREAD_ID/stream?beta=true" \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "anthropic-beta: managed-agents-2026-04-01"
      ```

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

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

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

      loop: for await (const event of stream) {
        switch (event.type) {
          case "agent.message":
            for (const block of event.content) {
              if (block.type === "text") {
                process.stdout.write(block.text);
              }
            }
            break;
          case "session.thread_status_idle":
            break loop;
        }
      }
      ```

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

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

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

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

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

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

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

  <Tab title="Listar eventos del hilo de sesión">
    Lista todos los eventos pasados del hilo de sesión para obtener un historial completo.

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

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

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

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

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

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

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

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

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

## Permisos de herramientas y herramientas personalizadas

Si un subagente necesita algo de tu cliente, como [permiso](https://platform.claude.com/docs/es/managed-agents/events-and-streaming#tool-confirmation) para ejecutar una llamada a herramienta o el [resultado de una herramienta personalizada](https://platform.claude.com/docs/es/managed-agents/events-and-streaming#handling-custom-tool-calls), el evento se publica también en el **hilo principal** con un `session_thread_id` que identifica el hilo de sesión de origen. Una llamada a herramienta necesita tu permiso bajo `always_ask`, o bajo [`auto`](https://platform.claude.com/docs/es/managed-agents/permission-policies#let-the-server-evaluate-each-call-with-auto) cuando el servidor no llega a ninguna determinación.

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

Publica `user.tool_confirmation` (con `tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`)) o `user.custom_tool_result` (con `custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`)); el servidor enruta la respuesta al hilo correcto automáticamente. La respuesta puede aparecer en el hilo principal y en el hilo del subagente con valores de `id` (csharp, go: `ID`) diferentes. Para emparejar las dos copias, compara `type` (csharp, go: `Type`) y `tool_use_id` (java: `toolUseId`; php: `toolUseID`; csharp, go: `ToolUseID`) (o `custom_tool_use_id` (java: `customToolUseId`; php: `customToolUseID`; csharp, go: `CustomToolUseID`)), no `id` (csharp, go: `ID`).

La sesión pasa a `idle` solo cuando ningún hilo está `running`, por lo que `session.status_idle` puede llegar mucho después de la llamada de un subagente. No tienes que esperarlo: envía el `user.custom_tool_result` en cuanto llegue el evento `agent.custom_tool_use` publicado en el hilo principal.

Bajo `auto`, tus eventos `user.message` pueden llevar al servidor a permitir una llamada que de otro modo denegaría. Nada en el hilo de un subagente cuenta como tu intención. Tu cliente no publica mensajes allí, y los mensajes que el agente del hilo principal envía al subagente no cuentan. Cuando el servidor deniega una llamada bajo `auto`, no se publica nada en el hilo principal: el evento y el resultado de herramienta de error aparecen solo en el propio [flujo del hilo](https://platform.claude.com/docs/es/managed-agents/session-threads#session-thread-events) del subagente, y el subagente sigue ejecutándose.

El siguiente ejemplo va dentro del bucle de eventos del [controlador de confirmación de herramientas](https://platform.claude.com/docs/es/managed-agents/events-and-streaming#tool-confirmation). Para cada ID en `stop_reason.event_ids`, envía un `user.tool_confirmation` que permite la llamada. El mismo patrón se aplica a `user.custom_tool_result`, que también puedes enviar en cuanto llegue su evento `agent.custom_tool_use`.

<CodeGroup exclude="shell:cURL, shell:CLI">
  <CodeGroupItem language="cURL">
    Este flujo de trabajo necesita más lógica que un solo comando de shell. Usa el [SDK de Claude](https://platform.claude.com/docs/es/cli-sdks-libraries/overview) para ejecutarlo.
  </CodeGroupItem>

  <CodeGroupItem language="CLI">
    Este flujo de trabajo necesita más lógica que un solo comando de shell. Usa el [SDK de Claude](https://platform.claude.com/docs/es/cli-sdks-libraries/overview) para ejecutarlo.
  </CodeGroupItem>

  ```python Python
  for event_id in stop.event_ids:
      client.beta.sessions.events.send(
          session.id,
          events=[
              {
                  "type": "user.tool_confirmation",
                  "tool_use_id": event_id,
                  "result": "allow",
              }
          ],
      )
  ```

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

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

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

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

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

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

El patrón anterior responde a las llamadas que enumera un evento de inactividad. En el flujo principal, el evento `session.thread_status_idle` de un subagente puede llegar antes que los eventos `agent.tool_use` o `agent.mcp_tool_use` que enumera su `stop_reason.event_ids`. Un `user.tool_confirmation` para una llamada cuyo evento aún no ha llegado puede devolver 400. Para evitarlo, responde a cada llamada cuyo [`evaluated_permission` (java, php: `evaluatedPermission`; csharp, go: `EvaluatedPermission`)](https://platform.claude.com/docs/es/managed-agents/permission-policies#see-how-each-call-was-evaluated) sea `ask` cuando su propio evento llegue al flujo principal.
