CEOからの12件の技術質問に対する設計判断と、設計書への反映内容をまとめる。
判断: 同意。Phase 0を「Step 0: 実測検証」と「Step 1: 実装」の2段階に分割する。
指摘のとおり、rate limitがIP/組織単位だった場合、account_pool.py 全体が無意味になる。コードを書く前に粒度を確認すべき。
具体的な検証スクリプト:
claude -p "hello" --output-format stream-json を実行rateLimitType/utilization を両アカウントで比較/tmp/claude-{UID}/ の共有による干渉がないかを検証logs/rate_limit_granularity_test.json に記録判定基準:
判断: asyncio.Lockを使用する。
asyncio はシングルスレッドだが、acquire() → release() の間に await がある場合(=subprocess実行中)、別のコルーチンが acquire() を呼ぶ可能性がある。
正確に言うと、acquire() と release() それぞれは同期関数で await を含まないため、単独の呼び出しはアトミック。しかし acquire() 内の「スコア計算 → active_count += 1」が途中で割り込まれる可能性を構造的に排除するため、asyncio.Lock で保護する。
既存の ProcessRegistry は threading.Lock を使っている(スレッドセーフ)。AccountPool は asyncio 環境でのみ使われるため asyncio.Lock が適切。
_lock: asyncio.Lock を追加。acquire/release/mark_rate_limitedを async メソッドに変更。「3.2 AccountPool クラス」を更新済み判断: 指摘は正しい。active_countを永続化しない設計に変更する。
そもそも active_count は「現在実行中のプロセス数」であり、再起動後に意味を持たない。永続化すべきは cooldown_until(時刻ベースなので再起動後もTTLで自然消滅)のみ。
補正ロジック:
rate_limit_status.json から cooldown_until を復元。active_count は全アカウント 0 で初期化し、ProcessRegistryの実行中プロセスを列挙して各アカウントの active_count を再計算try/finally で release() を保証(既存設計どおり)。kill -9 の場合はプロセス消滅 → 次の acquire() 時に ProcessRegistry の実行中PIDと突き合わせて active_count を補正persist() 呼び出し時に ProcessRegistry.active_pids() と照合し、存在しないプロセスの active_count を減算リスク表を「低」→「中」に引き上げる。
判断: 定量試算は現時点では不可能だが、構造的にネット利益が出る根拠がある。
--resumeが失われるケース: rate limit発生時のリトライ(別アカウントでone-shot実行)のみ。通常のタスク実行では同一アカウント・同一セッションで --resume が維持される。
コスト構造:
Max Planの特性: トークン従量課金ではないため、--resume喪失による「トークン再消費」は財務コストに影響しない。影響するのは実行時間のみ(CLAUDE.md再読込で数秒〜十数秒)。
Phase 0 Step 0の実測で rate limit 頻度を定量化し、その結果で費用対効果を再評価する。
判断: 同意。acquire()にwaitオプションを追加する。
現設計では全アカウント制限時に None を返し、既存の RateLimitError → タスクリトライに委ねている。しかしタスクリトライは exponential backoff(30s→60s→120s)で固定の待機時間を使うため、クールダウン解除タイミングと合わない。
追加するロジック:
acquire(wait=True): 全アカウント制限中なら、最短の cooldown_until まで asyncio.sleep で待機してからリトライpool_cooldown_max(30分)。超えた場合は None を返すacquire(wait=False)(デフォルト): 現行どおり即座に None を返すrate limitリトライ時(別アカウント取得の場面)では wait=True を使い、最短復帰を待つ。
wait: bool = False パラメータを追加。「3.2 AccountPool クラス」を更新済み判断: total_runsをスコアリングから除外する。
指摘のとおり、目的は「rate limit回避」。total_runsで均等化しても、rate limit はリクエスト数ではなく利用率(5時間ウィンドウのトークン消費量)で計算されるため、均等化に寄与しない。
変更後のスコアリング:
float('inf')active_counttotal_runs フィールド自体を削除する。rate limit はトークン消費量ベースで計算されるため、実行回数を記録しても意味がない。
active_count のみに簡素化。「3.3 スコアリング」を更新済み判断: 5分はデフォルトフォールバック値であり、CLI提供値を優先する設計に明確化する。
「実績」は正確ではなかった。Claude Maxの rate limit は「5時間ウィンドウ」で管理されているため、5分待てば利用率が多少下がるという推論に基づく。厳密なサンプル数に基づく根拠ではない。
クールダウン値の決定ロジック(優先順):
rate_limit_info.retry_after(CLIが明示的に返す待機秒数)rate_limit_info.resetsAt(リセット時刻から算出)Phase 0での調整: Step 0の実測で実際のrate limit情報(retry_after, resetsAt)がどの程度返されるかを確認。返却率が高ければフォールバックは滅多に使われない。返却率が低ければフォールバック値を実測値に基づき調整する。
pool_cooldown_seconds は設定値(.env)で変更可能なので、運用中も調整可能。
判断: bot/ に配置する。理由は3つ。
bot/claude_runner.py のみ。他のパッケージ(ops/, agents/)からは参照されない。利用者と同じパッケージに置くのが自然bot/process_registry.py(プロセス管理)、bot/task_tracker.py(タスク管理)はいずれも「インフラ/リソース管理」の責務だが bot/ に配置されている。account_pool も同じカテゴリinfra/ や lib/ を切り出すほどのモジュール数がない(1ファイル)。一人法人のプロジェクトで過度なディレクトリ分割は保守コストが増える将来 AI Ops がリソース管理を統括する段階になれば、agents/ai-ops/ 配下への移動を検討する。現時点では YAGNI。
判断: ファイル分離が正しい。理由は以下。
config.py への追記(+6行)は既存ファイルへの追記。新規ファイルは account_pool.py の1本のみ。
判断: 直列挟み込み方式を採用する。サイドカー方式は現時点では過剰。
サイドカー方式の問題点:
AI Opsとの責務分離:
rate_limit_status.json(runner が書込み、AI Ops が読取)と同じパターンDAG並行実行アーキテクチャ(Issue #139)で bot.py が DAG Scheduler 化する段階で、リソース管理をScheduler内の独立モジュールとして再配置する可能性はある。しかしそれは Phase 0 のスコープ外。
判断: rate_limit_status.json に統合する。
指摘のとおり、アカウント別のcooldown状態と全体のrate limitイベントは密接に関連しており、分散させるメリットがない。
統合後の rate_limit_status.json 構造:
{
"cooldown_until": null, // 既存: 全体のクールダウン(単一アカウント時の後方互換)
"last_event": { ... }, // 既存: 最後のrate limitイベント
"history": [ ... ], // 既存: イベント履歴(24h TTL)
"accounts": [ // 新規: アカウント別の状態(active_countはメモリのみ)
{
"name": "account-a",
"cooldown_until": null,
"last_rate_limit": null
}
],
"updated_at": 1743984001.0
}
accounts が空配列 → 既存の cooldown_until / history のみで動作(後方互換)_write_rate_limit_status() を拡張して accounts を書き出す判断: 同意。「均等利用」を完了条件から削除する。
目的は rate limit 回避であり、均等分散はそのための手段の一つに過ぎない。active_count ベースのスコアリングは「負荷が偏りすぎない」程度の効果があれば十分で、厳密な均等化は不要。
更新後の完了条件:
CLAUDE_ACCOUNTS 未設定)でも既存動作に影響なし(Step 1)「3アカウントが均等に使用されている」は削除。total_runs の差が多少あっても、rate limit 回避機能が動作すればPhase 0は達成。
3つのClaude Maxアカウントを使い分け、rate limitの影響を最小化する仕組みを実装する。CLAUDE_CONFIG_DIR + キーチェーンによるアカウント分離は技術的に実証済み。
bot/account_pool.py(〜130行)を作成。claude_runner.py は30行追加のみrate_limit_status.json の拡張フィールドで永続化。再起動しても安全(active_countは永続化しない)run_claude() のインターフェースは変更なし。内部でアカウント選択を注入@dataclass
class AccountInfo:
name: str # 識別名(例: "account-a")
config_dir: str # CLAUDE_CONFIG_DIR のパス
active_count: int = 0 # 現在実行中のプロセス数(メモリのみ、永続化しない)
cooldown_until: float = 0 # rate limit クールダウン終了時刻(time.time()ベース、0=制限なし)
last_rate_limit: float = 0 # 最後にrate limit検知した時刻
class AccountPool:
"""Claude Maxアカウントのプール管理
active_countベースのスコアリングで負荷分散し、
rate limit発生時は該当アカウントをクールダウンして別アカウントに振り替える。
asyncio.Lock で並行アクセスを保護する(レビュー②反映)。
"""
def __init__(self, accounts: list[dict]) -> None:
"""アカウント一覧から初期化
accounts: [{"name": "account-a", "config_dir": "/path/to/.claude-a"}, ...]
"""
self._lock = asyncio.Lock() # レビュー②: 並行性制御
async def acquire(self, *, exclude: str | None = None, wait: bool = False) -> AccountInfo | None:
"""最小スコアのアカウントを取得し、active_count += 1
exclude: 除外するアカウント名(rate limit後のリトライ時)
wait: 全アカウント制限中の場合、最短復帰まで待機するか(レビュー⑤反映)
Returns: AccountInfo(利用可能なアカウントがなく wait=False なら None)
"""
async def release(self, name: str) -> None:
"""プロセス完了時に active_count -= 1"""
async def mark_rate_limited(self, name: str, cooldown_seconds: int = 300) -> None:
"""rate limit検知時にクールダウン期間を設定"""
def _score(self, account: AccountInfo) -> tuple[float, str]:
"""アカウントのスコアを計算(低い = 優先)
クールダウン中 → (float('inf'), name)
それ以外 → (active_count, name) # 同スコアは名前の辞書順で破壊
"""
def _reconcile_active_counts(self) -> None:
"""ProcessRegistryの実行中PIDと突き合わせてactive_countを補正(レビュー③反映)"""
def status(self) -> list[dict]:
"""全アカウントの現在状態を返す(ダッシュボード / AI Ops用)"""
def persist(self) -> None:
"""rate_limit_status.json の accounts フィールドに書出し(レビュー⑪反映)
書出し前に _reconcile_active_counts() を実行して補正する"""
def all_limited(self) -> bool:
"""全アカウントがクールダウン中かどうかを返す"""
@property
def enabled(self) -> bool:
"""アカウントが2つ以上設定されている場合にTrue"""
| 条件 | スコア | 説明 |
|---|---|---|
| クールダウン中 | (float('inf'), name) | rate limit中のアカウントは選択されない |
| 通常 | (active_count, name) | 実行中プロセスが少ないアカウントを優先 |
min(available_accounts, key=lambda a: (a.active_count, a.name))accounts フィールドを追加する。AI Ops が1ファイルだけで全状態を把握できる。
{
"cooldown_until": null, // 既存: 全体のクールダウン(単一アカウント時の後方互換)
"last_event": { ... }, // 既存: 最後のrate limitイベント
"history": [ ... ], // 既存: イベント履歴(24h TTL)
"accounts": [ // 新規: アカウント別の状態(active_countはメモリのみ、永続化しない)
{
"name": "account-a",
"cooldown_until": null,
"last_rate_limit": null
},
{
"name": "account-b",
"cooldown_until": 1743984000.0,
"last_rate_limit": 1743983700.0
},
{
"name": "account-c",
"cooldown_until": null,
"last_rate_limit": null
}
],
"updated_at": 1743984001.0
}
保存先: logs/rate_limit_status.json(既存ファイルの拡張)
active_count は永続化しない(レビュー③)。起動時は 0 初期化 → ProcessRegistry と突き合わせて再計算total_runs フィールドは廃止(レビュー⑥)。rate limit はトークン消費量ベースのため実行回数は無意味accounts が空配列 → 既存フィールドのみで動作(後方互換)config_dir は永続化しない(設定値なので .env から読めばよい)_run_claude_inner() 内で、subprocess起動直前にアカウントを取得し、完了時(正常/異常問わず)にリリースする。rate limit検知時は別アカウントでリトライする。
# _run_claude_inner() の変更イメージ(約30行追加)
from bot.account_pool import pool # グローバルインスタンス
async def _run_claude_inner(..., *, _account_name: str | None = None, ...):
# ① アカウント取得(asyncio.Lock で保護)
account = await pool.acquire(exclude=_account_name) if pool.enabled else None
# ② env構築時に CLAUDE_CONFIG_DIR を注入
env = {k: v for k, v in os.environ.items() if k not in _env_exclude}
if account:
env["CLAUDE_CONFIG_DIR"] = account.config_dir
try:
# ... 既存のsubprocess実行ロジック(変更なし) ...
pass
finally:
# ③ 必ずリリース
if account:
await pool.release(account.name)
# ④ RateLimitError 検知時: 別アカウントでリトライ
if is_rate_issue and not result_text.strip():
if account:
await pool.mark_rate_limited(account.name, cooldown_seconds=wait_seconds)
# 別アカウントで即リトライ(1回のみ)
if not _account_name: # 初回のみリトライ
return await _run_claude_inner(
..., _account_name=account.name, ...
)
raise RateLimitError(wait_seconds=wait_seconds)
run_claude() の引数・戻り値は一切変わらない。アカウント選択は内部で透過的に行われる。
内部関数 _run_claude_inner() に _account_name: str | None 引数を追加(プライベート、リトライ時の除外用)。
| ステップ | 処理 | 担当 |
|---|---|---|
| 1 | Claude CLI が rate_limit_event を出力 | Claude CLI |
| 2 | 既存パースロジックで rate_limited=True を検知 | claude_runner.py(既存) |
| 3 | pool.mark_rate_limited(name, wait_seconds) でクールダウン設定 | claude_runner.py(追加) |
| 4 | pool.release(name) で現アカウントを解放 | claude_runner.py(追加) |
| 5 | pool.acquire(exclude=name, wait=True) で別アカウントを取得 | claude_runner.py(追加) |
| 6a | 別アカウント取得成功 → _run_claude_inner() を再帰呼出 | claude_runner.py(追加) |
| 6b | 全アカウント制限中 → 最短復帰まで待機後リトライ。上限超過時は RateLimitError 送出 | claude_runner.py(追加) |
| パラメータ | 値 | 根拠 |
|---|---|---|
| クールダウン値の決定(優先順) |
1. retry_after(CLI提供)2. resetsAt(CLI提供、時刻から算出)3. フォールバック: 300秒(5分) |
CLI が待機秒数を返す場合はそれを信頼する。返さない場合のみフォールバック(レビュー⑦反映) |
| クールダウン上限 | 1800秒(30分) | 過大な値を防ぐ安全弁 |
| リトライ回数 | 1回(別アカウントで即リトライ) | 2回以上のリトライは全アカウント制限のリスク。既存の Task.retry_count と組み合わせて上限管理 |
| 全アカウント制限時 | 最短の cooldown_until まで待機 | 既存backoffの固定待機より効率的(レビュー⑤反映) |
retry_after/resetsAtを返さなかった場合のフォールバック。Phase 0 Step 0で実測し、CLI提供値の返却率を確認する。返却率が低ければフォールバック値を調整する。pool_cooldown_seconds は .env で変更可能。
_run_claude_inner 内で1回だけ。同一タスク実行内で即座に別アカウントに切り替えTask.retry_count で管理。タスク全体を再実行。exponential backoffRateLimitError が送出され、既存のタスクリトライが発動AI Ops Monitor(agents/ai-ops/monitor/watcher.py)に _check_pool_status() メソッドを追加。
async def _check_pool_status(self) -> None:
"""アカウントプールの状態を監視
チェック項目:
1. 全アカウントがクールダウン中 → CRITICAL通知
2. 1アカウントが長時間(15分超)クールダウン中 → WARNING通知
3. 特定アカウントのrate limit頻度が高い → INFO通知(偏り検出)
データソース: rate_limit_status.json の accounts フィールド(レビュー⑪反映)
"""
| 条件 | レベル | 通知先 | クールダウン |
|---|---|---|---|
| 全アカウントが同時にクールダウン中 | CRITICAL | #bot-info | 5分 |
| 1アカウントのクールダウンが15分超 | WARNING | #bot-info | 30分 |
| 1アカウントが1時間に3回以上rate limit | INFO | #bot-info | 1時間 |
既存の reader.py に read_pool_accounts() メソッドを追加。rate_limit_status.json の accounts フィールドを読取る。
# reader.py に追加
@dataclass(frozen=True)
class PoolAccountRecord:
name: str
cooldown_until: float | None
last_rate_limit: float | None
def read_pool_accounts(self) -> list[PoolAccountRecord]:
"""rate_limit_status.json の accounts フィールドからアカウント状態を読取"""
class Settings(BaseSettings):
# ... 既存設定 ...
# アカウントプール設定
# 形式: "name1:/path1,name2:/path2,name3:/path3"
claude_accounts: str = ""
# クールダウン秒数(デフォルト5分。CLIが値を返さない場合のフォールバック)
pool_cooldown_seconds: int = 300
# クールダウン上限(デフォルト30分)
pool_cooldown_max: int = 1800
# アカウントプール(カンマ区切り、各 "名前:config_dirパス")
CLAUDE_ACCOUNTS=account-a:/Users/mnmladmin/.claude-a,account-b:/Users/mnmladmin/.claude-b,account-c:/Users/mnmladmin/.claude-c
# クールダウン秒数(任意、デフォルト300。CLIがretry_afterを返さない場合のフォールバック)
# POOL_COOLDOWN_SECONDS=300
claude_accounts が空文字の場合、AccountPool.enabled = False となり、従来どおり単一アカウントで動作する(後方互換性)。
| ファイル | 操作 | 変更内容 | 行数目安 |
|---|---|---|---|
bot/account_pool.py |
新規 | AccountPool クラス、asyncio.Lock並行制御、active_count補正、rate_limit_status.json拡張書込み、グローバルインスタンス | 〜130行 |
bot/claude_runner.py |
改修 | _run_claude_inner() にアカウント取得/解放/切替リトライを追加 |
+30行 |
bot/config.py |
改修 | claude_accounts, pool_cooldown_seconds, pool_cooldown_max の3設定を追加 |
+6行 |
agents/ai-ops/monitor/watcher.py |
改修 | _check_pool_status() メソッドを追加(Phase 1) |
+40行 |
agents/ai-ops/monitor/reader.py |
改修 | PoolAccountRecord + read_pool_accounts() 追加(Phase 1) |
+20行 |
.env |
改修 | CLAUDE_ACCOUNTS 環境変数を追加 |
+2行 |
scripts/test_rate_limit_granularity.py |
新規 | Phase 0 Step 0: rate limit粒度の実測検証スクリプト(レビュー①反映) | 〜80行 |
| 項目 | リスク | 対策 |
|---|---|---|
| Rate limitの粒度 | 高 |
Anthropicの公開情報にrate limitがアカウント単位かIP/組織単位かの明記なし。 Phase 0 Step 0で実測検証を実施(レビュー①反映)。検証スクリプトで2アカウント並行実行し粒度を確認。 IP単位 → 本設計は凍結。アカウント単位 → Step 1に進む。 |
| CLAUDE_CONFIG_DIRの互換性 | 中 |
Claude CLI のバージョンアップで挙動が変わる可能性あり。 → 技術実証済み(Issue #329確認事項)。CLI更新時に動作確認を入れる |
| active_countの不整合 | 中 |
(レビュー③でリスクレベル引上げ) kill -9やbot再起動で release() がスキップされるケース。対策:
|
| セッション継続(--resume) | 中 |
セッションIDはアカウント固有。rate limitリトライで別アカウントに切り替えると --resume が使えない。→ リトライ時は新規セッションで実行(one-shot)。Max Planは月額固定のためトークン再消費の金銭コストは0。影響は再読込の数秒〜十数秒のみ(レビュー④) |
| キーチェーンアクセス | 低 |
3アカウント分のOAuthトークンがキーチェーンに格納されている前提。 → Phase 0 Step 0の検証スクリプトで各アカウントの claude auth status も確認
|
| 根拠の種類 | 内容 | 信頼度 |
|---|---|---|
| Anthropic公式(API) | 有料APIキーは「組織単位(Organization level)で制御」と明示。 出典: platform.claude.com/docs/en/api/rate-limits |
高(ただしMaxプランには適用外) |
| Anthropic公式(Max) | 同一アカウント内でclaude.aiとClaude Codeの使用量は共有。ただし別アカウントの独立性を保証する記述なし。 出典: support.claude.com |
高(独立性の保証ではない) |
| ccrotate(OSS) | アカウント別に ~/.claude/.credentials.json を管理し切り替えるツール。動作実績あり。CLAUDE_CONFIG_DIR 環境変数で並行運用する実装例もGitHubに存在 |
中(ToS違反リスクあり、Anthropic保証なし) |
| /api/oauth/usage | アカウントごとに five_hour.utilization を個別取得可能 |
中 |
| バグ報告 (#12786, #22876) | Account Aで制限到達後、Account Bに切替えてもrate limitエラーが出る報告あり。ユーザー仮説「デバイス/マシンレベルの追跡が存在」。Anthropic側の公式説明なし(Closedで終了) | 中(ネガティブ要素) |
| 直接的根拠 | Anthropicが「Maxプランはper-account制御」と明示した記述は存在しない | — |
scripts/test_rate_limit_granularity.py(〜80行)を作成し、以下を検証する。
claude auth status でキーチェーン状態を確認claude -p "hello" --output-format stream-json を実行rate_limit_event の rateLimitType/utilization を両アカウントで比較/tmp/claude-{UID}/ の共有による干渉がないか検証(同一UIDで複数 CLAUDE_CONFIG_DIR が共存できるか)retry_after / resetsAt をどの頻度で返すか記録logs/rate_limit_granularity_test.json に保存| 結果 | 判定 | 次のアクション |
|---|---|---|
| アカウント単位 | Go | Step 1(実装)に進む |
| IP/組織単位 | Stop | 本設計は凍結。IPローテーション等の代替策を別途検討 |
| 混合(アカウント+IP) | Go(限定的) | Step 1に進む。部分的改善として実装 |
| /tmp 干渉あり | 要対策 | UID分離(別ユーザ実行)またはCLAUDE_TMPDIR設定で回避後にStep 1 |
bot/account_pool.py 新規作成bot/claude_runner.py にアカウント選択ロジック組込みbot/config.py に設定追加.env に3アカウント設定AccountPool の acquire/release/mark_rate_limitedCLAUDE_ACCOUNTS 未設定)でも既存動作に影響なし(Step 1)CLAUDE_ACCOUNTS のパース: 不正な形式(区切り文字不正、パス不存在)は起動時にログ警告し、該当アカウントをスキップconfig_dir のパス: 絶対パスのみ許可。相対パスは拒否cooldown_seconds: 0〜pool_cooldown_max の範囲にクランプrate_limit_status.json にはトークン情報を含めない(アカウント名と状態のみ).env は .gitignore 済みrate_limit_status.json は既存のパターン(tmpfile → os.replace)で書出し| 案 | 概要 | 採用/却下 | 理由 |
|---|---|---|---|
| A. ラウンドロビン | 順番にアカウントを割り当て | 却下 | 負荷の偏りを考慮できない。長時間タスクがあると特定アカウントに偏る |
| B. active_countベース(採用) | active_count最小のアカウントを選択 | 採用 | 実装がシンプルかつ負荷分散に効果的。total_runsフィールドは廃止(レビュー⑥) |
| C. 重み付きスコア | active_count + rate_limit_history でスコア計算 | 却下 | Phase 0では過剰。rate limitの実測データが不足している段階で重みを調整するのは時期尚早 |
| D. 外部プロキシ | HAProxy等でCLI実行をプロキシ | 却下 | Claude CLIはsubprocess実行のため、HTTPプロキシは不適。アーキテクチャの複雑化 |
| E. サイドカー方式 | 独立プロセスが全CLI実行を監視・割当 | 却下 | IPC通信が必要で複雑さが桁違い。障害点の増加。現時点では直列挟み込みで十分(レビュー⑩) |
| F. pool_status.json 分離 | アカウント状態を専用ファイルに保存 | 却下 | rate_limit_status.json と情報が重複。AI Opsが2ファイル突き合わせ必要。統合がシンプル(レビュー⑪) |
CEOレビュー(12件)の質問と設計決定を一覧にまとめる。
| # | 優先度 | 質問要旨 | 設計決定 | 影響セクション |
|---|---|---|---|---|
| ① | 高 | コード実装前にrate limit粒度を実測すべき | Phase 0を Step 0(実測)→ Step 1(実装)に分割。scripts/test_rate_limit_granularity.py(〜80行)を作成。/tmp/claude-{UID}/ 干渉も検証 |
11.1, 11.3 |
| ② | 高 | acquire/releaseにasyncio.Lockが必要 | AccountPool._lock: asyncio.Lock を追加。acquire/release/mark_rate_limitedを async メソッドに変更 |
3.2 |
| ③ | 高 | active_countの永続化と不整合リスク | active_countはメモリのみ(永続化廃止)。起動時にProcessRegistryと突き合わせて再計算。リスクを「低」→「中」に引上げ | 3.1, 3.4, 10 |
| ④ | 中 | --resume削減効果の定量化 | 設計変更なし。Max Planは月額固定のためトークン再消費に金銭コスト0。影響は再読込の数秒〜十数秒のみ。Phase 0実測後に再評価 | 10 |
| ⑤ | 中 | 全アカウント制限時の待機ロジック | acquire(wait: bool = False) パラメータを追加。wait=Trueなら最短cooldown_until解除まで待機(上限30分) |
3.2, 4, 6.1 |
| ⑥ | 低 | スコアリングのtotal_runsは必要か | total_runs フィールドを完全削除。スコアリングは active_count のみ。同スコア時はアカウント名の辞書順 |
3.1, 3.3, 3.4 |
| ⑦ | 低 | クールダウン5分の根拠 | 5分はフォールバック値。CLI提供の retry_after/resetsAt を優先。Phase 0実測で調整 |
6.2 |
| ⑧ | — | account_pool.pyの配置場所 | bot/account_pool.py で確定。利用者がbot/内に閉じており、既存パターン(process_registry等)と一貫 |
—(変更なし) |
| ⑨ | — | ファイル分離の必要性 | 分離維持。claude_runner.pyが既に500行超。テスト容易性・責務分離の観点から別ファイルが適切 | —(変更なし) |
| ⑩ | — | 直列挟み込み vs サイドカー方式 | 直列挟み込み方式を確定。サイドカーはIPC基盤だけで100行超の追加コード。既存の _semaphore パターンと一貫 |
—(変更なし) |
| ⑪ | — | pool_status.jsonをrate_limit_status.jsonと分ける理由 | pool_status.json新規作成を撤回。既存の rate_limit_status.json に accounts 配列を追加して統合 |
3.4, 9 |
| ⑫ | — | Phase 0完了条件の「均等利用」は不要 | 「均等利用」を削除。完了条件を3項目に絞り込み: 実測データ取得、アカウント切替動作、後方互換性 | 11.3 |