feat: reset one pooled credential without clearing sibling cooldowns

This commit is contained in:
Brian Le
2026-09-07 02:08:04 -07:00
committed by Teknium
parent 12ad29a48a
commit 53221df05f
7 changed files with 38 additions and 10 deletions

View File

@@ -8,7 +8,24 @@ if TYPE_CHECKING:
from agent.credential_pool import PooledCredential
def _cleared_status_copy(entry: PooledCredential) -> PooledCredential:
from agent.credential_pool import _CLEAR_STATUS
return replace(entry, **_CLEAR_STATUS,
extra={k: v for k, v in entry.extra.items() if k != "failure_reason"})
class CredentialPoolAdminMixin:
def reset_status(self, credential_id: str) -> Optional[PooledCredential]:
"""Clear only the target's local error state, preserving sibling cooldowns."""
with self._lock:
entry = self._find(lambda e: e.id == credential_id)
if entry is None:
return None
cleared = _cleared_status_copy(entry)
self._replace_entry(entry, cleared)
self._persist(status_cleared_ids=[cleared.id])
return cleared
def reset_statuses(self) -> int:
"""Clear exhaustion state on every entry. Returns how many were cleared.
@@ -27,11 +44,7 @@ class CredentialPoolAdminMixin:
if stale:
stale_ids = {e.id for e in stale}
self._entries = [
replace(
e, **_CLEAR_STATUS,
extra={k: v for k, v in e.extra.items() if k != "failure_reason"},
)
if e.id in stale_ids else e
_cleared_status_copy(e) if e.id in stale_ids else e
for e in self._entries
]
self._persist(status_cleared_ids=list(stale_ids))

View File

@@ -73,7 +73,7 @@ Examples:
hermes auth add <provider> Add a pooled credential
hermes auth list List pooled credentials
hermes auth remove <p> <t> Remove pooled credential by index, id, or label
hermes auth reset <provider> Clear exhaustion status for a provider
hermes auth reset <p> [t] Clear exhaustion status for a provider, or one credential
hermes model Select default model
hermes fallback [list] Show fallback provider chain
hermes fallback add Add a fallback provider (same picker as `hermes model`)

View File

@@ -446,9 +446,19 @@ def auth_remove_command(args) -> None:
def auth_reset_command(args) -> None:
provider = _normalize_provider(getattr(args, "provider", ""))
target = getattr(args, "target", None)
pool = load_pool(provider)
count = pool.reset_statuses()
print(f"Reset status on {count} {provider} credentials")
if target is None or not str(target).strip():
count = pool.reset_statuses()
print(f"Reset status on {count} {provider} credentials")
return
index, matched, error = pool.resolve_target(target)
if matched is None or index is None:
raise SystemExit(f"{error} Provider: {provider}.")
cleared = pool.reset_status(matched.id)
if cleared is None:
raise SystemExit(f'No credential matching "{target}" for provider {provider}.')
print(f"Reset status on {provider} credential #{index} ({cleared.label})")
def auth_status_command(args) -> None:

View File

@@ -34,8 +34,11 @@ def build_auth_parser(subparsers, *, cmd_auth: Callable) -> None:
auth_remove.add_argument("provider", help="Provider id")
auth_remove.add_argument("target", help="Credential index, entry id, or exact label")
auth_reset = auth_subparsers.add_parser(
"reset", help="Clear exhaustion status for all credentials for a provider")
"reset", help="Clear exhaustion status for a provider's credentials (all, or one target)")
auth_reset.add_argument("provider", help="Provider id")
auth_reset.add_argument(
"target", nargs="?",
help="Optional credential index, entry id, or exact label; clears every credential when omitted")
auth_status = auth_subparsers.add_parser("status", help="Show auth status for a provider")
auth_status.add_argument("provider", help="Provider id")
auth_logout = auth_subparsers.add_parser(

View File

@@ -337,7 +337,7 @@ TIPS = [
"hermes curator pin <skill> hard-fences a skill against both auto-archival and the agent's skill_manage tool.",
'hermes curator rollback restores skills from a pre-run snapshot — backups live under skills/.curator_backups/.',
# --- Credential Pools & Routing ---
'hermes auth reset <provider> clears all cooldowns and exhaustion flags on a credential pool.',
'hermes auth reset <provider> [target] clears cooldowns and exhaustion flags on a whole credential pool, or on one credential by index, id, or label.',
'credential_pool_strategies.<provider>: round_robin cycles keys evenly instead of the fill_first default.',
'use_gateway: true per-tool routes web, image, tts, or browser through your Nous subscription — no extra keys.',
'provider_routing.data_collection: deny excludes data-storing providers on OpenRouter.',

View File

@@ -583,6 +583,7 @@ hermes auth add openrouter --api-key sk-or-v1-xxx # Add API key
hermes auth add anthropic --type oauth # Add OAuth credential
hermes auth remove openrouter 2 # Remove by index
hermes auth reset openrouter # Clear cooldowns
hermes auth reset openrouter 2 # Clear the cooldown on one credential
hermes auth status anthropic # Show auth status for a provider
hermes auth logout anthropic # Log out and clear stored auth state
hermes auth spotify # Authenticate Hermes with Spotify via PKCE

View File

@@ -118,6 +118,7 @@ Type [1/2]:
| `hermes auth add <provider> --type oauth` | Add an OAuth credential via browser login |
| `hermes auth remove <provider> <index>` | Remove credential by 1-based index |
| `hermes auth reset <provider>` | Clear all cooldowns/exhaustion status |
| `hermes auth reset <provider> <target>` | Clear the cooldown on one credential by index, id, or label |
## Rotation Strategies