La comunicazione con Claude Managed Agents è basata su eventi. Invii eventi utente all'agente e ricevi in risposta eventi dell'agente e della sessione per monitorarne lo stato.
Gli eventi fluiscono in due direzioni.
user.* avviano una sessione e la guidano man mano che procede; system.message aggiunge contesto a livello di sistema che si applica al turno che lo accompagna e a tutti i turni successivi.Le stringhe dei tipi di evento di sessione, span, agente, utente e sistema seguono una convenzione di denominazione {domain}.{action}. Gli eventi di anteprima delta disponibili solo sullo stream (event_start, event_delta) costituiscono l'eccezione. Consulta Tipi di eventi nel riferimento per il catalogo completo.
Ogni evento persistito include un timestamp processed_at impostato quando l'evento termina l'elaborazione. Sugli eventi che invii, processed_at è null mentre l'evento è ancora in coda dietro eventi precedenti. Le eccezioni sono user.define_outcome, user.custom_tool_result e user.tool_result, che vengono elaborati alla ricezione e restituiti con processed_at già popolato.
Invia un evento user.message per avviare o proseguire il lavoro dell'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",
},
],
},
],
)Invia un evento user.interrupt per fermare l'agente durante l'esecuzione, quindi prosegui con un evento user.message per reindirizzarlo:
# L'agente sta attualmente analizzando un file...
# Interrompi con una nuova direzione:
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.",
},
],
},
],
)L'agente riconosce l'interruzione e passa al nuovo compito. Il turno interrotto termina con un evento session.status_idle il cui stop_reason è end_turn, lo stesso valore di un turno che termina da solo; non esiste uno stop reason specifico per l'interruzione.
Per impostazione predefinita, il testo di risposta dell'agente raggiunge lo stream come eventi agent.message bufferizzati, ciascuno emesso solo dopo che la richiesta al modello che lo ha prodotto è terminata. Gli "event deltas" (delta degli eventi) ti consentono di visualizzare quel testo in modo incrementale, come anteprima in tempo reale, mentre il modello lo sta ancora generando. Un'anteprima non è la risposta: le anteprime sono un ausilio di visualizzazione best-effort, e l'agent.message bufferizzato è sempre il record autorevole. Un client che ignora le anteprime riceve comunque uno stream completo e corretto.
Le anteprime sono opt-in per singola connessione stream. Aggiungi il parametro di query event_deltas[] allo stream che stai leggendo, ripetendolo una volta per ogni tipo di evento di cui vuoi l'anteprima. Poiché [] è un pattern glob della shell, racchiudi l'URL tra virgolette ogni volta che costruisci la richiesta in una shell; gli esempi codificano in percentuale le parentesi quadre come %5B%5D, il che funziona ugualmente. Entrambi gli endpoint stream accettano il parametro: lo stream a livello di sessione su GET /v1/sessions/{session_id}/events/stream e lo stream proprio di ciascun thread di sessione su GET /v1/sessions/{session_id}/threads/{thread_id}/stream. I valori accettati sono agent.message e agent.thinking; qualsiasi altro valore restituisce un errore 400, così come una richiesta con più di 100 valori. Le anteprime di un subagente compaiono sullo stream del thread proprio di quel subagente.
Quando inizia un evento in anteprima, lo stream emette un event_start che riporta il tipo e l'id dell'evento imminente:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Per agent.message, lo start è seguito da eventi event_delta che trasportano testo incrementale. Ogni delta indica l'evento che estende in event_id e il blocco di contenuto che estende in delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Quando viene mostrata l'anteprima di un evento agent.thinking, viene emesso solo l'event_start. Non seguono eventi event_delta, e l'evento agent.thinking bufferizzato che conclude l'anteprima non contiene alcun contenuto di pensiero; è un segnale di avanzamento, non un vettore di contenuto.
A differenza degli eventi persistiti, event_start ed event_delta non hanno un proprio id o processed_at. L'unico identificatore che trasportano è l'id dell'evento di cui mostrano l'anteprima.
Ogni SDK che supporta i delta degli eventi include un helper accumulatore che gestisce per te la contabilità degli index. Gli helper Go, Java, Ruby e C# indicizzano inoltre l'anteprima in accumulo tramite l'id dell'evento; con gli helper Python, TypeScript e PHP mantieni tu stesso quella mappa e incorpori ogni delta nella voce corrispondente al suo id. Il pattern manuale funziona anche in ogni linguaggio quando hai bisogno di una contabilità personalizzata: applicalo ai tipi di evento generati.
Nel pattern manuale, tratta l'anteprima come un buffer temporaneo e l'evento bufferizzato come il record. Indicizza il buffer per (event_id, index). Riconcilia per richiesta al modello: un turno si apre con un singolo evento session.status_running, poi in un turno che si completa normalmente ogni richiesta al modello produce, in ordine, span.model_request_start, event_start, gli eventi event_delta, l'agent.message bufferizzato e infine span.model_request_end (nella scheda Eventi span). Sulla connessione, questa è la porzione in anteprima di quella sequenza, intercalata con gli altri eventi bufferizzati della connessione:
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": [...]}La riga event_delta si ripete una volta per ogni frammento di testo. Elabora ogni evento man mano che arriva:
event_start, annota l'id annunciato. Gli identificatori coincidono sempre: event_start.event.id, ogni event_delta.event_id e l'id dell'agent.message bufferizzato sono lo stesso valore.event_delta, aggiungi delta.content.text alla voce in (event_id, delta.index) e visualizza il testo progressivo. Il primo delta per un index crea quella voce.agent.message bufferizzato, abbinalo per id, scarta l'anteprima accumulata e visualizza invece il contenuto del messaggio.span.model_request_end, chiudi qualsiasi anteprima che non sia stata riconciliata dal suo evento bufferizzato. Non arriveranno altri delta per essa. Se il turno va in errore o viene interrotto, l'evento bufferizzato potrebbe non arrivare mai; span.model_request_end arriva comunque.Garanzie su cui si basa il pattern:
(event_id, index), fornisce un prefisso di content[index].text nell'evento bufferizzato (un prefisso, non necessariamente l'intero testo, perché i delta potrebbero essere scartati sotto carico).event_start per event_id, e l'evento bufferizzato è l'ultima cosa che quella connessione consegna per quell'id.# Snapshot di anteprima, indicizzati per id evento. accumulate_managed_agents_event accumula ogni
# event_start / event_delta in uno snapshot agent.message; l'evento
# agent.message bufferizzato lo sostituisce.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Attiva le anteprime agent.message su questa connessione
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":
# L'evento bufferizzato è il record: sostituisce e chiude l'anteprima
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":
# Non arriveranno altri delta. Chiudi ogni anteprima il cui
# evento bufferizzato non è mai arrivato.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
breakIn una sessione multiagente, ogni thread di sessione ha il proprio flusso di eventi su GET /v1/sessions/{session_id}/threads/{thread_id}/stream, e accetta lo stesso parametro event_deltas[] con gli stessi valori. Le anteprime sono limitate al thread per progettazione: una connessione mostra l'anteprima solo del thread che sta leggendo. Le anteprime di un thread figlio vengono consegnate sullo stream proprio di quel figlio e non vengono mai ripubblicate sullo stream a livello di sessione, le cui anteprime restano limitate al thread primario. Per osservare il testo di un subagente mentre il modello lo genera, apri lo stream del thread di quel subagente.
Il percorso dello stream del thread è facile da sbagliare: è /threads/{thread_id}/stream, non /events/stream (che esiste solo a livello di sessione), e non esiste un endpoint /threads/{thread_id}/events/stream.
Gli eventi di anteprima in sé non cambiano. event_start ed event_delta hanno la stessa forma su uno stream di thread e sullo stream a livello di sessione, e il pattern accumulare e riconciliare si applica così come descritto. L'unico adattamento riguarda la contabilità: esegui un'istanza di accumulatore per ogni connessione stream.
# Elenca i thread della sessione e scegli un figlio: i thread figli hanno un parent_thread_id
# non nullo, mentre il parent_thread_id del thread primario è 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'
)
# Lo stream del thread figlio accetta lo stesso parametro event_deltas[] dello
# stream di sessione. Codifica in percent-encoding le parentesi (%5B%5D) e metti l'URL tra virgolette.
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)
# L'evento bufferizzato è il record autorevole; visualizzane il contenuto.
printf '\n'
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
session.thread_status_idle)
break
;;
esac
done
exec {stream}<&-Il ciclo di lettura termina su session.thread_status_idle, l'evento emesso quando il turno del thread di sessione finisce e il thread diventa inattivo.
Le anteprime sono ottimizzate per la reattività. Sviluppa tenendo conto di questi vincoli:
agent.message bufferizzato arriva comunque completo. Non trattare mai un'anteprima accumulata come definitiva.agent.message che la tua anteprima stava attendendo. Non c'è modo di richiedere nuovamente i delta persi.agent.thinking solo start: Un'anteprima agent.thinking emette solo l'event_start come segnale che un blocco di pensiero è iniziato; non seguono eventi event_delta.event_start ed event_delta esistono solo sullo stream in tempo reale. Non compaiono nella cronologia degli eventi della sessione (GET /v1/sessions/{session_id}/events) né nella cronologia degli eventi di alcun thread di sessione.Se lo stream non si comporta come ti aspetti:
| Cosa vedi | Cosa significa |
|---|---|
Uno stream con eventi bufferizzati ma senza event_start o event_delta | La connessione che stai leggendo non ha aderito (event_deltas[] si applica per connessione, non per sessione), oppure il turno non ha mai toccato il thread che stai ricevendo in streaming. Le anteprime sono limitate al thread, quindi elenca i thread della sessione (GET /v1/sessions/{session_id}/threads) per trovare quale è stato eseguito. |
| Un 404 sull'URL dello stream | Il percorso o un ID è errato, oppure la richiesta non contiene alcun header beta managed-agents. Gli endpoint dei thread sono protetti da beta, quindi senza l'header non esistono. |
Un 400 che menziona event_deltas | Sono accettati solo agent.message e agent.thinking. |
Quando l'agente invoca uno strumento personalizzato:
agent.custom_tool_use contenente il nome dello strumento e l'input.session.status_idle contenente stop_reason: requires_action. Gli ID degli eventi bloccanti si trovano nell'array stop_reason.event_ids.user.custom_tool_result per ciascuno, passando l'ID dell'evento nel parametro custom_tool_use_id insieme al contenuto del risultato.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:
# Cerca l'evento di uso dello strumento personalizzato ed eseguilo
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Invia il risultato indietro
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 una policy di autorizzazione richiede una conferma prima dell'esecuzione di uno strumento:
agent.tool_use o agent.mcp_tool_use.session.status_idle contenente stop_reason: requires_action. Gli ID degli eventi bloccanti si trovano nell'array stop_reason.event_ids.user.tool_confirmation per ciascuno, passando l'ID dell'evento nel parametro tool_use_id. Imposta result su "allow" o "deny". Usa deny_message per spiegare un rifiuto.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:
# Approva la chiamata allo strumento in sospeso
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
breakLe sessioni persistono tra le interazioni. La cronologia della conversazione viene conservata a meno che la sessione non venga eliminata esplicitamente. Quando una sessione diventa inattiva, viene creato un checkpoint della sua sandbox, preservando l'intero stato della sandbox, inclusi il filesystem, i pacchetti installati e tutti i file creati dall'agente. Questo ti consente di riprendere in modo pulito dopo un periodo di inattività.
Per riprendere una sessione, inviale un evento user.message come di consueto:
# In produzione, passa l'ID memorizzato della sessione che vuoi riprendere.
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.
YAMLUna sessione creata con un budget si mette in pausa invece di spendere oltre il limite. Quando il costo di listino tracciato della sessione raggiunge il tetto, la piattaforma mette in pausa ogni thread prima della sua successiva richiesta al modello, e la sessione diventa inattiva con uno stop_reason pari a budget_reached anziché terminare. La richiesta che ha portato il totale oltre il tetto viene eseguita fino al completamento, quindi il list_cost riportato dallo snapshot session.usage può risultare pari o leggermente superiore al tetto. Sullo stream, la pausa arriva come tre eventi, in ordine:
session.thread_status_idle con stop_reason: budget_reached, per ogni thread man mano che si mette in pausa.session.usage, uno snapshot dell'utilizzo cumulativo della sessione e del costo di listino tracciato.session.status_idle con stop_reason: budget_reached. L'evento session.usage precede sempre immediatamente questo idle.Un thread la cui richiesta finale supera il tetto e al tempo stesso completa il proprio turno riporta end_turn sul proprio evento session.thread_status_idle mentre la sessione riporta comunque budget_reached; basati sullo stop_reason a livello di sessione per rilevare la pausa.
Mentre la sessione è al suo tetto, accetta solo gli eventi che concludono il lavoro già in corso: user.tool_confirmation, user.tool_result, user.custom_tool_result e user.interrupt. Qualsiasi evento che avvierebbe nuovo lavoro, incluso user.message, viene rifiutato con un errore 400 che elenca tali eventi. Quando una sessione ha sia un thread in attesa di una richiesta di strumento sia un thread in pausa al tetto, lo stop_reason a livello di sessione è requires_action, non budget_reached: concludere la richiesta non attiva una richiesta al modello, quindi rispondi come di consueto.
Nessun evento riprende una sessione in pausa al suo tetto. Aggiorna invece il budget della sessione: modificare il tetto a qualsiasi valore superiore al costo di listino consumato, oppure rimuovere il budget aggiornando la sessione con "budget": null, riprende automaticamente il lavoro in pausa. Consulta Budget delle sessioni per sapere come viene tracciato il costo di listino e per la semantica completa dell'aggiornamento del budget.
Invia un evento system.message per fornire all'agente un contesto privilegiato a livello di sistema che si applica al turno che lo accompagna e a tutti i turni successivi. A differenza del campo system nella definizione dell'agente (che imposta il prompt di sistema di livello superiore), il contenuto di system.message viene aggiunto al contesto di sistema della sessione come turno role: "system" anziché sostituire quel prompt. Usalo quando l'agente ha bisogno di indicazioni aggiornate a livello di sistema a metà sessione: una persona diversa, vincoli rivisti o contesto recuperato a runtime che dovrebbe modellare il comportamento del modello da quel momento in poi.
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."
YAMLMentre la sessione è inattiva con stop_reason: requires_action, un system.message viene accettato solo quando segue un evento di risultato di strumento nella stessa richiesta; inviato da solo o con un user.message, viene rifiutato finché gli eventi di strumento in sospeso non vengono risolti. content accetta da 1 a 1000 elementi di testo.
L'oggetto sessione include un campo usage con l'utilizzo cumulativo della sessione: conteggi dei token, uso degli strumenti lato server, tempo attivo e il costo di listino tracciato. Recupera la sessione dopo che è diventata inattiva per leggere i totali più recenti.
{
"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 riporta i token di input non in cache e output_tokens riporta i token di output totali su tutte le chiamate al modello nella sessione. Il campo cache_read_input_tokens riporta i token letti dalla cache dei prompt, e l'oggetto cache_creation suddivide i token di creazione della cache per durata della cache (ephemeral_5m_input_tokens e ephemeral_1h_input_tokens). Le voci della cache usano un TTL di 5 minuti per impostazione predefinita, quindi i turni consecutivi all'interno di quella finestra beneficiano delle letture dalla cache, che riducono il costo per token.
list_cost è il consumo cumulativo della sessione valutato alle tariffe di listino pubbliche, espresso come numero intero di centesimi in una stringa, con un codice valuta. active_seconds è il tempo cumulativo durante il quale la sessione aveva almeno un thread in esecuzione; l'attività sovrapposta di thread concorrenti viene conteggiata una sola volta, a differenza di active_seconds nell'oggetto stats della sessione, che somma il tempo attivo proprio di ciascun thread. Questo valore deduplicato è la durata su cui viene calcolato il costo di runtime della sessione. server_tool_use conta le richieste di strumenti eseguite lato server ai fini della tariffazione: le richieste di ricerca web sono incluse nel costo di listino per richiesta, mentre le richieste di web fetch non comportano alcun addebito per richiesta e non vengono misurate, quindi web_fetch_requests riporta 0. Anche il campo usage proprio di ciascun thread di sessione contiene list_cost e active_seconds. I valori per thread vengono arrotondati in modo indipendente ed escludono il costo del tempo di esecuzione della sessione, quindi la loro somma non corrisponde esattamente al list_cost della sessione; il valore della sessione è quello autorevole.
Non è necessario interrogare periodicamente la sessione per osservare questi totali. L'evento session.usage trasporta la stessa istantanea cumulativa (l'oggetto usage, più il budget della sessione, che è null quando la sessione non ne ha uno) sullo stream della sessione e nella cronologia degli eventi. Viene emesso nelle transizioni verso lo stato inattivo anziché a intervalli regolari: la sessione ne emette uno immediatamente prima di diventare inattiva, qualunque sia il motivo di arresto, e uno quando un thread si mette in pausa al raggiungimento di un budget di sessione. Un lettore dello stream vede quindi il costo finale di un turno, o del lavoro che ha raggiunto un budget, senza un recupero aggiuntivo.
Per imporre un limite di spesa, imposta un budget di sessione anziché interrogare l'utilizzo e arrestare la sessione manualmente. La piattaforma valuta continuamente il consumo della sessione e mette in pausa ciascun thread prima della sua successiva richiesta al modello una volta che il costo di listino della sessione raggiunge il limite; consulta Raggiungere un budget di sessione per vedere come appare sullo stream.
La Claude Console fornisce una vista a timeline visiva delle sessioni dei tuoi agenti. Vai alla sezione Claude Managed Agents nella Console per vedere:
session.errorWas this page helpful?