Managed Agentsの予算到達・アイドル再開・システムメッセージの実装パターン
Managed Agentsのセッションを止め、続け、割り込む3つの実践パターンをイベント順序付きで解説します。
Managed Agentsのセッションを止め、続け、割り込む3つの操作
Managed Agentsのセッションは、放置しても消えません。アイドル状態になったサンドボックスはチェックポイントとして保存され、あとから続きを送るだけで復帰します。予算(budget)を設定していれば、上限に達したセッションは終了せずに一時停止し、上限を上げるか外すだけで自動的に動き出します。そして会話の途中でも、system.message イベントでモデルへの指示をあとから差し込めます。
この3つは別々の機能に見えますが、実装上はどれも「セッションが今どの状態にあるかを、送れるイベントの種類で判断する」という共通の作法でつながっています。状態を誤認したままuser.messageを送ると400エラーになり、逆に正しい状態を把握していれば再開処理は数行で済みます。なお予算の設計・上限の決め方そのものはManaged Agentsのセッション予算(budget)でコストを上限管理するで扱っており、本記事は上限に達した後の再開・アイドル・システムメッセージによるセッション制御に絞ります。
アイドル再開 — サンドボックスは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": "先ほどの変更に対してテストを実行して"}]}
]
}'30日以内であれば、実装側が意識すべき分岐はほぼありません。ここでの実務上の要点は「保存期間はアクティビティで延長されない」という1点に尽きます。定期実行のジョブが29日目に1回動いても、期限は初回作成日から数えたままです。
予算到達 — budget_reachedはセッションを止めるが終了しない
セッション予算を設定していると、上限に達したセッションは終了ではなく一時停止(idle)します。追跡している累積コスト(list cost)が上限に届いた時点で、プラットフォームは次のモデルリクエストの前に各スレッドを止めます。上限を超えて開始済みのリクエストはそのまま完走するため、報告されるlist_costは上限ぴったりではなく、わずかに超過した値になります。
ストリーム上では、一時停止が次の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が来る |
判定のキーはセッションレベルのstop_reasonです。上限を超えつつ自分のターンを完走したスレッドは、自分のsession.thread_status_idleではend_turnを報告することがあります。個別スレッドのstop_reasonではなく、セッション全体のstop_reasonを見てbudget_reachedかどうかを判定してください。
一時停止中のセッションが受け付けるイベントは、進行中の作業を片付けるものだけです。
user.tool_confirmationuser.tool_resultuser.custom_tool_resultuser.interrupt
新しい作業を始めようとするイベント(user.messageを含む)は、この許可リストを名指しした400エラーで拒否されます。ツールの確認待ちと予算到達が同時に起きているセッションでは、セッションレベルのstop_reasonはbudget_reachedではなくrequires_actionになります。確認待ちを解消してもモデルリクエストは発生しないため、通常どおり応答して構いません。
予算到達したセッションを再開させるイベントは存在しません。再開するには、セッションの予算そのものを更新します。消費済みのlist_costより高い値にmax_list_costを変更するか、"budget": nullで予算そのものを外すと、止まっていた作業は自動的に再開します。
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"}}}'新しい上限は、古いmax_list_costではなくusage.list_cost(セッションが実際に消費した額)を基準にして、そこから最低1セント以上余裕を持たせて設定します。古い上限を基準にすると、超過分を含んだ消費額を下回ってしまい更新が拒否されるためです。
max_list_costの単位 — 米セント整数の文字列
セッション作成時に渡すbudget.max_list_cost.amountは、小数ではなく米セント単位の整数を文字列で指定します。"125"は1.25ドル、"25.00"のような小数表記は拒否されます。文字列にしているのは、浮動小数点の丸め誤差を計算のどこにも持ち込まないためです。currencyはUSDのみサポートされます。作成時のbudgetフィールドの渡し方や上限の決め方自体はManaged Agentsのセッション予算(budget)でコストを上限管理するを参照してください。
使用量を追う — 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はセッションレベルでは、複数スレッドが同時に動いていた時間を重複排除したうえで合算します(スレッドごとのactive_secondsは個別の実行時間をそのまま足すため、両者は一致しません)。web_fetch_requestsが常に0なのは、Web Fetchがリクエスト単位の課金対象になっていないためで、実装ミスではありません。
セッションをポーリングしなくても、session.usageイベントが同じスナップショットをストリーム上に流します。このイベントはタイマーではなく、セッションがアイドルへ遷移する直前に必ず1回発火します。予算の上限に達したときも、直前に必ず1回発火するため、ストリームを見ているだけで「その時点までにいくら使ったか」を追加のフェッチなしに把握できます。支出上限を強制したいなら、usageをポーリングして自前で止めるのではなく、予算そのものを設定してプラットフォーム側に止めさせるのが正しい設計です。
システムメッセージ — 会話を止めずに指示を追加する
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"}]}
]
}'もう1点、requires_action(ツール確認待ち)の状態にあるセッションでは、system.messageは単独では送れません。同じリクエストの中でツール結果イベントの直後に続けて送るときだけ受け付けられます。単独で送る、あるいはuser.messageと一緒に送ると、保留中のツールイベントが解消されるまで拒否されます。contentは1〜1000個のテキスト項目を受け付けます。
3パターンをまたいで踏みやすい落とし穴
- アイドルの理由を確認せず
user.messageを送る:requires_actionで止まっているセッションにuser.messageを送ると、保留中のツールイベントの解消より先に新しい会話を始めようとして拒否されます。まずstop_reasonを見てから次のイベントを決めます - 予算到達を
user.interruptで抜けようとする: 全スレッドが予算上限で止まっているセッションにuser.interruptを送っても、受理はされるものの何も起きません。イベント履歴にも残りません。予算を変更するかnullにするしか再開経路がありません - 古い上限を基準に予算を再設定する: 消費額は上限を超過した状態で止まるため、
max_list_costをそのまま据え置いても足りません。usage.list_costを読んでから上乗せします - サブエージェントに
system.messageが届くと思い込む: マルチエージェント構成でも、system.messageが反映されるのはプライマリスレッドだけです。ロースター内の個別エージェントへ届けたい指示は、別の経路(エージェント定義やツール結果)で渡します
まとめ
セッションが止まっている理由はstop_reasonが教えてくれます。budget_reachedならセッション自体のstop_reasonを見て予算を更新し、requires_actionならツール結果を先に解消し、それ以外の単純なアイドルならuser.messageを送るだけです。この判定を先に組み込んでおけば、再開処理そのものは複雑になりません。エージェントが使えるツール自体の有効・無効設定はManaged Agentsのツール一覧と有効化・無効化の設定方法、セッションの状態をコードを書かずに追うにはConsoleのセッションビューアが使えます。詳しい画面構成はManaged AgentsをConsoleで可視化・デバッグする方法で扱っています。設計思想の全体像はManaged Agentsの設計思想を参照してください。