認証情報プール
目次
認証情報プールを使うと、同じプロバイダに対して複数の API キーや OAuth トークンを登録しておけます。あるキーがレート制限や課金の上限に当たると、Hermes は自動で次の健全なキーへ切り替えます。プロバイダを乗り換えることなく、会話を続けられます。
これはフォールバックプロバイダとは別のしくみです。あちらは*まったく別の*プロバイダへ切り替えます。認証情報プールは同じプロバイダの中での切り替え、フォールバックプロバイダはプロバイダをまたぐ切り替えです。先に試されるのはプールのほうで、プールのキーを使い切って*はじめて*フォールバックプロバイダが動きます。
しくみ
Your request
→ Pick key from pool (round_robin / least_used / fill_first / random)
→ Send to provider
→ 429 rate limit?
→ Plan/usage limit reached (e.g. ChatGPT/Codex "usage limit reached")?
→ Rotate to next pool key immediately (no retry — the cap won't clear on retry)
→ Generic / transient 429?
→ Retry same key once (transient blip)
→ Second 429 → rotate to next pool key
→ All keys exhausted → fallback_model (different provider)
→ 402 billing error?
→ Immediately rotate to next pool key (1h cooldown)
→ 401 auth expired?
→ Try refreshing the token (OAuth)
→ Refresh failed → rotate to next pool key
→ 400 "model is not supported when using Codex with a ChatGPT account"?
→ Bench this key for that model only, rotate to the next key (other models stay usable)
→ Every key rejects the model → fallback_model; the model is skipped for the session
→ HTTP 200 but `response.status: failed` (ChatGPT/Codex reports usage limits this way)?
→ Same rules as above, keyed on the embedded error code/message:
quota/billing/auth → pool rotation first, provider fallback only once the pool is exhausted;
content-policy and other failures → no rotation
→ Success → continue normallyすぐに使い始める
.env にすでに API キーを書いてある場合、Hermes はそれを自動で見つけてキー 1 本のプールとして扱います。プールの利点を活かすには、キーを増やしてください。
# Add a second OpenRouter key
hermes auth add openrouter --api-key sk-or-v1-your-second-key
# ...or let a browser login mint one (OpenRouter OAuth PKCE; stored as a plain API key)
hermes auth add openrouter --type oauth
# Add a second Anthropic key
hermes auth add anthropic --type api-key --api-key sk-ant-api03-your-second-key
# Add an Anthropic OAuth credential (requires Claude Max plan + extra usage credits)
hermes auth add anthropic --type oauth
# Opens browser for OAuth loginプールの中身を確認します。
hermes auth list出力はこうなります。
openrouter (2 credentials):
#1 OPENROUTER_API_KEY api_key id=ab12cd34 priority=0 env:OPENROUTER_API_KEY ←
#2 backup-key api_key id=ef56gh78 priority=1 manual
anthropic (3 credentials):
#1 hermes_pkce oauth id=ab12cd34 priority=0 hermes_pkce ←
#2 claude_code oauth id=cd34ef56 priority=1 claude_code
#3 ANTHROPIC_API_KEY api_key id=ef56gh78 priority=2 env:ANTHROPIC_API_KEY← が現在選ばれている認証情報を指しています。id= は項目の ID で、ラベルだけではどれを指すか決められないときに hermes auth remove <provider> <target> へ渡せます。priority= は fill_first の方針のもとで プールが認証情報を試す順番です。
対話式の管理
サブコマンドなしで hermes auth を実行すると、対話式のウィザードが開きます。
hermes authプールの状態がひととおり表示され、次のメニューが出ます。
What would you like to do?
1. Add a credential
2. Remove a credential
3. Reset cooldowns for a provider
4. Set rotation strategy for a provider
5. ExitAPI キーと OAuth の両方に対応しているプロバイダ(Anthropic、Nous、Codex)では、追加のときにどちらかを尋ねられます。
anthropic supports both API keys and OAuth login.
1. API key (paste a key from the provider dashboard)
2. OAuth login (authenticate via browser)
Type [1/2]:hermes auth add openai-codex でログインするたびに、それぞれが別のプールの項目になります。ただし切り替えの効き目があるのは別々の OpenAI アカウントの場合だけです。同じアカウントで 2 回ログインすると、上流では 1 つのトークンの系統を共有することになるので、OpenAI は古いほうを取り消し、2 つ目の項目は枠を増やしません。Hermes は追加のときに警告を出します(warning: this login is the same OpenAI account as openai-codex credential #N)。別のアカウントでログインするか、1 つだけにしておいてください。
CLI のコマンド
| コマンド | 説明 |
|---|---|
hermes auth |
対話式のプール管理ウィザードを開きます |
hermes auth list |
すべてのプールと認証情報を表示します |
hermes auth list <provider> |
指定したプロバイダのプールを表示します |
hermes auth add <provider> |
認証情報を追加します(種類とキーを尋ねられます) |
hermes auth add <provider> --type api-key --api-key <key> |
対話なしで API キーを追加します |
hermes auth add <provider> --type oauth |
ブラウザでのログインを通して OAuth の認証情報を追加します |
hermes auth add openai-codex --browser |
Codex 専用。既定のデバイスコードの代わりに、localhost:1455 でブラウザによる認可コード + PKCE の流れでサインインします(デバイスコードの付与を禁じている組織向け)。ポートがふさがっているときはデバイスコードに戻ります。auth.codex_login_flow: browser を設定すると、Codex のログインすべてでこちらが既定になります |
hermes auth add <provider> --priority 0 |
認証情報を追加し、fill_first の順番で先頭に置きます |
hermes auth priority <provider> <target> <n> |
認証情報を優先度 n(0 が最初に試されます)へ移します。残りは番号を付け直します |
hermes auth remove <provider> <index> |
1 から数えた番号で認証情報を削除します |
hermes auth reset <provider> |
待機時間と使い切り扱いをすべて解除します |
hermes auth reset <provider> <target> |
番号・ID・ラベルのいずれかで、認証情報 1 つの待機時間を解除します |
hermes auth refresh <provider> [target] |
OAuth の認証情報 1 つのトークンを更新し、切り替えの輪に戻します(許可が生きていることを確かめる操作で、使える量は次のリクエストで改めて見直されます) |
Nous では auth refresh が使えるのは、ログインで作られる device_code の 1 つだけです。 別立ての Nous アカウントは更新の前に断られ、そのトークンと待機時間はそのまま保たれます。 1 つだけのものを更新したいときは hermes auth add nous --type oauth で認証し直します。これで別立てのアカウントが更新されるわけではありません。 他のプロバイダは、これまでどおりの取得元ごとの更新に対応します。
切り替えの方針
優先度の位置は 0 から数え、プールの端を超える値は端に丸められます。画面で指定する対象は 1 から数えた番号、項目の ID、または他と見分けられる完全一致のラベルです。auth add --priority は、 認証し直して更新された既存の項目を置くときにも使えます。Anthropic では手で追加した認証情報を 自動で取り込んだものより前に置くため、この規則で位置が変わったときは実際の位置が報告されます。他の方針では 優先度よりそちらが優先されることがあり、並べ直しても、すでに動いているセッションが持っている認証情報は割り当て直されません。
プールからの選択が成功するたびに、方針に関係なく request_count が 1 増えます。更新だけの参照や下見は数えません。 状態を見るだけの操作(hermes doctor、/model の選択画面に並ぶプロバイダの行、ダッシュボードの認証カード)は下見にあたります。 プールの認証情報を更新したり、切り替えたり、待機に入れたりすることは決してないので、選択画面を開いている間に トークンの受け口が一時的に不調になっても、まだリクエストをさばけているプロバイダが隠れてしまうことはありません。 これは選択の回数であって、課金の合計でも、推論リクエストをすべて数えたものでもありません。手元に保持された認証情報は 複数のリクエストをまかなえるからです。数は、次にプールへ書き込む機会(切り替え、使い切り、更新、管理上の変更など)まで メモリに置かれます。選択のたびにディスクへ書くことはありません。
hermes auth の「Set rotation strategy」から、または config.yaml で設定します。
credential_pool_strategies:
openrouter: round_robin
anthropic: least_used| 方針 | 動き |
|---|---|
fill_first(既定) |
最初の健全なキーを使い切るまで使い、それから次へ移ります。順番は各認証情報の priority で、hermes auth priority で変えられます |
round_robin |
選ぶたびに次のキーへ移り、均等に回します |
least_used |
常にリクエスト数が最も少ないキーを選びます |
random |
健全なキーの中からランダムに選びます |
健全な認証情報を後ろへ回す
プールが認証情報をいったん外すのは、プロバイダがそれを拒んだ*あと*です(429 / 402 / 401)。ほかの用途で現に使っているもの、たとえば週ごとの枠を対話的な作業のために取っておきたい Codex のログインなどを、使い切る*前*にゲートウェイの最初の選択肢から外しておきたいときは、削除するのではなく fill_first の順番の最後尾へ移します。
hermes auth list openai-codex # find the index, id or label
hermes auth priority openai-codex 1 99 # 1-based index, entry id, or exact label; large n = last
hermes auth priority openai-codex work-seat 0 # ...and back to the front laterhermes auth priority <provider> <target> <priority> は 1 つの認証情報の順番を入れ替え、残りに番号を振り直したうえで、新しい順番を auth.json に保存します。後ろへ回した項目は健全なままです。使い切った扱いにはならないので、Codex の枠の回復を調べる処理が触ることはなく、hermes auth reset が消すものもありません。時間で更新されることもなく、前に並ぶ認証情報がすべて外されれば、ちゃんと出番が来ます。 すでに認証情報を握っているセッションは、切り替わるまでそれを使い続けます。新しいセッション(および次のゲートウェイ起動)は新しい順番に従います。
エラーからの復帰
プールはエラーの種類ごとに動きを変えます。
| エラー | 動き | 待機時間 |
|---|---|---|
| 429 レート制限 | 同じキーで 1 回だけやり直します(一時的なものとみなす)。続けて 2 回目の 429 が出たら次のキーへ切り替えます | 1 時間 |
| 402 課金・上限 | ただちに次のキーへ切り替えます | 1 時間 |
| 401 認証切れ | まず OAuth トークンの更新を試みます。更新に失敗したときだけ切り替えます | 5 分 |
400 Codex のモデル利用資格(The '<model>' model is not supported when using Codex with a ChatGPT account.) |
拒まれたモデルについてだけこのキーを外し、次のキーへ切り替えます。ほかのモデルは同じキーを使い続けます。それ以外の 400 で切り替えが起きることはありません | hermes auth reset まで(モデルごと。利用資格は契約の性質であって、時間で開く枠ではありません) |
| キーをすべて使い切った | 設定してあれば fallback_model へ回します |
— |
プロバイダから reset_at の時刻が返ってきた場合は、上の既定の待機時間より優先されます。
has_retried_429 のフラグは API 呼び出しが成功するたびに戻るので、一時的な 429 が 1 回出ただけで切り替えが起きることはありません。
使用量による待機は、動いているセッションにとっても一時的なものです。 429 や 402 でセッションが ある認証情報から切り替わったとき、そのセッションは毎ターンの初めに、待機に入った認証情報が輪に戻って いないかを確かめ、待機時間が明けしだいそこへ戻ります。新しいセッションが選ぶのと同じものを選ぶわけです。 長く続いている対話(ゲートウェイはエージェントを手元に保持します)も、そのおかげで枠が開き次第、 使った分だけ支払う方の当てを一生使い続けるのではなく、定額の席へ戻ります。401 による待機ではこれは起きません。 /model で明示的に切り替えると、戻る予定は取り消されます。
Anthropic の 429 はモデルごとです。 Anthropic はレート制限をモデル単位でかけるため、ある Claude モデルで 一般的な 429 が返っても、その認証情報が待機に入るのは *そのモデルだけ* です。同じキーはほかの Claude モデルに これまでどおり使われ、ANTHROPIC_API_KEY や借りてきた Claude Code のトークンも、同じモデル単位の待機時間に 従います。請求まわり(402、利用上限)と認証(401)の失敗は、いままでどおり認証情報そのものを外します。
切れた OAuth のログインは、休ませるのではなく報告します。 更新用のトークンが完全に 拒まれたとき(invalid_grant、invalid_token、refresh_token_reused — トークンが取り消されたか、 同じログインを持つ別のプログラムが先に入れ替えた場合、あるいは Nous で、そのプロファイルに 更新のもとになる Portal のログインもトークンの組も無い場合)、プールはその項目の名前と直し方の コマンド(hermes auth add <provider>)を挙げた WARNING を 1 回だけ記録し、その認証情報は 切り替えの輪から外れます。dead の印が付くか、プールがちょうど消したトークンファイルを 写していただけなら丸ごと落とされ、もう一度サインインするまで戻りません。これは Anthropic、 Codex、xAI、Nous の OAuth ログインのいずれにも当てはまります。切れた認証情報が時間まかせで 輪に戻ることはないので、失われたログインは毎時間こっそり失敗し続けるのではなく、 記録に一度だけ姿を見せます。
常時稼働の環境で Codex のログインがすべて切れているときは、取り込みを待たずサインインし直してください。 Hermes が Codex CLI の ~/.codex/auth.json を自動で取り込むのは、*すでに持っているログインを直すため*だけです。つまり、自分の openai-codex の項目の 更新に失敗し、かつ auth.adopt_external_logins が有効なときです(借りてきた CLI のログインを参照)。 Codex の項目がすべて dead(あるいは削除済み)のプールには、直す相手が残っていません。そのため、画面のないゲートウェイや 定時実行のプロファイルは、*その*ホームで hermes auth add openai-codex(名前付きプロファイルなら hermes -p <profile> auth add openai-codex)を実行するまで、Codex の認証情報を持たないままになります。このコマンドは Codex CLI からの取り込みを対話的に提案します。1 つのログインを共有したいプロファイルは、1 回しか使えない 更新トークンをそれぞれ抱えるのではなく、同じ HERMES_HOME を指すようにできます。
待機中や切れている認証情報は、まっさらな導入とは違います。 設定済みのプロファイルが、たった 1 つの 認証情報が待機中か隔離中の状態で CLI を起動すると、起動時にその失敗が表示され、待機なら残りの待機時間が、 切れているなら hermes auth add <provider> でのログインし直しが併せて示されます。初回起動の 「No inference provider is configured yet」のウィザードが出るのは、解決の仕組みが設定を何も見つけられなかったときだけです。
独自エンドポイントのプール
OpenAI 互換の独自エンドポイント(Together.ai、RunPod、手元のサーバーなど)も、それぞれ独自のプールを持ちます。キーになるのは config.yaml の providers: 辞書に書いたエンドポイント名です(旧来の custom_providers リストも自動で移行されます)。
hermes model から独自エンドポイントを設定すると、「Together.ai」「Local (localhost:8080)」のような名前が自動で作られます。この名前がそのままプールのキーになります。
# After setting up a custom endpoint via hermes model:
hermes auth list
# Shows:
# Together.ai (1 credential):
# #1 config key api_key config:Together.ai ←
# Add a second key for the same endpoint:
hermes auth add Together.ai --api-key sk-together-second-key独自エンドポイントのプールは auth.json の credential_pool の下に、custom: を付けた形で保存されます。
{
"credential_pool": {
"openrouter": [...],
"custom:together.ai": [...]
}
}自動の読み取り
Hermes は起動時にいくつもの場所から認証情報を見つけ出し、プールの初期値にします。
| 取得元 | 例 | 自動で入る? |
|---|---|---|
| 環境変数 | OPENROUTER_API_KEY、ANTHROPIC_API_KEY |
はい |
| 番号付きの環境変数 | OPENROUTER_API_KEY_2、OPENROUTER_API_KEY_3、… |
はい(下を参照) |
| OAuth トークン(auth.json) | Codex のデバイスコード、Nous のデバイスコード | はい |
| Claude Code の認証情報 | ~/.claude/.credentials.json |
はい(Anthropic として) |
| Hermes の PKCE OAuth | ~/.hermes/auth.json |
はい(Anthropic として) |
| 独自エンドポイントの設定 | config.yaml の model.api_key |
はい(独自エンドポイントとして) |
| 手で追加したもの | hermes auth add で追加 |
auth.json に保存されます |
自動で入った項目はプールを読み込むたびに更新されます。環境変数を消せば、対応する項目も自動で取り除かれます。手で追加した項目(hermes auth add によるもの)が自動で消えることはありません。
環境変数から複数のキーを使う
1 つのプロバイダで複数のキーを使いたいけれど、どれも auth.json には保存したくない。そんなときは番号を付けます。NVIDIA_API_KEY の隣に NVIDIA_API_KEY_2、NVIDIA_API_KEY_3、… をシェル、.env、またはシークレット管理ツール(Bitwarden Secrets、Vault など)に設定すると、次に読み込んだときにそれぞれが別のプールの項目になります。コマンドも設定も要りません。番号が欠けたところで読み取りは止まるので、_4 がないのに _5 だけがあっても無視されます。credential_pool_strategies と組み合わせると順番に使い回せます。
credential_pool_strategies:
nvidia: round_robin外部から借りてくる実行時の秘密情報(環境変数、Bitwarden / Vault / keyring / systemd への参照、独自設定の値など)は、auth.json の側では参照だけを持ちます。Hermes はその実行の間だけ解決した値をメモリ上で使い、ディスクに残すのは取得元の参照、ラベル、状態、リクエストの回数、元に戻せない指紋といった情報だけです。手で追加した項目と、Hermes 自身が持つ OAuth・デバイスコードの状態については、更新に必要なトークンをそのまま保持します。
委譲とサブエージェントでの共有
エージェントが delegate_task でサブエージェントを起動すると、親の認証情報プールが子へ自動的に共有されます。
- 同じプロバイダのとき — 子は親のプールをそのまま受け取り、レート制限に当たったときにキーを切り替えられます
- 別のプロバイダのとき — 子はそのプロバイダ自身のプールを読み込みます(設定してあれば)
- プールがないとき — 子は受け継いだ 1 本の API キーだけで動きます
つまりサブエージェントも、追加の設定なしに親と同じだけレート制限に強くなります。タスクごとに認証情報を貸し出すので、子どうしが同時にキーを切り替えてもぶつかりません。
スレッド安全性
認証情報プールは、状態を変えるすべての操作(select()、mark_exhausted_and_rotate()、try_refresh_current()、mark_used())でスレッドのロックを使います。これにより、ゲートウェイが複数のチャットセッションを同時にさばいていても安全に扱えます。
プロセスをまたぐ場合(多数のサブエージェント、ゲートウェイと CLI の併用、定時実行など)は、auth.json のファイルロックによって OAuth の更新が順番に処理されます。共有された OAuth の許可が多数のプロセスの下で同時に期限切れになっても、更新を行うのはちょうど 1 つのプロセスだけです。残りはディスク上のトークンが失敗したものと違っていることに気づき、1 回しか使えないリフレッシュトークンを重ねて使うのではなく、新しいほうを採用します。ロックの取り合いに負けたプロセスは自分の項目を健全なまま保ってやり直します。ロックの競合が認証情報の失敗として記録されることはありません。プールの古い写しを持ったままのセッションやプロセスが、別のプロセスがその後に入れ替えたトークンの組を、自分の写しのトークンで上書きすることはありません。ディスク上の新しい組をそのまま残し、そちらを採用します。
構成
データの流れ全体の図は、リポジトリの docs/credential-pool-flow.excalidraw を参照してください。
認証情報プールは、プロバイダを解決する層に組み込まれています。
agent/credential_pool.py— プールの管理。保存、選択、切り替え、待機時間。agent/credential_pool_admin.pyが、ロックを取った上での対象の特定、解除、追加、削除、優先度の変更を受け持ちます。agent/credential_pool_model_cooldowns.pyが、Anthropic の 429 に対するモデルごとの待機時間を受け持ちますhermes_cli/auth_commands.py— CLI のコマンドと対話式ウィザードhermes_cli/runtime_provider.py— プールを踏まえた認証情報の解決agent/turn_api_error.py— エラーからの復帰。429 / 402 / 401 → プール内での切り替え → フォールバック
保存先
プールの状態は ~/.hermes/auth.json の credential_pool キーの下に保存されます。
{
"version": 1,
"credential_pool": {
"openrouter": [
{
"id": "abc123",
"label": "OPENROUTER_API_KEY",
"auth_type": "api_key",
"priority": 0,
"source": "env:OPENROUTER_API_KEY",
"secret_source": "bitwarden",
"secret_fingerprint": "sha256:12ab34cd56ef7890",
"last_status": "ok",
"request_count": 142
}
],
"anthropic": [
{
"id": "manual1",
"label": "personal-api-key",
"auth_type": "api_key",
"priority": 0,
"source": "manual",
"access_token": "sk-ant-api03-..."
}
]
}
}上の OpenRouter の項目は外部から借りてきたものなので、キーそのものは auth.json に入っていません。手で追加したほうの Anthropic の項目は Hermes の保管場所へ意図して登録したものなので、トークンをそのまま保存できます。
env: の行は読み込むたびに環境変数から値を入れ直します。変数名は、Hermes がそのプロバイダ向けに決めている名前でなくてもかまいません。番号付きの変数(OPENROUTER_API_KEY_2。自動の読み取りを参照)はここに自動で現れ、ほかの変数を指すように手で書いた行も同じように値が入ります。どちらの場合も、シークレットそのものが auth.json に書き込まれることはありません。
方針の設定は auth.json ではなく config.yaml に保存されます。
credential_pool_strategies:
openrouter: round_robin
anthropic: least_used