A comunicação com o Claude Managed Agents é baseada em eventos. Você envia eventos de usuário ao agente e recebe de volta eventos do agente e da sessão para acompanhar o status.
Os eventos fluem em duas direções.
user.* iniciam uma sessão e a conduzem à medida que ela progride; system.message acrescenta contexto de nível de sistema que se aplica ao turno que o acompanha e a todos os turnos subsequentes.As strings de tipo de eventos de sessão, span, agente, usuário e sistema seguem uma convenção de nomenclatura {domain}.{action}. Os eventos de prévia de delta exclusivos do stream (event_start, event_delta) são a exceção. Consulte Tipos de eventos na referência para o catálogo completo.
Todo evento persistido inclui um timestamp processed_at definido quando o evento termina de ser processado. Nos eventos que você envia, processed_at é null enquanto o evento ainda está na fila atrás de eventos anteriores. As exceções são user.define_outcome, user.custom_tool_result e user.tool_result, que são processados no recebimento e ecoados de volta com processed_at já preenchido.
Envie um evento user.message para iniciar ou continuar o trabalho do agente:
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",
},
],
},
],
)Envie um evento user.interrupt para parar o agente durante a execução e, em seguida, envie um evento user.message para redirecioná-lo:
# O agente está analisando um arquivo no momento...
# Interrompa com uma nova direção:
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.",
},
],
},
],
)O agente reconhece a interrupção e muda para a nova tarefa. O turno interrompido termina com um evento session.status_idle cujo stop_reason é end_turn, o mesmo valor de um turno que termina por conta própria; não há um stop reason específico para interrupção.
Por padrão, o texto de resposta do agente chega ao stream como eventos agent.message em buffer, cada um emitido somente depois que a requisição ao modelo que o produziu termina. Os "event deltas" (deltas de eventos) permitem que você renderize esse texto de forma incremental, como uma prévia ao vivo, enquanto o modelo ainda o está gerando. Uma prévia não é a resposta: prévias são um auxílio de exibição de melhor esforço, e o agent.message em buffer é sempre o registro autoritativo. Um cliente que ignora as prévias ainda recebe um stream completo e correto.
As prévias são opcionais por conexão de stream. Adicione o parâmetro de consulta event_deltas[] ao stream que você está lendo, repetindo-o uma vez para cada tipo de evento que deseja pré-visualizar. Como [] é um padrão glob do shell, coloque a URL entre aspas sempre que montar a requisição em um shell; os exemplos codificam os colchetes em percent-encoding como %5B%5D, o que também funciona. Ambos os endpoints de stream aceitam o parâmetro: o stream de nível de sessão em GET /v1/sessions/{session_id}/events/stream e o stream próprio de cada thread de sessão em GET /v1/sessions/{session_id}/threads/{thread_id}/stream. Os valores aceitos são agent.message e agent.thinking; qualquer outro valor retorna um erro 400, assim como uma requisição com mais de 100 valores. As prévias de um subagente aparecem no stream da thread do próprio subagente.
Quando um evento pré-visualizado começa, o stream emite um event_start contendo o tipo e o id do evento que está por vir:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Para agent.message, o início é seguido por eventos event_delta contendo texto incremental. Cada delta indica o evento que ele estende em event_id e o bloco de conteúdo que ele estende em delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Quando um evento agent.thinking é pré-visualizado, apenas o event_start é emitido. Nenhum evento event_delta o segue, e o evento agent.thinking em buffer que conclui a prévia não carrega conteúdo de pensamento; ele é um sinal de progresso, não um portador de conteúdo.
Diferentemente dos eventos persistidos, event_start e event_delta não têm id nem processed_at próprios. O único identificador que carregam é o id do evento que pré-visualizam.
Todo SDK que oferece suporte a deltas de eventos inclui um helper acumulador que cuida da contabilidade de index para você. Os helpers de Go, Java, Ruby e C# também indexam a prévia em acumulação pelo id do evento; com os helpers de Python, TypeScript e PHP, você mantém esse mapa por conta própria e incorpora cada delta à entrada correspondente ao seu id. O padrão manual também funciona em todas as linguagens quando você precisa de contabilidade personalizada: aplique-o aos tipos de eventos gerados.
No padrão manual, trate a prévia como um buffer de rascunho e o evento em buffer como o registro. Indexe o buffer por (event_id, index). Reconcilie por requisição ao modelo: um turno abre com um único evento session.status_running; depois, em um turno que se completa normalmente, cada requisição ao modelo produz, em ordem, span.model_request_start, event_start, os eventos event_delta, o agent.message em buffer e, por fim, span.model_request_end (na aba Span events). Na transmissão, esta é a porção pré-visualizada dessa sequência, intercalada com os outros eventos em buffer da conexão:
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": [...]}A linha event_delta se repete uma vez por fragmento de texto. Processe cada evento à medida que ele chega:
event_start, anote o id anunciado. Os identificadores sempre coincidem: event_start.event.id, todo event_delta.event_id e o id do agent.message em buffer são o mesmo valor.event_delta, acrescente delta.content.text à entrada em (event_id, delta.index) e renderize o texto acumulado. O primeiro delta para um index cria essa entrada.agent.message em buffer chegar, faça a correspondência pelo id, descarte a prévia acumulada e renderize o conteúdo da mensagem em seu lugar.span.model_request_end, feche qualquer prévia que não tenha sido reconciliada pelo seu evento em buffer. Não virão mais deltas para ela. Se o turno falhar ou for interrompido, o evento em buffer pode nunca chegar; span.model_request_end ainda chega.Garantias nas quais o padrão se baseia:
(event_id, index), resulta em um prefixo de content[index].text no evento em buffer (um prefixo, não necessariamente o texto inteiro, porque deltas podem ser descartados sob carga).event_start por event_id, e o evento em buffer é a última coisa que essa conexão entrega para aquele id.# Snapshots de prévia, indexados por id de evento. accumulate_managed_agents_event agrega cada
# event_start / event_delta em um snapshot de agent.message; o
# agent.message bufferizado o substitui.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Habilite prévias de agent.message nesta conexão
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":
# O evento bufferizado é o registro: ele substitui e encerra a prévia
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":
# Não virão mais deltas. Encerre qualquer prévia cujo
# evento bufferizado nunca chegou.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
breakEm uma sessão multiagente, cada thread de sessão tem seu próprio fluxo de eventos em GET /v1/sessions/{session_id}/threads/{thread_id}/stream, e ele aceita o mesmo parâmetro event_deltas[] com os mesmos valores. As prévias têm escopo de thread por design: uma conexão pré-visualiza apenas a thread que está lendo. As prévias de uma thread filha são entregues no stream próprio dessa filha e nunca são replicadas no stream de nível de sessão, cujas prévias permanecem restritas à thread primária. Para acompanhar o texto de um subagente enquanto o modelo o gera, abra o stream da thread desse subagente.
É fácil errar o caminho do stream da thread: ele é /threads/{thread_id}/stream, não /events/stream (que existe apenas no nível de sessão), e não existe um endpoint /threads/{thread_id}/events/stream.
Os eventos de prévia em si não mudam. event_start e event_delta têm o mesmo formato em um stream de thread e no stream de nível de sessão, e o padrão de acumular e reconciliar se aplica conforme descrito. O único ajuste é de contabilidade: execute uma instância de acumulador por conexão de stream.
# Liste as threads da sessão e escolha uma filha: threads filhas têm um
# parent_thread_id não nulo, e o parent_thread_id da thread primária é null.
THREAD_ID=$(
curl --fail-with-body -sS \
"https://api.anthropic.com/v1/sessions/$SESSION_ID/threads?beta=true" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" |
jq -er 'first(.data[] | select(.parent_thread_id != null)).id'
)
# O stream da thread filha aceita o mesmo parâmetro event_deltas[] que o
# stream da sessão. Codifique os colchetes (%5B%5D) e coloque a URL entre aspas.
exec {stream}< <(
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"
)
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
case $(jq -r '.type' <<<"$event_json") in
event_delta)
jq -j '.delta.content.text' <<<"$event_json"
;;
agent.message)
# O evento em buffer é o registro autoritativo; renderize seu conteúdo.
printf '\n'
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
session.thread_status_idle)
break
;;
esac
done
exec {stream}<&-O loop de leitura termina em session.thread_status_idle, o evento emitido quando o turno da thread de sessão termina e a thread fica ociosa.
As prévias são ajustadas para responsividade. Desenvolva considerando estas restrições:
agent.message em buffer ainda chega completo. Nunca trate uma prévia acumulada como final.agent.message que sua prévia estava aguardando. Não há como solicitar novamente deltas perdidos.agent.thinking apenas com início: Uma prévia de agent.thinking emite apenas o event_start como sinal de que um bloco de pensamento começou; nenhum evento event_delta o segue.event_start e event_delta existem apenas no stream ao vivo. Eles não aparecem no histórico de eventos da sessão (GET /v1/sessions/{session_id}/events) nem no histórico de eventos de qualquer thread de sessão.Se o stream não se comportar como você espera:
| Você vê | O que significa |
|---|---|
Um stream com eventos em buffer, mas sem event_start ou event_delta | A conexão que você está lendo não optou pelas prévias (event_deltas[] se aplica por conexão, não por sessão), ou o turno nunca tocou a thread da qual você está fazendo streaming. As prévias têm escopo de thread, então liste as threads da sessão (GET /v1/sessions/{session_id}/threads) para descobrir qual delas foi executada. |
| Um 404 na URL do stream | O caminho ou um ID está errado, ou a requisição não carrega nenhum cabeçalho beta de managed-agents. Os endpoints de thread são restritos ao beta, portanto sem o cabeçalho eles não existem. |
Um 400 mencionando event_deltas | Apenas agent.message e agent.thinking são aceitos. |
Quando o agente invoca uma ferramenta personalizada:
agent.custom_tool_use contendo o nome da ferramenta e a entrada.session.status_idle contendo stop_reason: requires_action. Os IDs dos eventos bloqueantes estão no array stop_reason.event_ids.user.custom_tool_result para cada uma, passando o ID do evento no parâmetro custom_tool_use_id junto com o conteúdo do resultado.running.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:
# Busque o evento de uso de ferramenta personalizada e execute-o
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Envie o resultado de volta
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 "end_turn":
breakQuando uma política de permissão exige confirmação antes que uma ferramenta seja executada:
agent.tool_use ou agent.mcp_tool_use.session.status_idle contendo stop_reason: requires_action. Os IDs dos eventos bloqueantes estão no array stop_reason.event_ids.user.tool_confirmation para cada um, passando o ID do evento no parâmetro tool_use_id. Defina result como "allow" ou "deny". Use deny_message para explicar uma negação.running.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:
# Aprove a chamada de ferramenta pendente
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
breakAs sessões persistem entre interações. O histórico da conversa é preservado, a menos que a sessão seja explicitamente excluída. Quando uma sessão fica ociosa, seu sandbox passa por um checkpoint, preservando o estado completo do sandbox, incluindo o sistema de arquivos, os pacotes instalados e quaisquer arquivos que o agente tenha criado. Isso permite que você retome de forma limpa após a inatividade.
Para retomar uma sessão, envie um evento user.message para ela como de costume:
# Em produção, passe o ID armazenado da sessão que você deseja retomar.
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.
YAMLUma sessão criada com um orçamento pausa em vez de gastar além do limite. Quando o custo de lista rastreado da sessão atinge o teto, a plataforma pausa cada thread antes de sua próxima requisição ao modelo, e a sessão fica ociosa com um stop_reason de budget_reached em vez de ser encerrada. A requisição que levou o total além do teto é executada até o fim, portanto o list_cost informado pelo snapshot session.usage pode indicar um valor igual ou ligeiramente acima do teto. No stream, a pausa chega como três eventos, em ordem:
session.thread_status_idle com stop_reason: budget_reached, para cada thread à medida que ela pausa.session.usage, um snapshot do uso acumulado da sessão e do custo de lista rastreado.session.status_idle com stop_reason: budget_reached. O evento session.usage sempre precede imediatamente esse idle.Uma thread cuja requisição final tanto ultrapassa o teto quanto completa seu turno informa end_turn em seu próprio evento session.thread_status_idle, enquanto a sessão ainda informa budget_reached; baseie-se no stop_reason de nível de sessão para detectar a pausa.
Enquanto a sessão está no teto, ela aceita apenas os eventos que concluem trabalho já em andamento: user.tool_confirmation, user.tool_result, user.custom_tool_result e user.interrupt. Qualquer evento que iniciaria novo trabalho, incluindo user.message, é rejeitado com um erro 400 que menciona essa lista. Quando uma sessão tem tanto uma thread aguardando uma solicitação de ferramenta quanto uma thread pausada no teto, o stop_reason de nível de sessão é requires_action, não budget_reached: resolver a solicitação não dispara uma requisição ao modelo, então responda a ela como de costume.
Nenhum evento retoma uma sessão pausada no teto. Em vez disso, atualize o orçamento da sessão: alterar o teto para qualquer valor acima do custo de lista consumido, ou remover o orçamento atualizando a sessão com "budget": null, retoma o trabalho pausado automaticamente. Consulte Orçamentos de sessão para saber como o custo de lista é rastreado e a semântica completa de atualização de orçamento.
Envie um evento system.message para dar ao agente contexto privilegiado de nível de sistema que se aplica ao turno que o acompanha e a todos os turnos subsequentes. Diferentemente do campo system na definição do agente (que define o prompt do sistema de nível superior), o conteúdo de system.message é acrescentado ao contexto de sistema da sessão como um turno role: "system" em vez de substituir esse prompt. Use-o quando o agente precisar de orientação de nível de sistema atualizada no meio da sessão: uma persona diferente, restrições revisadas ou contexto obtido em tempo de execução que deve moldar o comportamento do modelo daí em diante.
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."
YAMLEnquanto a sessão está ociosa com stop_reason: requires_action, um system.message é aceito apenas quando vem após um evento de resultado de ferramenta na mesma requisição; enviado sozinho ou com um user.message, ele é rejeitado até que os eventos de ferramenta pendentes sejam resolvidos. content aceita de 1 a 1000 itens de texto.
O objeto de sessão inclui um campo usage com o uso cumulativo da sessão: contagens de tokens, uso de ferramentas do servidor, tempo ativo e o custo de lista rastreado. Busque a sessão depois que ela ficar ociosa para ler os totais mais recentes.
{
"id": "sesn_01...",
"status": "idle",
"usage": {
"input_tokens": 5000,
"output_tokens": 3200,
"cache_read_input_tokens": 20000,
"cache_creation": {
"ephemeral_5m_input_tokens": 2000,
"ephemeral_1h_input_tokens": 0
},
"list_cost": {
"amount": "187",
"currency": "USD"
},
"active_seconds": 342.5,
"server_tool_use": {
"web_search_requests": 3,
"web_fetch_requests": 0
}
}
}input_tokens informa os tokens de entrada não armazenados em cache e output_tokens informa o total de tokens de saída em todas as chamadas de modelo na sessão. O campo cache_read_input_tokens informa os tokens lidos do "prompt cache" (cache de prompt), e o objeto cache_creation detalha os tokens de criação de cache por tempo de vida do cache (ephemeral_5m_input_tokens e ephemeral_1h_input_tokens). As entradas de cache usam um TTL de 5 minutos por padrão, portanto turnos consecutivos dentro dessa janela se beneficiam de leituras de cache, que reduzem o custo por token.
list_cost é o consumo cumulativo da sessão precificado pelas tarifas públicas de lista, como um número inteiro de centavos em uma string, com um código de moeda. active_seconds é o tempo cumulativo durante o qual a sessão teve pelo menos uma thread em execução; a atividade sobreposta de threads concorrentes é contada uma única vez, diferentemente do active_seconds no objeto stats da sessão, que soma o tempo ativo próprio de cada thread. Esse valor deduplicado é a duração sobre a qual o custo de tempo de execução da sessão é precificado. server_tool_use conta as solicitações de ferramentas executadas pelo servidor para fins de precificação: as solicitações de busca na web são precificadas no custo de lista por solicitação, e as solicitações de web fetch não têm cobrança por solicitação e não são medidas, portanto web_fetch_requests exibe 0. O usage próprio de cada thread de sessão também traz list_cost e active_seconds. Os valores por thread são arredondados de forma independente e excluem o custo de tempo de execução da sessão, portanto não somam exatamente o list_cost da sessão; o valor da sessão é o autoritativo.
Você não precisa consultar a sessão repetidamente para observar esses totais. O evento session.usage traz o mesmo snapshot cumulativo (o objeto usage, mais o budget da sessão, que é null quando a sessão não tem nenhum) no stream da sessão e no histórico de eventos. Ele é emitido em transições para o estado ocioso, e não em um temporizador: a sessão emite um imediatamente antes de ficar ociosa, qualquer que seja o motivo de parada, e um quando uma thread pausa em um orçamento de sessão. Um leitor de stream, portanto, vê o custo final de um turno, ou do trabalho que atingiu um orçamento, sem uma busca extra.
Para impor um limite de gastos, defina um orçamento de sessão em vez de consultar o uso repetidamente e parar a sessão você mesmo. A plataforma precifica o consumo da sessão continuamente e pausa cada thread antes de sua próxima solicitação ao modelo assim que o custo de lista da sessão atinge o limite; consulte Atingindo um orçamento de sessão para ver como isso aparece no stream.
O Claude Console fornece uma visualização de linha do tempo visual das suas sessões de agente. Navegue até a seção Claude Managed Agents no Console para ver:
session.errorWas this page helpful?