[DRAFT] fix(credential-pool): harden multi-account pool against rate limits and subagent stampede - #5374
Closed
MestreY0d4-Uninter wants to merge 2 commits into
Closed
Conversation
added 2 commits
April 6, 2026 01:27
…nd subagent stampede Layer 1 — PooledCredential + dynamic cooldown + health scoring - Add fields: cooldown_until, last_retry_after_seconds, last_error_category, last_success_at, consecutive_failures, consecutive_429s, transient_error_count, structural_error_count - Add helpers: _is_structural_error(), _is_transient_error() - Add _entry_health_score() to CredentialPool for smart entry selection - Update _mark_exhausted() to accept retry_after_seconds and error_category - Update _available_entries() to check cooldown_until, last_error_reset_at, then fixed TTL - Update _select_unlocked() to sort by health score - Update mark_exhausted_and_rotate() signature (retry_after_seconds, error_category) - Add size() method - Add acquire_lease(), release_lease(), active_lease_count() methods Layer 1 — run_agent Retry-After extraction + failure classification - Add _extract_retry_after_seconds(): reads Retry-After, x-ratelimit-reset, etc. - Add _classify_api_failure(): categorizes errors into stable categories - Add _should_defer_fallback_to_credential_pool(): defer fallback when pool available - Update _recover_with_credential_pool() to propagate retry_after_seconds + error_category - Wire _should_defer_fallback_to_credential_pool() into the rate-limit error path Layer 2 — Subagent pool participation - Add _resolve_child_credential_pool() in delegate_tool.py - Call it in _build_child_agent() so children share the parent pool Layer 3 — Lease-based concurrency control - In _run_single_child(): acquire_lease() before run_conversation() - Release in finally block, bind child to leased credential via _swap_credential() Layer 4 — Task-tier delegation profiles - Add SUPPORTED_TIERS and resolve_tier_config() to delegate_tool.py - Add tier param to delegate_task() and per-task in batch schema - Add override_reasoning_effort to _build_child_agent() - Add reasoning_effort to _resolve_delegation_credentials() all return paths - Add delegation.reasoning_effort default to hermes_cli/config.py - Tier floor guardrails: review/planning >= high, heavy/research >= medium Layer 5 — Fallback precedence - Wire _should_defer_fallback_to_credential_pool() to replace inline pool check
…ssing delegation tests - Replace string-matching is_rate_limited with failure_category from _classify_api_failure (transient_rate_limit, quota_exhausted) - Add failure_category to error logging output - Add TestDelegationReasoningEffort (2 tests) - Add TestChildCredentialPoolResolution (7 tests) - Add TestChildCredentialLeasing (2 tests) - Add TestTierResolution (9 tests)
This was referenced Apr 6, 2026
Contributor
Author
|
Closing this draft in favor of smaller validated slices that were extracted and revalidated on clean worktrees against current origin/main:\n- #5576 child pool participation\n- #5580 child pool leasing\n- #5583 tiered delegation profiles\n\nThe remaining unsplit portion now overlaps too much with active upstream work (notably #5441 and issue threads #5449 / #5570) to justify keeping this large draft open in its current form. Keeping the branch as historical reference is fine, but the PR itself is now more confusing than helpful. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With 10+ accounts in the credential pool, the chance of all being rate-limited simultaneously is very low. However, the current code was not reaching those accounts in practice. This PR fixes the structural issues that caused the pool to underperform.
Root causes addressed
Subagents weren't seeing the pool — they inherited a single fixed credential from the parent. With 10 accounts available, a subagent knew only 1. If that account hit 429, it got stuck.
Parallel subagent stampede — batch delegation of 3 subagents caused all 3 to acquire the same "current" credential simultaneously, hitting 429 on one account while 9 others were idle.
Blind TTL cooldowns — on 429, credentials were blocked for 1 hour by default, even when the provider header said "retry in 15 seconds".
Fallback triggered too early —
fallback_providerscould activate before the pool's other credentials were tried.No health-aware selection — a credential with 5 consecutive 429s could be selected before a clean one.
What changed
Layer 1 — PooledCredential hardening + Retry-After extraction
cooldown_until,last_retry_after_seconds,last_error_category,last_success_at,consecutive_failures,consecutive_429s,transient_error_count,structural_error_count_entry_health_score(): scores entries by failure history, prefers healthier entries_available_entries(): checkscooldown_until(from Retry-After), thenlast_error_reset_at, then fixed TTL_extract_retry_after_seconds(): readsRetry-After,x-ratelimit-reset, etc. from API error headers_classify_api_failure(): categorizes errors into stable categories (transient_rate_limit,quota_exhausted,auth_structural, etc.)retry_after_seconds+error_categorypropagated through the full rotation chainLayer 2 — Subagent pool participation
_resolve_child_credential_pool(): same provider → share parent pool (synchronized state); different provider → load its own poolLayer 3 — Lease-based concurrency control
acquire_lease()/release_lease()/active_lease_count()onCredentialPoolDEFAULT_MAX_CONCURRENT_PER_CREDENTIAL = 2) — distributes parallel subagents across accountsrun_conversation(), released infinally(even on error/interrupt)acquire_lease()selects least-leased + healthiest entry, then rebinds child via_swap_credential()Layer 4 — Task-tier delegation profiles
SUPPORTED_TIERS:light,heavy,review,planning,researchresolve_tier_config(cfg, tier): merges flat config with tier overridesreview/planning≥high,heavy/research≥mediumtierfield indelegate_taskschema (top-level and per-task in batch)override_reasoning_effortin_build_child_agent()delegation.reasoning_effortadded to config defaultsLayer 5 — Fallback precedence
_should_defer_fallback_to_credential_pool(): returnsTruewhen pool still has available credentialsValidation
Result: 89 passed
Notes
cooldown_untilfrom Retry-After headers takes priority over fixed TTL and overlast_error_reset_at.