mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(delegate): share credential pools with subagents + per-task leasing
Cherry-picked from PR #5580 by MestreY0d4-Uninter. - Share parent's credential pool with child agents for key rotation - Leasing layer spreads parallel children across keys (least-loaded) - Thread-safe acquire_lease/release_lease in CredentialPool - Reverted sneaked-in tool-name restoration change (kept original getattr + isinstance guard pattern)
This commit is contained in:
parent
8dee82ea1e
commit
f2c11ff30c
4 changed files with 308 additions and 3 deletions
|
|
@ -279,6 +279,12 @@ def _build_child_agent(
|
|||
# Set delegation depth so children can't spawn grandchildren
|
||||
child._delegate_depth = getattr(parent_agent, '_delegate_depth', 0) + 1
|
||||
|
||||
# Share a credential pool with the child when possible so subagents can
|
||||
# rotate credentials on rate limits instead of getting pinned to one key.
|
||||
child_pool = _resolve_child_credential_pool(effective_provider, parent_agent)
|
||||
if child_pool is not None:
|
||||
child._credential_pool = child_pool
|
||||
|
||||
# Register child for interrupt propagation
|
||||
if hasattr(parent_agent, '_active_children'):
|
||||
lock = getattr(parent_agent, '_active_children_lock', None)
|
||||
|
|
@ -312,6 +318,18 @@ def _run_single_child(
|
|||
_saved_tool_names = getattr(child, "_delegate_saved_tool_names",
|
||||
list(model_tools._last_resolved_tool_names))
|
||||
|
||||
child_pool = getattr(child, '_credential_pool', None)
|
||||
leased_cred_id = None
|
||||
if child_pool is not None:
|
||||
leased_cred_id = child_pool.acquire_lease()
|
||||
if leased_cred_id is not None:
|
||||
try:
|
||||
leased_entry = child_pool.current()
|
||||
if leased_entry is not None and hasattr(child, '_swap_credential'):
|
||||
child._swap_credential(leased_entry)
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to bind child to leased credential: %s", exc)
|
||||
|
||||
try:
|
||||
result = child.run_conversation(user_message=goal)
|
||||
|
||||
|
|
@ -422,6 +440,12 @@ def _run_single_child(
|
|||
}
|
||||
|
||||
finally:
|
||||
if child_pool is not None and leased_cred_id is not None:
|
||||
try:
|
||||
child_pool.release_lease(leased_cred_id)
|
||||
except Exception as exc:
|
||||
logger.debug("Failed to release credential lease: %s", exc)
|
||||
|
||||
# Restore the parent's tool names so the process-global is correct
|
||||
# for any subsequent execute_code calls or other consumers.
|
||||
import model_tools
|
||||
|
|
@ -430,6 +454,8 @@ def _run_single_child(
|
|||
if isinstance(saved_tool_names, list):
|
||||
model_tools._last_resolved_tool_names = list(saved_tool_names)
|
||||
|
||||
# Remove child from active tracking
|
||||
|
||||
# Unregister child from interrupt propagation
|
||||
if hasattr(parent_agent, '_active_children'):
|
||||
try:
|
||||
|
|
@ -626,6 +652,38 @@ def delegate_task(
|
|||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
def _resolve_child_credential_pool(effective_provider: Optional[str], parent_agent):
|
||||
"""Resolve a credential pool for the child agent.
|
||||
|
||||
Rules:
|
||||
1. Same provider as the parent -> share the parent's pool so cooldown state
|
||||
and rotation stay synchronized.
|
||||
2. Different provider -> try to load that provider's own pool.
|
||||
3. No pool available -> return None and let the child keep the inherited
|
||||
fixed credential behavior.
|
||||
"""
|
||||
if not effective_provider:
|
||||
return getattr(parent_agent, "_credential_pool", None)
|
||||
|
||||
parent_provider = getattr(parent_agent, "provider", None) or ""
|
||||
parent_pool = getattr(parent_agent, "_credential_pool", None)
|
||||
if parent_pool is not None and effective_provider == parent_provider:
|
||||
return parent_pool
|
||||
|
||||
try:
|
||||
from agent.credential_pool import load_pool
|
||||
pool = load_pool(effective_provider)
|
||||
if pool is not None and pool.has_credentials():
|
||||
return pool
|
||||
except Exception as exc:
|
||||
logger.debug(
|
||||
"Could not load credential pool for child provider '%s': %s",
|
||||
effective_provider,
|
||||
exc,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:
|
||||
"""Resolve credentials for subagent delegation.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue