NousResearch/hermes-agent

Subagent iteration budget isolation bug and fixes

Cluster 2873 · agent. Data as of

Nothing open on GitHub. Fix merged in #3004 on 25 Mar 2026. Check it covers your case.

The short answer

0open issues
0open PRs
4items grouped
Known?
Yes. 4 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #3004. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #3004. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where subagents share the parent agent's iteration budget, causing premature max_iterations cutoffs. Multiple fix attempts address giving subagents independent, isolated iteration budgets.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #3004 fix(delegate): give subagents independent iteration budgets

    merged to main

    files: hermes_cli/config.py, run_agent.py, tools/delegate_tool.py

    Show the diff
    • hermes_cli/config.py
      @@ -317,6 +317,8 @@ def ensure_hermes_home():
               "provider": "",    # e.g. "openrouter" (empty = inherit parent provider + credentials)
               "base_url": "",    # direct OpenAI-compatible endpoint for subagents
               "api_key": "",     # API key for delegation.base_url (falls back to OPENAI_API_KEY)
      +        "max_iterations": 50,  # per-subagent iteration cap (each subagent gets its own budget,
      +                               # independent of the parent's max_iterations)
           },
       
           # Ephemeral prefill messages file — JSON list of {role, content} dicts
    • run_agent.py
      @@ -162,11 +162,15 @@ def _install_safe_stdio() -> None:
       
       
       class IterationBudget:
      -    """Thread-safe shared iteration counter for parent and child agents.
      +    """Thread-safe iteration counter for an agent.
       
      -    Tracks total LLM-call iterations consumed across a parent agent and all
      -    its subagents.  A single ``IterationBudget`` is created by the parent
      -    and passed to every child so they share the same cap.
      +    Each agent (parent or subagent) gets its own ``IterationBudget``.
      +    The parent's budget is capped at ``max_iterations`` (default 90).
      +    Each subagent gets an independent budget capped at
      +    ``delegation.max_iterations`` (default 50) — this means total
      +    iterations across parent + subagents can exceed the parent's cap.
      +    Users control the per-subagent limit via ``delegation.max_iterations``
      +    in config.yaml.
       
           ``execute_code`` (programmatic tool calling) iterations are refunded via
           :meth:`refund` so they don't eat into the budget.
      @@ -5719,7 +5723,7 @@ def run_conversation(
                   api_call_count += 1
                   if not self.iteration_budget.consume():
                       if not self.quiet_mode:
      -                    self._safe_print(f"\n⚠️  Session iteration budget exhausted ({self.iteration_budget.max_total} total across agent + subagents)")
      +                    self._safe_print(f"\n⚠️  Iteration budget exhausted ({self.iteration_budget.used}/{self.iteration_budget.max_total} iterations used)")
                       break
       
                   # Fire step_callback for gateway hooks (agent:step event)
      @@ -7178,7 +7182,7 @@ def _stop_spinner():
                   or self.iteration_budget.remaining <= 0
               ):
                   if self.iteration_budget.remaining <= 0 and not self.quiet_mode:
      -                print(f"\n⚠️  Session iteration budget exhausted ({self.iteration_budget.used}/{self.iteration_budget.max_total} used, including subagents)")
      +                print(f"\n⚠️  Iteration budget exhausted ({self.iteration_budget.used}/{self.iteration_budget.max_total} iterations used)")
                   final_response = self._handle_max_iterations(messages, api_call_count)
               
               # Determine if conversation completed successfully
    • tools/delegate_tool.py
      @@ -191,9 +191,10 @@ def _build_child_agent(
           # Build progress callback to relay tool calls to parent display
           child_progress_cb = _build_child_progress_callback(task_index, parent_agent)
       
      -    # Share the parent's iteration budget so subagent tool calls
      -    # count toward the session-wide limit.
      -    shared_budget = getattr(parent_agent, "iteration_budget", None)
      +    # Each subagent gets its own iteration budget capped at max_iterations
      +    # (configurable via delegation.max_iterations, default 50).  This means
      +    # total iterations across parent + subagents can exceed the parent's
      +    # max_iterations.  The user controls the per-subagent cap in config.yaml.
       
           # Resolve effective credentials: config override > parent inherit
           effective_model = model or parent_agent.model
      @@ -230,7 +231,7 @@ def _build_child_agent(
               providers_order=parent_agent.providers_order,
               provider_sort=parent_agent.provider_sort,
               tool_progress_callback=child_progress_cb,
      -        iteration_budget=shared_budget,
      +        iteration_budget=None,  # fresh budget per subagent
           )
           # Set delegation depth so children can't spawn grandchildren
           child._delegate_depth = getattr(parent_agent, '_delegate_depth', 0) + 1

2 PRs closed without merging

From the close comments: done elsewhere 2

  • #2875 fix(delegation): isolate subagent iteration budgets by default (fixes #2873) · done elsewhere → #3004
  • #2928 fix(delegate): give subagents isolated iteration budgets · done elsewhere → #3004
Closed issues · 1
  • Closed#2873[Bug]: Subagents hit max_iterations prematurely — shared IterationBudget drains parent's remaining quota
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#3004
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
25 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.