-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Bug Description
Subagents frequently exit with exit_reason: max_iterations before completing their task, even when max_iterations is set explicitly (e.g. 50). The issue is especially visible in batch mode (3 parallel subagents).
Root Cause
In tools/delegate_tool.py, line ~196:
shared_budget = getattr(parent_agent, "iteration_budget", None)
# ...
child = AIAgent(
max_iterations=max_iterations, # e.g. 50
iteration_budget=shared_budget, # <-- parent's already-partially-consumed budget
...
)And in run_agent.py (AIAgent.init):
self.iteration_budget = iteration_budget or IterationBudget(max_iterations)When a subagent is created, it receives the parent's shared IterationBudget instead of a fresh one. The parent has already consumed iterations during its own setup and reasoning. In batch mode with 3 parallel subagents, all 3 share the same depleted budget.
Example: Parent has max_iterations=90, has consumed 30 by the time it spawns 3 subagents with max_iterations=50. Each subagent thinks it has 50 iterations, but they all race to consume the remaining 60 — so each only gets ~20 on average before hitting the budget cap. The child's own max_iterations=50 is set but effectively overridden by the inherited budget.
Steps to Reproduce
- Run a task that requires ~20+ parent iterations before delegating
- Call
delegate_taskwithtasks=[...](3 parallel subagents),max_iterations=50 - Observe subagents finishing with
exit_reason: max_iterationsafter only 15-25 tool calls, mid-task
Expected Behavior
A subagent created with max_iterations=50 should be allowed up to 50 LLM turns regardless of how many turns the parent has consumed. The budget cap on a subagent should be independent of the parent's consumption.
Proposed Fix
Give each subagent a fresh IterationBudget seeded from its own max_iterations, rather than inheriting the parent's shared (and partially consumed) budget:
# delegate_tool.py — _build_child_agent()
# Before (line ~196):
shared_budget = getattr(parent_agent, "iteration_budget", None)
# After:
# Each child gets its own budget capped at its own max_iterations.
# The parent budget is NOT shared — subagents should not be penalized
# for iterations the parent already consumed.
shared_budget = None # let AIAgent.__init__ create a fresh IterationBudget(max_iterations)This one-line change makes IterationBudget(max_iterations) in AIAgent.init always apply correctly for subagents.
Alternative / More Nuanced Fix
If the intent is to have a session-wide hard cap (parent + all children combined), that's a valid design — but it should be documented clearly and the max_iterations parameter on delegate_task should reflect the true remaining budget, not a misleading per-child number. Alternatively, expose a config flag like subagent_budget: isolated | shared.
Environment
- hermes-agent (current main)
- delegate_tool.py DEFAULT_MAX_ITERATIONS = 50
- Reproducible with any task requiring 3 parallel subagents and a parent that has run 20+ turns first
Related
- [Feature]: Allow to limit the parallelism on sub-agents #2838 — requests to limit subagent parallelism (different but connected concern)