Managed Agentsの予算到達・アイドル再開・システムメッセージの実装パターン
Managed Agentsのセッションを止め、続け、割り込む3つの実践パターンをイベント順序付きで解説します。
Managed Agentsのセッションを止め、続け、割り込む3つの操作
Managed Agentsのセッションは、放置しても消えません。アイドル状態になったサンドボックスはチェックポイントとして保存され、あとから続きを送るだけで復帰します。予算(budget)を設定していれば、上限に達したセッションは終了せずに一時停止し、上限を上げるか外すだけで自動的に動き出します。会話の途中でも、system.message イベントでモデルへの指示を差し込めます。
この3つはどれも「セッションが今どの状態にあるかを、送れるイベントの種類で判断する」という作法でつながっています。状態を読み違えてuser.messageを送ると、予算で止まったセッションでは400エラーになります。上限の決め方はセッション予算でコストを上限管理するで扱っています。本記事は上限に達した後の再開、アイドル、システムメッセージの扱いに絞ります。
止まったセッションを前に進める判断の順番
セッションがsession.status_idleで止まったら、stop_reasonの種類で次に送るものが決まります。end_turnのように単純に手が空いた場合と、予算や承認待ちで止まった場合とで、受け付けられるイベントが違うためです。
idleの理由から次の一手を選ぶ
- 1
セッション全体の stop_reason を読む
スレッド単位の
stop_reasonではなく、session.status_idleに載っているセッションレベルの値を見ます。予算の上限をまたいで自分のターンを終えたスレッドは、自分のイベントではend_turnを返すことがあるためです。 - 2
requires_action ならツールの返答を先に送る
user.tool_confirmation/user.tool_result/user.custom_tool_resultのどれかで保留中の依頼に答えます。予算で止まった別スレッドがあっても、セッションの値はrequires_actionが優先されます。 - 3
budget_reached なら予算を更新する
再開用のイベントはありません。
usage.list_costより1セント以上高いmax_list_costに更新するか、budgetをnullにします。 - 4
それ以外のアイドルなら user.message を送る
履歴とサンドボックスの両方から続きが始まります。
アイドル再開 — サンドボックスは30日残り、通常のアイドルはuser.messageで続く
セッションは明示的に削除しない限り会話履歴を保持します。アイドルになった時点でサンドボックスの状態(ファイルシステム、インストール済みパッケージ、エージェントが作成したファイル)はチェックポイントされ、そこから再開できます。
保存しておいたセッションIDに向けて、通常どおりuser.messageイベントを送れば再開できます。
curl --fail-with-body -sS "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.message", "content": [{"type": "text", "text": "先ほどの変更に対してテストを実行して"}]}
]
}'なお、作業を終えたセッションはterminatedではなくidleになります。terminatedは回復不能なエラーか、アーカイブされたときの状態です。「終わったから終了済み」と読むと、再開できるセッションを捨ててしまいます。
予算到達 — budget_reachedはセッションを止めるが終了しない
予算を設定したセッションは、上限に達すると終了ではなく一時停止(idle)します。プラットフォームはモデルリクエストの直前に累積のlist cost(公開の定価で換算した消費額)を調べ、上限に届いていれば各スレッドを止めます。履歴とサンドボックスはほかのアイドルと同じように残ります。
上限を超える瞬間に実行中だったリクエストは、止められずに完走します。たとえば上限を50セントにしたセッションは、list costが53セントで止まることがあります。超過はスレッドあたり最大1リクエスト分です。予算は正確な停止点ではなく、新しい作業に対する上限と考えるのが安全です。
ストリーム上では、一時停止が次の3イベントの順で届きます。
| 順序 | イベント | 内容 |
|---|---|---|
| 1 | イベントsession.thread_status_idle | 内容stop_reason: budget_reached。停止した各スレッドごとに発火 |
| 2 | イベントsession.usage | 内容累積使用量とlist_costのスナップショット |
| 3 | イベントsession.status_idle | 内容stop_reason: budget_reached。直前に必ずsession.usageが来る |
一時停止中のセッションが受け付けるのは、進行中の作業を片付けるイベントだけです。
user.tool_confirmationuser.tool_resultuser.custom_tool_resultuser.interrupt
新しい作業を始めるイベント(user.messageを含む)は、この許可リストを名指しした400エラーで拒否されます。返答済みのツール結果は記録されますが、新しいモデルリクエストは起きず、セッションは止まったままです。ツールの確認待ちと予算到達が同時に起きているセッションは、budget_reachedではなくrequires_actionを報告します。確認待ちに答えてもモデルリクエストは発生しないため、通常どおり応答して構いません。
再開はイベントではなく、セッションの予算そのものの更新で行います。受理された更新は、止まっていた作業を自動的に再開します。
curl --fail-with-body -sS "https://api.anthropic.com/v1/sessions/$SESSION_ID" \
-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 '{"budget": {"type": "limit", "max_list_cost": {"amount": "500", "currency": "USD"}}}'新しい上限は、現在の上限より低くても構いません。条件は、セッションがすでに消費した額を超えていることです。満たさない更新はbudget.max_list_cost must be greater than the session's consumed list costという400エラーになります。
基準にするのは古いmax_list_costではなく、セッションが返すusage.list_costです。そこから1セント以上上乗せしてください。報告値は四捨五入されており、判定に使う正確な消費額より少し小さいことがあるためです。
予算を外すときは"budget": nullを送ります。作業は再開し、結果のsession.updatedイベントではbudgetがnullになります。
定価のないモデルが使用量に加わったセッションも、同じ形で止まることがあります。予算付きセッションの使用量に定価のないモデルが含まれると、予算の変更が拒否されます。この場合は予算を外して再開します。
max_list_costの単位と、list costの中身
セッション作成時に渡すbudget.max_list_cost.amountは、小数ではなく米セント単位の整数を文字列で指定します。"125"は1.25ドル、"50"は50セントです。"25.00"のような小数表記や0以下の値は拒否されます。文字列にしているのは、浮動小数点の丸めを持ち込まないためです。currencyはUSDのみで、typeは常に"limit"になります。作成時の渡し方はセッション予算の解説にあります。
上限と比べられるlist costは、トークンだけの合計ではありません。モデルのトークンに加えて、次の2つも定価で足し込まれます。
list costに含まれるもの
モデルトークン
各モデルの定価
使われたモデルごとに換算
Web検索
$10 / 1,000回
1回あたり1セント
セッションの稼働時間
$0.08 / 時間
active_seconds が換算の基準
組織が割引を交渉している場合、上限に届くのは定価の合計が届いた時点です。実際の請求額は上限より低くなることがあります。
判定には丸めない正確な額が使われますが、イベントやセッションに載るlist_costは1セント単位に四捨五入されます。表示値は正確な額から最大で0.5セントずれて見えます。
使用量を追う — usageフィールドとsession.usageイベント
セッションオブジェクトのusageフィールドには、トークン数・サーバーツール使用回数・アクティブ時間・追跡中のlist costが入っています。
{
"usage": {
"input_tokens": 5000,
"output_tokens": 3200,
"list_cost": { "amount": "187", "currency": "USD" },
"active_seconds": 342.5,
"server_tool_use": { "web_search_requests": 3, "web_fetch_requests": 0 }
}
}セッションレベルのactive_secondsは、複数スレッドが同時に動いていた時間を1回だけ数えます。スレッドごとのusageは個別に丸められ、セッションの稼働時間コストを含みません。そのため、スレッドの値を足してもセッション全体のlist_costとは一致しません。予算が照合されるのは、セッション側の値です。
web_fetch_requestsが常に0なのは、Web Fetchがリクエスト単位の課金対象ではなく、計測もされないためです。
ポーリングしなくても、session.usageイベントが同じスナップショットをストリームに流します。このイベントはタイマーではなく、セッションがアイドルへ遷移する直前に必ず1回発火します。理由がend_turnでもbudget_reachedでも変わりません。1ターン分のコストや、予算に当たるまでの作業の最終コストが、追加のフェッチなしで読めます。イベントには、そのセッションのbudgetの写し(予算なしならnull)も載ります。
支出の上限を強制したいときは、usageをポーリングして自前で止めるのではなく、予算を設定してプラットフォームに止めさせる設計にします。
複数エージェントとデプロイでの予算の効き方
予算は、作る単位によって効く範囲が変わります。
予算が効く範囲
マルチエージェントのセッション
予算は1つで、スレッドごとの上限はありません。各スレッドの消費はそのスレッドが使ったモデルの価格で換算されます。アドバイザーへの相談も同じ予算から引かれ、アドバイザーのモデルの価格で計算されます。
上限に近づくと、スレッドは別々のタイミングで止まります。あるスレッドが一時停止している間に、別のスレッドが実行中のリクエストを終えることもあります。
スケジュール実行のデプロイ
デプロイに設定した予算は、起動する各セッションにコピーされます。上限になるのは実行1回の消費で、デプロイ全体の累計ではありません。
デプロイの予算を変えても、すでに動いているセッションには及びません。セッションと違い、デプロイの予算はnullで消したあとに再設定できます。
システムメッセージ — 会話を止めずに指示を追加する
system.messageイベントは、エージェント定義のsystemフィールドとは別物です。systemフィールドは最初のシステムプロンプトを置き換えます。system.messageはセッションのシステムコンテキストにrole: "system"のターンとして追記され、同じターンとそれ以降のすべてのターンに効きます。ペルソナの変更、制約の追加、実行時に取得した情報の反映など、会話を止めずにモデルの振る舞いを変えたいときに使います。
curl --fail-with-body -sS "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": "system.message", "content": [{"type": "text", "text": "ユーザーの現在のタイムゾーンはAsia/Tokyo"}]}
]
}'requires_action(ツール確認待ち)で止まっているセッションでは、system.messageを単独で送れません。同じリクエストの中で、ツール結果イベントの直後に置いたときだけ受理されます。単独で送るかuser.messageと一緒に送ると、保留中のツールイベントが片付くまで拒否されます。contentは1〜1000個のテキスト項目を受け付けます。
セッション作成時のinitial_eventsには、system.messageを入れられません。セッションのinitial_eventsが受け付けるのは開始用のイベントだけで、スケジュール実行のデプロイのinitial_eventsとは扱いが違います。
3パターンをまたいで踏みやすい落とし穴
- 予算到達を
user.interruptで抜けようとする: 全スレッドが予算上限で止まっているセッションにuser.interruptを送ると、受理はされますが何も起きず、イベント履歴にも残りません。再開の経路は予算の変更かnullだけです - 割り込みの
stop_reasonに専用の値を期待する: 通常のセッションでuser.interruptを送ると、中断されたターンはsession.status_idleで終わります。そのstop_reasonは自然に終わったターンと同じend_turnで、中断専用の値はありません system.messageがサブエージェントにも届くと思い込む: マルチエージェント構成でも、反映されるのはプライマリスレッドだけです
まとめ
再開処理の核は、止まった理由を読んでから送るイベントを決めることです。予算が絡むセッションは、更新する金額の基準をusage.list_costに置いておけば、再開の実装は数行で収まります。
ツールの有効・無効設定はツール一覧と有効化・無効化の設定方法にあります。コードを書かずにセッションの状態を追うときは、Consoleのセッションビューアが使えます。画面構成はManaged AgentsをConsoleで可視化・デバッグする方法で扱っています。全体の設計はManaged Agentsの設計思想を参照してください。