並列で5本走らせていたエージェントの3本目以降が、揃って中途半端な出力で止まったことがあります。
AI Ultra に上げてから、上限を気にせず並列でエージェントを回せるようになった、と思っていました。ところが朝にまとめて重い処理を投げた日、後半のエージェントだけが、途中まで動いてから明確なエラーも出さずに薄い結果を返しました。最初はモデルの調子かと思いましたが、時間帯を変えると再現しません。利用枠を短時間に使い切っていたのです。
やっかいなのは、この枯渇が「429 を返して止まる」という分かりやすい形では来ないことでした。私自身、エラーコードを待つ受け身の作りのままで、何度か後半の成果物を無駄にしました。上限が外から見えない前提で、自分側にブレーキを作る設計を、実運用の体感とあわせて順に組み立てていきます。
クォータ枯渇は明確なエラーとして来ない
レート制限というと、HTTP 429 が返ってリトライすれば直る、というイメージがあります。エージェント型のツールでは、必ずしもそうなりません。
実運用で観察した枯渇の現れ方は、おおむね次の3パターンでした。ひとつは途中での品質低下です。複数ステップの作業のうち、序盤は通常どおり進み、後半のステップだけ短い・浅い出力になります。ふたつ目は無音の打ち切りです。エージェントが「完了」と報告するのに、成果物が途中までしかありません。みっつ目は体感の鈍化で、応答が極端に遅くなり、タイムアウト境界で失敗します。
共通するのは、どれも「クォータを使い切った」という明示信号を伴わないことです。Ultra のような上位プランは上限が高い反面、残量がどれだけあるかが外から正確には見えません。だからこそ、エラーコードに頼った受け身の対処では遅いのです。枯渇を検知してから止めるのではなく、枯渇する前に自分で止める。これがサーキットブレーカーの発想です。
自分側で消費を推定して記録する
外から残量が見えないなら、自分が投入した量を自分で数えるしかありません。正確なトークン数は取れなくても、近似で十分です。私は「1エージェント実行 = 概算トークン」という粗い見積もりを、実行ごとにファイルに追記しています。
# record_usage.sh <weight>
# 1日の消費を概算トークンの重みで積算する(日付ごとにリセット)
DAY = "$( TZ = Asia/Tokyo date +%F)"
LEDGER = " $HOME /.agent_quota/${ DAY }.tally"
mkdir -p " $HOME /.agent_quota"
WEIGHT = " ${1 :- 1000} " # この実行の概算重み(重い生成ほど大きく)
echo " $WEIGHT " >> " $LEDGER "
TOTAL = "$( awk '{s+=$1} END{print s+0}' " $LEDGER ")"
echo "本日累計(概算): $TOTAL "
重みは厳密でなくてかまいません。軽いトリアージは 500、通常の記事生成は 3000、ブラウザサブエージェントを伴う重い処理は 8000、といった「相対的な目方」を決めておけば、1日の消費の山がどれくらいかを掴めます。日付を TZ=Asia/Tokyo で固定しているのは、日付境界がずれて前日の台帳を上書きする事故を避けるためです。
しきい値でエージェント投入を止める
台帳が貯まったら、新しいエージェントを投入する前に「今日の累計が1日予算を超えていないか」を検査します。超えていたら、その実行は走らせずに翌日へ送ります。これがブレーカーの本体です。
#!/usr/bin/env bash
# quota_breaker.sh <weight-of-this-run>
# 1日予算を超えていたら、このエージェントを起動しない
set -euo pipefail
DAY = "$( TZ = Asia/Tokyo date +%F)"
LEDGER = " $HOME /.agent_quota/${ DAY }.tally"
DAILY_BUDGET = 60000 # 1日の概算予算(実測で調整する)
SOFT_RATIO = 80 # この割合を超えたら重い処理だけ止める(%)
WEIGHT = " ${1 :- 3000} "
TOTAL = "$( awk '{s+=$1} END{print s+0}' " $LEDGER " 2> /dev/null || echo 0 )"
PROJECTED = $(( TOTAL + WEIGHT ))
if [ " $PROJECTED " -ge " $DAILY_BUDGET " ]; then
echo "⛔ ブレーカー作動: 予測 $PROJECTED ≥ 予算 $DAILY_BUDGET 。この実行は翌日に送ります。"
exit 75 # EX_TEMPFAIL: 一時的失敗(後で再試行してよい)
fi
SOFT_LINE = $(( DAILY_BUDGET * SOFT_RATIO / 100 ))
if [ " $TOTAL " -ge " $SOFT_LINE " ] && [ " $WEIGHT " -ge 5000 ]; then
echo "⚠️ ソフト制限帯( $TOTAL ≥ $SOFT_LINE )。重い処理は見送り、軽い処理のみ許可します。"
exit 75
fi
echo "✅ 投入許可(予測 $PROJECTED / 予算 $DAILY_BUDGET )"
二段構えにしているのがポイントです。予算を完全に超える手前に「ソフト制限帯」を設け、その帯に入ったら重い処理(ブラウザサブエージェントを伴う生成など)だけを先に止めます。軽いトリアージや小さな修正はまだ通します。こうすると、枯渇の直前に重い処理が枠を一気に食い尽くして、後続の軽い作業まで巻き添えにする、という最悪のパターンを避けられます。
終了コード75(EX_TEMPFAIL)を返しているのは、これが「恒久的な失敗」ではなく「いまは混んでいるので後で」という一時的な合図だからです。スケジューラ側はこれを見て、翌サイクルに自然に再投入できます。
重い処理と軽い処理を分けて投入順を決める
ブレーカーを入れたうえで、もう一段効くのが投入順の設計です。1日の予算は同じでも、どの順で投げるかで枯渇の起き方が変わります。
私自身は、朝の早い時間に重い生成を固め打ちするのをやめ、重い処理と軽い処理を交互に挟む運用に切り替えました。重い処理が予算の山を作る前に、軽い処理を先に通しておくことで、ソフト制限帯に入ったあとでも軽い作業だけは最後まで走り切れます。
アプリ開発で AdMob のレポート集計のような軽い定期処理と、記事や App Store 向け素材の重い生成を同じ枠で回している私の構成では、重い処理を1日3本までに制限し、各処理を約4時間間隔で配分したところ、後半の薄い出力がほぼ消えました。同じ本数でも、投入を時間で散らすだけで枯渇までの余裕が体感で1.5倍ほど伸びた印象です。
観測モードから本番運用へ移す手順
いきなり止める設定で本番投入すると、予算の見積もりが外れたときに必要な処理まで止めてしまいます。私が踏んでいる導入手順は次の3段階です。
まずブレーカーを「記録のみ・停止しない」観測モードで1〜2週間回し、後半のエージェントが薄くなり始める累計値を実測します。
その実測値の約80%を DAILY_BUDGET に設定し、ソフト制限帯(重い処理だけ止める帯)を先に有効化します。ここで止まるのは重い処理だけなので、軽い処理への巻き添えがありません。
1週間運用して必要な処理が不当に止まっていないことを確認してから、ハード制限(全処理停止)を有効化します。
この順で広げると、ブレーカーが過剰に効いて本来やるべき処理まで殺してしまう落とし穴を避けられます。予算は固定値ではなく、月ごとに実測へ寄せて見直すことをお勧めします。
1日のトークン予算をどう引くか
予算値(DAILY_BUDGET)は最初から正解を当てる必要はありません。1〜2週間、ブレーカーを「記録だけして止めない」観測モードで回し、後半のエージェントが薄くなり始める累計値を実測します。その値の少し手前を予算に設定すれば、枯渇に達する前にブレーキがかかります。
私の体感では、重い生成を朝に集中させると枯渇が早まり、4時間ほどの間隔で配分すると同じ本数でも枯渇しにくくなりました。これは台帳の累計が、時間あたりの上限ではなく1日の総量で効いていることを示しています。だからこそ、瞬間のレートを抑えるより、1日の総量を予算で締めるほうが効きます。
上限が見えない環境では、外からの信号を待つほど対処が後手に回ります。自分の消費を自分で数え、枯渇する前に自分で止める。この自衛の仕組みをひとつ挟むだけで、後半のエージェントが静かに薄くなる現象は驚くほど減ります。まずは観測モードの台帳から始めてみてください。