Взаимодействие с Claude Managed Agents основано на событиях. Вы отправляете агенту пользовательские события и получаете в ответ события агента и сессии для отслеживания состояния.
События передаются в двух направлениях.
user.* запускают сессию и направляют её по мере выполнения; system.message добавляет контекст системного уровня, который применяется к сопутствующему ходу и всем последующим ходам.Строки типов событий сессии, span, агента, пользователя и системы следуют соглашению об именовании {domain}.{action}. Исключение составляют доступные только в потоке события предварительного просмотра дельт (event_start, event_delta). Полный каталог см. в разделе Типы событий справочника.
Каждое сохраняемое событие содержит временную метку processed_at, устанавливаемую по завершении обработки события. Для отправляемых вами событий processed_at равно null, пока событие всё ещё находится в очереди за более ранними событиями. Исключения — user.define_outcome, user.custom_tool_result и user.tool_result, которые обрабатываются при получении и возвращаются обратно с уже заполненным processed_at.
Отправьте событие user.message, чтобы начать или продолжить работу агента:
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",
},
],
},
],
)Отправьте событие user.interrupt, чтобы остановить агента в процессе выполнения, а затем событие user.message, чтобы перенаправить его:
# Агент в данный момент анализирует файл...
# Прерываем с новым направлением:
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.",
},
],
},
],
)Агент подтверждает прерывание и переключается на новую задачу. Прерванный ход завершается событием session.status_idle, у которого stop_reason равен end_turn — то же значение, что и у хода, завершившегося самостоятельно; специального значения stop reason для прерывания не существует.
По умолчанию текст ответа агента поступает в поток в виде буферизованных событий agent.message, каждое из которых отправляется только после завершения породившего его запроса к модели. «Event deltas» (дельты событий) позволяют отображать этот текст постепенно, в виде живого предварительного просмотра, пока модель ещё генерирует его. Предварительный просмотр — это не ответ: предварительные просмотры являются вспомогательным средством отображения, работающим по принципу «best effort», а буферизованное событие agent.message всегда остаётся авторитетной записью. Клиент, игнорирующий предварительные просмотры, по-прежнему получает полный и корректный поток.
Предварительные просмотры включаются отдельно для каждого потокового соединения. Добавьте параметр запроса event_deltas[] к потоку, который вы читаете, повторяя его по одному разу для каждого типа событий, который вы хотите просматривать предварительно. Поскольку [] является шаблоном подстановки (glob) оболочки, заключайте URL в кавычки всякий раз, когда формируете запрос в оболочке; в примерах скобки закодированы процентным кодированием как %5B%5D, что также работает. Параметр принимают обе конечные точки потока: поток уровня сессии по адресу GET /v1/sessions/{session_id}/events/stream и собственный поток каждого потока выполнения сессии (session thread) по адресу GET /v1/sessions/{session_id}/threads/{thread_id}/stream. Допустимые значения — agent.message и agent.thinking; любое другое значение возвращает ошибку 400, как и запрос с более чем 100 значениями. Предварительные просмотры субагента появляются в собственном потоке thread этого субагента.
Когда начинается просматриваемое событие, поток отправляет event_start, содержащий тип и id предстоящего события:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Для agent.message за началом следуют события event_delta, несущие инкрементальный текст. Каждая дельта указывает событие, которое она дополняет, в event_id, и блок содержимого, который она дополняет, в delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Когда предварительно просматривается событие agent.thinking, отправляется только event_start. События event_delta за ним не следуют, а буферизованное событие agent.thinking, завершающее предварительный просмотр, не содержит содержимого мышления; это сигнал прогресса, а не носитель содержимого.
В отличие от сохраняемых событий, event_start и event_delta не имеют собственных id или processed_at. Единственный идентификатор, который они несут, — это id события, которое они предварительно показывают.
Каждый SDK, поддерживающий дельты событий, включает вспомогательный аккумулятор, который ведёт учёт index за вас. Вспомогательные средства Go, Java, Ruby и C# также индексируют накапливаемый предварительный просмотр по id события; при использовании вспомогательных средств Python, TypeScript и PHP вы ведёте эту карту самостоятельно и добавляете каждую дельту в запись для её id. Ручной шаблон также работает на любом языке, когда вам нужен собственный учёт: применяйте его к сгенерированным типам событий.
В ручном шаблоне рассматривайте предварительный просмотр как временный буфер, а буферизованное событие — как запись. Индексируйте буфер по (event_id, index). Выполняйте согласование для каждого запроса к модели: ход открывается единственным событием session.status_running, затем в ходе, который завершается нормально, каждый запрос к модели порождает по порядку span.model_request_start, event_start, события event_delta, буферизованное agent.message и, наконец, span.model_request_end (на вкладке Span events). При передаче это просматриваемая часть данной последовательности, перемежающаяся с другими буферизованными событиями соединения:
event_start {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
event_delta {"event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "..."}}}
...
agent.message {"id": "sevt_01abc...", "content": [...]}Строка event_delta повторяется по одному разу для каждого фрагмента текста. Обрабатывайте каждое событие по мере поступления:
event_start запомните объявленный id. Идентификаторы всегда совпадают: event_start.event.id, каждый event_delta.event_id и id буферизованного agent.message — одно и то же значение.event_delta добавляйте delta.content.text к записи по ключу (event_id, delta.index) и отображайте накопленный текст. Первая дельта для index создаёт эту запись.agent.message, сопоставьте его по id, отбросьте накопленный предварительный просмотр и отобразите вместо него содержимое сообщения.span.model_request_end закройте любой предварительный просмотр, который не был согласован своим буферизованным событием. Больше дельт для него не поступит. Если ход завершается ошибкой или прерывается, буферизованное событие может никогда не прийти; span.model_request_end всё равно придёт.Гарантии, на которые опирается шаблон:
(event_id, index) даёт префикс content[index].text в буферизованном событии (префикс, а не обязательно весь текст, поскольку дельты могут отбрасываться под нагрузкой).event_start на event_id, а буферизованное событие — это последнее, что данное соединение доставляет для этого id.# Снимки предпросмотра, с ключом по id события. accumulate_managed_agents_event сворачивает каждое
# событие event_start / event_delta в снимок agent.message; буферизованное
# agent.message заменяет его.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Включаем предпросмотр agent.message для этого соединения
with client.beta.sessions.events.stream(
session.id, event_deltas=["agent.message"]
) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Describe the repo in one sentence."}],
},
],
)
for event in stream:
match event.type:
case "event_start":
snapshot = accumulate_managed_agents_event(None, event)
if snapshot is not None:
previews[event.event.id] = snapshot
print(f"event_start {event.event.type} {event.event.id}")
case "event_delta":
preview = accumulate_managed_agents_event(previews.get(event.event_id), event)
if preview is not None:
previews[event.event_id] = preview
text = "".join(block.text for block in preview.content)
print(f"event_delta preview: {text!r}")
case "agent.message":
# Буферизованное событие — это итоговая запись: оно заменяет и закрывает предпросмотр
preview = accumulate_managed_agents_event(previews.pop(event.id, None), event)
text = "".join(block.text for block in preview.content)
print(f"agent.message {event.id} {text!r}")
case "span.model_request_end":
# Дельт больше не будет. Закрываем все предпросмотры, чьё
# буферизованное событие так и не пришло.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
breakВ мультиагентной сессии каждый поток выполнения сессии (session thread) имеет собственный поток событий по адресу GET /v1/sessions/{session_id}/threads/{thread_id}/stream, и он принимает тот же параметр event_deltas[] с теми же значениями. Предварительные просмотры намеренно ограничены областью thread: соединение показывает предварительный просмотр только того thread, который оно читает. Предварительные просмотры дочернего thread доставляются в собственный поток этого дочернего thread и никогда не дублируются в поток уровня сессии, предварительные просмотры которого остаются ограниченными основным thread. Чтобы наблюдать за текстом субагента по мере его генерации моделью, откройте поток thread этого субагента.
В пути потока thread легко ошибиться: это /threads/{thread_id}/stream, а не /events/stream (который существует только на уровне сессии), и конечной точки /threads/{thread_id}/events/stream не существует.
Сами события предварительного просмотра не меняются. event_start и event_delta имеют ту же форму в потоке thread, что и в потоке уровня сессии, и шаблон накопления и согласования применяется в том виде, как он описан. Единственная поправка касается учёта: запускайте по одному экземпляру аккумулятора на каждое потоковое соединение.
# Получаем список тредов сессии и выбираем дочерний: у дочерних тредов parent_thread_id
# не равен null, а у основного треда parent_thread_id равен 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'
)
# Поток дочернего треда принимает тот же параметр event_deltas[], что и
# поток сессии. Закодируйте скобки в процентном виде (%5B%5D) и заключите 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)
# Буферизованное событие — авторитетная запись; отображайте его содержимое.
printf '\n'
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
session.thread_status_idle)
break
;;
esac
done
exec {stream}<&-Цикл чтения завершается при session.thread_status_idle — событии, отправляемом, когда ход потока выполнения сессии завершается и thread переходит в состояние ожидания.
Предварительные просмотры настроены на отзывчивость. Учитывайте при разработке следующие ограничения:
agent.message всё равно приходит полностью. Никогда не считайте накопленный предварительный просмотр окончательным.agent.message, которого ожидал ваш предварительный просмотр. Повторно запросить пропущенные дельты невозможно.agent.thinking только с началом: Предварительный просмотр agent.thinking отправляет только event_start как сигнал о том, что начался блок мышления; события event_delta за ним не следуют.event_start и event_delta существуют только в живом потоке. Они не появляются в истории событий сессии (GET /v1/sessions/{session_id}/events) или в истории событий какого-либо thread сессии.Если поток ведёт себя не так, как вы ожидаете:
| Вы видите | Что это означает |
|---|---|
Поток с буферизованными событиями, но без event_start или event_delta | Соединение, которое вы читаете, не включило эту возможность (event_deltas[] применяется к соединению, а не к сессии), или ход ни разу не затронул thread, который вы читаете в потоке. Предварительные просмотры ограничены областью thread, поэтому получите список thread сессии (GET /v1/sessions/{session_id}/threads), чтобы найти, какой из них выполнялся. |
| Ошибка 404 по URL потока | Неверен путь или идентификатор, либо запрос вообще не содержит бета-заголовка managed-agents. Конечные точки thread закрыты бета-доступом, поэтому без заголовка они не существуют. |
Ошибка 400 с упоминанием event_deltas | Принимаются только agent.message и agent.thinking. |
Когда агент вызывает пользовательский инструмент:
agent.custom_tool_use, содержащее имя инструмента и входные данные.session.status_idle, содержащим stop_reason: requires_action. Идентификаторы блокирующих событий находятся в массиве stop_reason.event_ids.user.custom_tool_result для каждого из них, передав идентификатор события в параметре custom_tool_use_id вместе с содержимым результата.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:
# Находим событие использования пользовательского инструмента и выполняем его
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Отправляем результат обратно
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":
breakКогда политика разрешений требует подтверждения перед выполнением инструмента:
agent.tool_use или agent.mcp_tool_use.session.status_idle, содержащим stop_reason: requires_action. Идентификаторы блокирующих событий находятся в массиве stop_reason.event_ids.user.tool_confirmation для каждого из них, передав идентификатор события в параметре tool_use_id. Установите result в "allow" или "deny". Используйте deny_message, чтобы объяснить отказ.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:
# Одобряем ожидающий вызов инструмента
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
breakСессии сохраняются между взаимодействиями. История разговора сохраняется, если сессия не удалена явно. Когда сессия переходит в состояние ожидания, для её песочницы создаётся контрольная точка, сохраняющая полное состояние песочницы, включая файловую систему, установленные пакеты и любые файлы, созданные агентом. Это позволяет корректно возобновить работу после периода неактивности.
Чтобы возобновить сессию, отправьте ей событие user.message как обычно:
# В продакшене передайте сохранённый ID сессии, которую хотите возобновить.
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.
YAMLСессия, созданная с бюджетом, приостанавливается вместо того, чтобы превысить расходы. Когда отслеживаемая стоимость сессии по прейскуранту достигает лимита, платформа приостанавливает каждый thread перед его следующим запросом к модели, и сессия переходит в состояние ожидания со значением stop_reason, равным budget_reached, а не завершается. Запрос, который вывел общую сумму за пределы лимита, выполняется до конца, поэтому list_cost, сообщаемый снимком session.usage, может показывать значение на уровне лимита или немного выше него. В потоке приостановка поступает в виде трёх событий по порядку:
session.thread_status_idle с stop_reason: budget_reached для каждого thread по мере его приостановки.session.usage — снимок совокупного использования сессии и отслеживаемой стоимости по прейскуранту.session.status_idle с stop_reason: budget_reached. Событие session.usage всегда непосредственно предшествует этому событию ожидания.Thread, чей последний запрос одновременно пересекает лимит и завершает свой ход, сообщает end_turn в собственном событии session.thread_status_idle, в то время как сессия по-прежнему сообщает budget_reached; для обнаружения приостановки ориентируйтесь на stop_reason уровня сессии.
Пока сессия находится на своём лимите, она принимает только события, завершающие уже выполняющуюся работу: user.tool_confirmation, user.tool_result, user.custom_tool_result и user.interrupt. Любое событие, которое начало бы новую работу, включая user.message, отклоняется с ошибкой 400, в которой перечислен этот список. Когда в сессии есть и thread, ожидающий ответа на запрос инструмента, и thread, приостановленный на лимите, stop_reason уровня сессии равен requires_action, а не budget_reached: ответ на запрос не вызывает запроса к модели, поэтому отвечайте на него как обычно.
Никакое событие не возобновляет сессию, приостановленную на лимите. Вместо этого обновите бюджет сессии: изменение лимита на любое значение выше израсходованной стоимости по прейскуранту или удаление бюджета путём обновления сессии с "budget": null автоматически возобновляет приостановленную работу. О том, как отслеживается стоимость по прейскуранту, и о полной семантике обновления бюджета см. в разделе Бюджеты сессий.
Отправьте событие system.message, чтобы предоставить агенту привилегированный контекст системного уровня, который применяется к сопутствующему ходу и всем последующим ходам. В отличие от поля system в определении агента (которое задаёт «system prompt» (системную подсказку) верхнего уровня), содержимое system.message добавляется к системному контексту сессии как ход с role: "system", а не заменяет эту подсказку. Используйте его, когда агенту требуются обновлённые указания системного уровня в середине сессии: другая персона, пересмотренные ограничения или контекст, полученный во время выполнения, который должен определять поведение модели в дальнейшем.
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."
YAMLПока сессия находится в состоянии ожидания с stop_reason: requires_action, system.message принимается только тогда, когда оно следует за событием результата инструмента в том же запросе; отправленное само по себе или вместе с user.message, оно отклоняется до тех пор, пока ожидающие события инструментов не будут разрешены. content принимает от 1 до 1000 текстовых элементов.
Объект сессии включает поле usage с совокупным использованием сессии: количество токенов, использование серверных инструментов, активное время и отслеживаемая стоимость по прейскуранту. Получите сессию после того, как она перейдёт в состояние простоя, чтобы прочитать последние итоговые значения.
{
"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 сообщает о некэшированных входных токенах, а output_tokens — об общем количестве выходных токенов по всем вызовам модели в сессии. Поле cache_read_input_tokens сообщает о токенах, прочитанных из кэша подсказок, а объект cache_creation разбивает токены создания кэша по времени жизни кэша (ephemeral_5m_input_tokens и ephemeral_1h_input_tokens). Записи кэша по умолчанию используют TTL в 5 минут, поэтому последовательные ходы в пределах этого окна выигрывают от чтений из кэша, которые снижают стоимость за токен.
list_cost — это совокупное потребление сессии, оценённое по публичным прейскурантным тарифам, в виде целого числа центов в строке, с кодом валюты. active_seconds — это совокупное время, в течение которого в сессии выполнялся хотя бы один поток; перекрывающаяся активность параллельных потоков учитывается один раз, в отличие от active_seconds в объекте stats сессии, который суммирует собственное активное время каждого потока. Это дедуплицированное значение — та длительность, по которой рассчитывается стоимость времени выполнения сессии. server_tool_use подсчитывает запросы к инструментам, выполняемым на сервере, для целей ценообразования: запросы веб-поиска включаются в стоимость по прейскуранту за каждый запрос, а запросы веб-загрузки (web fetch) не несут платы за запрос и не учитываются, поэтому web_fetch_requests показывает 0. Собственное поле usage каждого потока сессии также содержит list_cost и active_seconds. Значения по потокам округляются независимо и не включают стоимость времени работы сессии, поэтому их сумма не совпадает в точности с list_cost сессии; значение на уровне сессии является авторитетным.
Вам не нужно опрашивать сессию, чтобы наблюдать эти итоговые значения. Событие session.usage несёт тот же совокупный снимок (объект usage плюс budget сессии, который равен null, если у сессии его нет) в потоке сессии и в истории событий. Оно отправляется при переходах в состояние простоя, а не по таймеру: сессия отправляет одно событие непосредственно перед переходом в простой, независимо от причины остановки, и одно, когда поток приостанавливается при достижении бюджета сессии. Таким образом, читатель потока видит итоговую стоимость хода или работы, достигшей бюджета, без дополнительного запроса.
Чтобы обеспечить соблюдение лимита расходов, задайте бюджет сессии, вместо того чтобы опрашивать использование и останавливать сессию самостоятельно. Платформа непрерывно оценивает потребление сессии и приостанавливает каждый поток перед его следующим запросом к модели, как только стоимость сессии по прейскуранту достигает предела; см. раздел Достижение бюджета сессии, чтобы узнать, как это выглядит в потоке.
Claude Console предоставляет визуальное представление временной шкалы ваших агентских сессий. Перейдите в раздел Claude Managed Agents в Console, чтобы увидеть:
session.errorWas this page helpful?