Skip to content

fix(loop): graceful exit on max iterations with wrap-up nudge + forced text-only#148

Merged
warren618 merged 1 commit into
HKUDS:mainfrom
toanalien:fix/agent-loop-max-iterations-graceful-exit
May 30, 2026
Merged

fix(loop): graceful exit on max iterations with wrap-up nudge + forced text-only#148
warren618 merged 1 commit into
HKUDS:mainfrom
toanalien:fix/agent-loop-max-iterations-graceful-exit

Conversation

@toanalien

Copy link
Copy Markdown
Contributor

Summary

  • Problem: AgentLoop could exhaust all 50 iterations when the LLM kept calling tools endlessly, resulting in status="failed" with no useful output returned to the user.
  • Fix: Mirror the swarm worker's proven approach — inject a wrap-up nudge at 80% of the iteration budget and drop tool definitions on the last iteration to force a text response.
  • Test: Added test_force_text_only_on_last_iteration validating that an LLM which always returns tool calls now produces status="success" instead of status="failed".

Changes

agent/src/agent/loop.py

  1. Calculate wrap_up_at = max(1, int(self.max_iterations * 0.8)) before the main loop
  2. At 80% of iteration budget, inject a [SYSTEM] user message nudging the LLM to stop calling tools and produce a final answer
  3. On the last iteration, pass tools=None to stream_chat — the LLM cannot request tool calls and must produce text output, guaranteeing a final answer

agent/tests/test_agent_loop_terminal_state.py

  • Added _StubLLMAlwaysToolCalls — LLM stub that returns compact tool calls until tools=None forces text
  • Added test_force_text_only_on_last_iteration — verifies the forced text-only last iteration produces status="success" with content

How it works

The swarm worker (agent/src/swarm/worker.py) already had both mechanisms (lines 363-426). This PR brings the same pattern to AgentLoop:

Mechanism Swarm Worker AgentLoop (before) AgentLoop (after)
Wrap-up nudge at 80% Yes No Yes
Force text-only on last iter Yes No Yes

Test plan

  • Existing 3 tests in test_agent_loop_terminal_state.py pass unchanged
  • New test_force_text_only_on_last_iteration passes
  • ruff check passes
  • python -m py_compile passes

…d text-only

AgentLoop could exhaust all 50 iterations when the LLM kept calling tools,
resulting in status="failed" with no useful output. Mirror the swarm worker's
proven approach: inject a wrap-up nudge at 80% of iteration budget and drop
tool definitions on the last iteration to force a text response.
@warren618 warren618 merged commit eefbec8 into HKUDS:main May 30, 2026
warren618 added a commit that referenced this pull request May 30, 2026
Follow-up to #148. The wrap-up nudge fired at iteration == wrap_up_at
unconditionally, which on tiny budgets (max_iter<=2 -> wrap_up_at==1)
injected a '[SYSTEM] wrap up' user message on the very first iteration,
displacing the active research-goal context as the most recent user
message and breaking test_agent_goal_context. It also conflicted with
goal-continuation by telling the agent to stop early.

Now the nudge only fires for 1 < iteration < max_iterations: never on
the first iteration, and never on the last (the forced text-only path
already guarantees a final answer there).
@warren618

Copy link
Copy Markdown
Collaborator

Merged — thanks @toanalien! 🙏 Mirroring the swarm worker's wrap-up + forced-text-only pattern into AgentLoop is a nice consistency win, and the forced-text last iteration is verified to turn a tool-spamming run into a success with a final answer (confirmed against the real LLM e2e for max_iter).

Heads-up: I pushed a small follow-up on top (7fd8c56). The nudge fired at iteration == wrap_up_at unconditionally, and on tiny budgets (max_iter <= 2wrap_up_at == 1) it injected the [SYSTEM] wrap-up message on the first iteration. That displaced the active research-goal context as the most recent user message — breaking test_agent_goal_context — and could tell the agent to stop early while a goal was still continuing. The follow-up gates the nudge to 1 < iteration < max_iterations (never on the first iteration, never on the last, where the forced-text path already guarantees an answer). Full suite is green again. Thanks for the well-structured PR and tests!

longmfe pushed a commit to longmfe/Vibe-Trading-Mars that referenced this pull request Jul 14, 2026
…d text-only (HKUDS#148)

AgentLoop could exhaust all 50 iterations when the LLM kept calling tools,
resulting in status="failed" with no useful output. Mirror the swarm worker's
proven approach: inject a wrap-up nudge at 80% of iteration budget and drop
tool definitions on the last iteration to force a text response.
longmfe pushed a commit to longmfe/Vibe-Trading-Mars that referenced this pull request Jul 14, 2026
Follow-up to HKUDS#148. The wrap-up nudge fired at iteration == wrap_up_at
unconditionally, which on tiny budgets (max_iter<=2 -> wrap_up_at==1)
injected a '[SYSTEM] wrap up' user message on the very first iteration,
displacing the active research-goal context as the most recent user
message and breaking test_agent_goal_context. It also conflicted with
goal-continuation by telling the agent to stop early.

Now the nudge only fires for 1 < iteration < max_iterations: never on
the first iteration, and never on the last (the forced text-only path
already guarantees a final answer there).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants