Komunikasi dengan Claude Managed Agents berbasis event. Anda mengirim event pengguna ke agen, dan menerima kembali event agen dan event sesi untuk melacak status.
Event mengalir dalam dua arah.
user.* memulai sesi dan mengarahkannya seiring berjalannya sesi; system.message menambahkan konteks tingkat sistem yang berlaku untuk giliran yang menyertainya dan semua giliran berikutnya.String jenis event sesi, span, agen, pengguna, dan sistem mengikuti konvensi penamaan {domain}.{action}. Event pratinjau delta khusus stream (event_start, event_delta) adalah pengecualiannya. Lihat Jenis event di referensi untuk katalog lengkapnya.
Setiap event yang dipersistensi menyertakan timestamp processed_at yang ditetapkan saat event selesai diproses. Pada event yang Anda kirim, processed_at bernilai null selama event masih mengantre di belakang event sebelumnya. Pengecualiannya adalah user.define_outcome, user.custom_tool_result, dan user.tool_result, yang diproses saat diterima dan dipantulkan kembali dengan processed_at yang sudah terisi.
Kirim event user.message untuk memulai atau melanjutkan pekerjaan agen:
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",
},
],
},
],
)Kirim event user.interrupt untuk menghentikan agen di tengah eksekusi, lalu lanjutkan dengan event user.message untuk mengarahkannya ulang:
# Agen sedang menganalisis sebuah file...
# Interupsi dengan arahan baru:
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.",
},
],
},
],
)Agen mengakui interupsi tersebut dan beralih ke tugas baru. Giliran yang diinterupsi berakhir dengan event session.status_idle yang stop_reason-nya adalah end_turn, nilai yang sama dengan giliran yang selesai dengan sendirinya; tidak ada stop reason khusus untuk interupsi.
Secara default, teks respons agen mencapai stream sebagai event agent.message yang di-buffer, masing-masing dipancarkan hanya setelah permintaan model yang menghasilkannya selesai. "Event deltas" (delta event) memungkinkan Anda merender teks tersebut secara bertahap, sebagai pratinjau langsung, selagi model masih menghasilkannya. Pratinjau bukanlah respons: pratinjau adalah alat bantu tampilan best-effort, dan agent.message yang di-buffer selalu menjadi catatan otoritatif. Klien yang mengabaikan pratinjau tetap menerima stream yang lengkap dan benar.
Pratinjau bersifat opt-in per koneksi stream. Tambahkan parameter query event_deltas[] ke stream yang Anda baca, ulangi sekali untuk setiap jenis event yang ingin Anda pratinjau. Karena [] adalah pola glob shell, beri tanda kutip pada URL setiap kali Anda menyusun permintaan di shell; contoh-contoh di sini melakukan percent-encode pada tanda kurung siku sebagai %5B%5D, yang juga berfungsi. Kedua endpoint stream menerima parameter ini: stream tingkat sesi di GET /v1/sessions/{session_id}/events/stream, dan stream milik setiap thread sesi di GET /v1/sessions/{session_id}/threads/{thread_id}/stream. Nilai yang diterima adalah agent.message dan agent.thinking; nilai lain apa pun mengembalikan error 400, begitu pula permintaan dengan lebih dari 100 nilai. Pratinjau subagen muncul di stream thread milik subagen tersebut.
Ketika event yang dipratinjau dimulai, stream memancarkan event_start yang membawa jenis dan id event yang akan datang:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Untuk agent.message, start tersebut diikuti oleh event event_delta yang membawa teks bertahap. Setiap delta menyebutkan event yang diperluasnya di event_id dan blok konten yang diperluasnya di delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Ketika event agent.thinking dipratinjau, hanya event_start yang dipancarkan. Tidak ada event event_delta yang mengikuti, dan event agent.thinking yang di-buffer yang mengakhiri pratinjau tidak membawa konten thinking; event ini adalah sinyal kemajuan, bukan pembawa konten.
Tidak seperti event yang dipersistensi, event_start dan event_delta tidak memiliki id atau processed_at sendiri. Satu-satunya pengenal yang dibawanya adalah id dari event yang dipratinjaunya.
Setiap SDK yang mendukung delta event menyertakan helper akumulator yang menangani pembukuan index untuk Anda. Helper Go, Java, Ruby, dan C# juga mengunci pratinjau yang sedang diakumulasi berdasarkan id event; dengan helper Python, TypeScript, dan PHP Anda menyimpan map tersebut sendiri dan menggabungkan setiap delta ke entri untuk id-nya. Pola manual juga berfungsi di setiap bahasa ketika Anda memerlukan pembukuan kustom: terapkan pola tersebut pada jenis event yang dihasilkan.
Dalam pola manual, perlakukan pratinjau sebagai buffer sementara dan event yang di-buffer sebagai catatannya. Kunci buffer berdasarkan (event_id, index). Rekonsiliasi per permintaan model: sebuah giliran dibuka dengan satu event session.status_running, lalu pada giliran yang selesai secara normal setiap permintaan model menghasilkan, secara berurutan, span.model_request_start, event_start, event-event event_delta, agent.message yang di-buffer, dan terakhir span.model_request_end (di tab Span events). Di wire, inilah bagian yang dipratinjau dari urutan tersebut, berselang-seling dengan event ter-buffer lain milik koneksi:
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": [...]}Baris event_delta berulang sekali per fragmen teks. Proses setiap event saat tiba:
event_start, catat id yang diumumkan. Pengenalnya selalu selaras: event_start.event.id, setiap event_delta.event_id, dan id milik agent.message yang di-buffer adalah nilai yang sama.event_delta, tambahkan delta.content.text ke entri di (event_id, delta.index) dan render teks berjalannya. Delta pertama untuk sebuah index membuat entri tersebut.agent.message yang di-buffer tiba, cocokkan berdasarkan id, buang pratinjau yang terakumulasi, dan render konten pesan tersebut sebagai gantinya.span.model_request_end, tutup pratinjau apa pun yang belum direkonsiliasi oleh event ter-buffer-nya. Tidak ada lagi delta yang akan datang untuknya. Jika giliran mengalami error atau diinterupsi, event yang di-buffer mungkin tidak pernah tiba; span.model_request_end tetap tiba.Jaminan yang diandalkan pola ini:
(event_id, index), menghasilkan prefiks dari content[index].text di event yang di-buffer (sebuah prefiks, belum tentu seluruh teks, karena delta mungkin dibuang saat beban tinggi).event_start per event_id, dan event yang di-buffer adalah hal terakhir yang dikirimkan koneksi tersebut untuk id itu.# Snapshot pratinjau, dengan kunci id event. accumulate_managed_agents_event melipat setiap
# event_start / event_delta menjadi snapshot agent.message; agent.message
# yang di-buffer akan menggantikannya.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Aktifkan pratinjau agent.message pada koneksi ini
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":
# Event yang di-buffer adalah catatan resminya: ia menggantikan dan menutup pratinjau
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":
# Tidak ada delta lagi yang akan datang. Tutup setiap pratinjau yang
# event buffer-nya tidak pernah tiba.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
breakDalam sesi multiagen, setiap thread sesi memiliki aliran event sendiri di GET /v1/sessions/{session_id}/threads/{thread_id}/stream, dan menerima parameter event_deltas[] yang sama dengan nilai yang sama. Pratinjau dibatasi per thread secara desain: sebuah koneksi hanya mempratinjau thread yang dibacanya. Pratinjau thread anak dikirimkan di stream milik anak tersebut dan tidak pernah diposting silang ke stream tingkat sesi, yang pratinjaunya tetap dibatasi pada thread utama. Untuk menyaksikan teks subagen saat model menghasilkannya, buka stream thread subagen tersebut.
Path stream thread mudah keliru: path-nya adalah /threads/{thread_id}/stream, bukan /events/stream (yang hanya ada di tingkat sesi), dan tidak ada endpoint /threads/{thread_id}/events/stream.
Event pratinjaunya sendiri tidak berubah. event_start dan event_delta memiliki bentuk yang sama di stream thread seperti di stream tingkat sesi, dan pola mengakumulasi dan merekonsiliasi berlaku sebagaimana tertulis. Satu-satunya penyesuaian adalah pembukuan: jalankan satu instance akumulator per koneksi stream.
# Tampilkan daftar thread sesi dan pilih satu anak: thread anak memiliki
# parent_thread_id yang tidak null, dan parent_thread_id thread utama bernilai 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'
)
# Stream thread anak menerima parameter event_deltas[] yang sama dengan
# stream sesi. Lakukan percent-encode pada tanda kurung (%5B%5D) dan beri tanda kutip pada URL.
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)
# Event yang di-buffer adalah catatan otoritatif; render kontennya.
printf '\n'
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
session.thread_status_idle)
break
;;
esac
done
exec {stream}<&-Loop pembacaan keluar pada session.thread_status_idle, event yang dipancarkan ketika giliran thread sesi selesai dan thread menjadi idle.
Pratinjau disetel untuk responsivitas. Bangun dengan memperhatikan batasan-batasan ini:
agent.message yang di-buffer tetap tiba secara lengkap. Jangan pernah memperlakukan pratinjau yang terakumulasi sebagai final.agent.message yang ditunggu pratinjau Anda. Tidak ada cara untuk meminta ulang delta yang terlewat.agent.thinking hanya start: Pratinjau agent.thinking hanya memancarkan event_start sebagai sinyal bahwa blok thinking telah dimulai; tidak ada event event_delta yang mengikutinya.event_start dan event_delta hanya ada di stream langsung. Keduanya tidak muncul di riwayat event sesi (GET /v1/sessions/{session_id}/events) atau di riwayat event thread sesi mana pun.Jika stream tidak berperilaku seperti yang Anda harapkan:
| Yang Anda lihat | Artinya |
|---|---|
Stream dengan event ter-buffer tetapi tanpa event_start atau event_delta | Koneksi yang Anda baca tidak memilih ikut serta (event_deltas[] berlaku per koneksi, bukan per sesi), atau giliran tersebut tidak pernah menyentuh thread yang Anda stream. Pratinjau dibatasi per thread, jadi daftarkan thread sesi (GET /v1/sessions/{session_id}/threads) untuk menemukan thread mana yang berjalan. |
| 404 pada URL stream | Path atau salah satu ID salah, atau permintaan tidak membawa header beta managed-agents sama sekali. Endpoint thread dibatasi beta, jadi tanpa header tersebut endpoint itu tidak ada. |
400 yang menyebut event_deltas | Hanya agent.message dan agent.thinking yang diterima. |
Ketika agen memanggil alat kustom:
agent.custom_tool_use yang berisi nama alat dan input.session.status_idle yang berisi stop_reason: requires_action. ID event yang memblokir ada di array stop_reason.event_ids.user.custom_tool_result untuk masing-masing, dengan memberikan ID event di parameter custom_tool_use_id beserta konten hasilnya.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:
# Cari event custom tool use dan jalankan
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Kirim hasilnya kembali
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":
breakKetika kebijakan izin memerlukan konfirmasi sebelum alat dijalankan:
agent.tool_use atau agent.mcp_tool_use.session.status_idle yang berisi stop_reason: requires_action. ID event yang memblokir ada di array stop_reason.event_ids.user.tool_confirmation untuk masing-masing, dengan memberikan ID event di parameter tool_use_id. Atur result ke "allow" atau "deny". Gunakan deny_message untuk menjelaskan penolakan.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:
# Setujui panggilan alat yang tertunda
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
breakSesi bertahan di antara interaksi. Riwayat percakapan dipertahankan kecuali sesi dihapus secara eksplisit. Ketika sesi menjadi idle, sandbox-nya di-checkpoint, mempertahankan status sandbox lengkap, termasuk filesystem, paket yang terinstal, dan file apa pun yang dibuat agen. Ini memungkinkan Anda melanjutkan dengan bersih setelah tidak aktif.
Untuk melanjutkan sesi, kirim event user.message ke sesi tersebut seperti biasa:
# Di produksi, berikan ID tersimpan dari sesi yang ingin Anda lanjutkan.
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.
YAMLSesi yang dibuat dengan anggaran berhenti sejenak alih-alih membelanjakan berlebih. Ketika biaya daftar (list cost) terlacak milik sesi mencapai batas, platform menghentikan sejenak setiap thread sebelum permintaan model berikutnya, dan sesi menjadi idle dengan stop_reason berupa budget_reached alih-alih dihentikan. Permintaan yang membawa total melewati batas berjalan hingga selesai, sehingga list_cost yang dilaporkan oleh snapshot session.usage dapat terbaca tepat di batas atau sedikit melewatinya. Di stream, jeda tersebut tiba sebagai tiga event, secara berurutan:
session.thread_status_idle dengan stop_reason: budget_reached, untuk setiap thread saat thread itu berhenti sejenak.session.usage, snapshot penggunaan kumulatif sesi dan biaya daftar terlacak.session.status_idle dengan stop_reason: budget_reached. Event session.usage selalu langsung mendahului idle ini.Thread yang permintaan terakhirnya sekaligus melewati batas dan menyelesaikan gilirannya melaporkan end_turn pada event session.thread_status_idle miliknya sendiri sementara sesi tetap melaporkan budget_reached; gunakan stop_reason tingkat sesi sebagai kunci untuk mendeteksi jeda.
Selagi sesi berada di batasnya, sesi hanya menerima event yang menyelesaikan pekerjaan yang sudah berjalan: user.tool_confirmation, user.tool_result, user.custom_tool_result, dan user.interrupt. Event apa pun yang akan memulai pekerjaan baru, termasuk user.message, ditolak dengan error 400 yang menyebutkan daftar tersebut. Ketika sesi memiliki thread yang menunggu permintaan alat sekaligus thread yang berhenti sejenak di batas, stop_reason tingkat sesi adalah requires_action, bukan budget_reached: menyelesaikan permintaan tersebut tidak memicu permintaan model, jadi tanggapi seperti biasa.
Tidak ada event yang melanjutkan sesi yang berhenti sejenak di batasnya. Sebagai gantinya, perbarui anggaran sesi: mengubah batas ke nilai apa pun di atas biaya daftar yang telah terpakai, atau menghapus anggaran dengan memperbarui sesi menggunakan "budget": null, akan melanjutkan pekerjaan yang terjeda secara otomatis. Lihat Anggaran sesi untuk cara biaya daftar dilacak dan semantik pembaruan anggaran selengkapnya.
Kirim event system.message untuk memberi agen konteks tingkat sistem yang diistimewakan yang berlaku untuk giliran yang menyertainya dan semua giliran berikutnya. Tidak seperti field system pada definisi agen (yang menetapkan prompt sistem tingkat atas), konten system.message ditambahkan ke konteks sistem sesi sebagai giliran role: "system" alih-alih menggantikan prompt tersebut. Gunakan ketika agen memerlukan panduan tingkat sistem yang diperbarui di tengah sesi: persona yang berbeda, batasan yang direvisi, atau konteks yang diambil saat runtime yang seharusnya membentuk perilaku model ke depannya.
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."
YAMLSelagi sesi idle dengan stop_reason: requires_action, system.message hanya diterima ketika mengikuti event hasil alat dalam permintaan yang sama; jika dikirim sendiri atau bersama user.message, event ini ditolak hingga event alat yang tertunda terselesaikan. content menerima 1–1000 item teks.
Objek sesi menyertakan field usage dengan penggunaan kumulatif sesi: jumlah token, penggunaan alat server, waktu aktif, dan biaya daftar yang dilacak. Ambil sesi setelah sesi menjadi idle untuk membaca total terbaru.
{
"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 melaporkan token input yang tidak di-cache dan output_tokens melaporkan total token output di seluruh panggilan model dalam sesi. Field cache_read_input_tokens melaporkan token yang dibaca dari cache prompt, dan objek cache_creation merinci token pembuatan cache berdasarkan masa berlaku cache (ephemeral_5m_input_tokens dan ephemeral_1h_input_tokens). Entri cache menggunakan TTL 5 menit secara default, sehingga giliran yang berurutan dalam jendela waktu tersebut mendapat manfaat dari pembacaan cache, yang mengurangi biaya per token.
list_cost adalah konsumsi kumulatif sesi yang dihargai berdasarkan tarif daftar publik, sebagai bilangan bulat sen dalam bentuk string, dengan kode mata uang. active_seconds adalah waktu kumulatif selama sesi memiliki setidaknya satu thread yang berjalan; aktivitas yang tumpang tindih dari thread konkuren dihitung sekali, berbeda dengan active_seconds dalam objek stats sesi, yang menjumlahkan waktu aktif masing-masing thread. Angka yang telah dideduplikasi ini adalah durasi yang menjadi dasar penetapan harga biaya runtime sesi. server_tool_use menghitung permintaan alat yang dieksekusi server untuk penetapan harga: permintaan web search dihargai ke dalam biaya daftar per permintaan, dan permintaan web fetch tidak dikenai biaya per permintaan dan tidak diukur, sehingga web_fetch_requests bernilai 0. usage milik setiap thread sesi juga memuat list_cost dan active_seconds. Angka per thread dibulatkan secara independen dan tidak mencakup biaya waktu berjalan sesi, sehingga jumlahnya tidak persis sama dengan list_cost sesi; angka sesi adalah angka yang otoritatif.
Anda tidak perlu melakukan polling pada sesi untuk mengamati total ini. Event session.usage membawa snapshot kumulatif yang sama (objek usage, ditambah budget sesi, yang bernilai null ketika sesi tidak memilikinya) pada stream sesi dan dalam riwayat event. Event ini dipancarkan pada transisi idle, bukan berdasarkan timer: sesi memancarkan satu event tepat sebelum menjadi idle, apa pun alasan berhentinya, dan satu event ketika sebuah thread dijeda pada anggaran sesi. Oleh karena itu, pembaca stream melihat biaya akhir dari suatu giliran, atau dari pekerjaan yang mencapai anggaran, tanpa pengambilan tambahan.
Untuk menerapkan batas pengeluaran, tetapkan anggaran sesi alih-alih melakukan polling penggunaan dan menghentikan sesi sendiri. Platform menghargai konsumsi sesi secara terus-menerus dan menjeda setiap thread sebelum permintaan model berikutnya setelah biaya daftar sesi mencapai batas; lihat Mencapai anggaran sesi untuk melihat tampilannya pada stream.
Claude Console menyediakan tampilan timeline visual dari sesi agen Anda. Buka bagian Claude Managed Agents di Console untuk melihat:
session.errorWas this page helpful?