Skip to content

fix(rewoo): prevent plan() and aplan() replay from mutating cached tool_calls#192

Open
BEASTSHRIRAM wants to merge 3 commits intomesa:mainfrom
BEASTSHRIRAM:fix/rewoo-mutable-alias
Open

fix(rewoo): prevent plan() and aplan() replay from mutating cached tool_calls#192
BEASTSHRIRAM wants to merge 3 commits intomesa:mainfrom
BEASTSHRIRAM:fix/rewoo-mutable-alias

Conversation

@BEASTSHRIRAM
Copy link

Summary

Fixes a silent tool replay bug in ReWOOReasoning.plan() and aplan() where multi-step tool call replays mutate self.current_plan.tool_calls in-place, causing the agent to silently replay incorrect tool steps on subsequent calls. The fix stores the original tool calls separately and returns a copy instead of mutating the cached plan.

Bug / Issue

Fixes #184 (related to async memory issue, but exposes a broader sync/async mutation bug)

In a complex reasoning task using ReWOOReasoning (e.g., the negotiation example
where an agent plans multiple steps ahead), when a plan has 3+ tool calls:

  1. First replay call: remaining_tool_calls=3, len(current_plan.tool_calls)=3, index = 3-3 = 0 ✓ Correct tool returned
  2. First replay mutates: current_plan.tool_calls = [tool_0] (now length 1)
  3. Second replay call: remaining_tool_calls=2, len(current_plan.tool_calls)=1, index = 1-2 = -1 ✗ Negative index wrong tool
  4. Subsequent calls: Continue with corrupted indices, agent replays wrong tool step

The mutation happens because both branches of the early-return path used a mutable alias:

# BEFORE (buggy): mutation in both plan() and aplan()
current_plan = self.current_plan
current_plan.tool_calls = tool_call  # ← mutates the cached original

No error is raised. The agent silently replays the wrong tool, leading to semantic failures
in tasks requiring multi-step plans (negotiation, complex reasoning, sequential tool use).

Implementation

  1. Added self._all_tool_calls = [] to __init__() to store the original tool calls list separately
  2. When a new plan is generated, store a copy: self._all_tool_calls = list(rewoo_plan.llm_plan.tool_calls)
  3. Use the original list for index calculation: index_of_tool = len(self._all_tool_calls) - self.remaining_tool_calls
  4. Return a copy of the plan with only the required tool call, leaving the original untouched:
    temp_plan = copy.copy(self.current_plan)
    temp_plan.tool_calls = [self.current_plan.tool_calls[index_of_tool]]
    return Plan(llm_plan=temp_plan, ...)
  5. Applied to both plan() and aplan() methods (identical early-return logic in both)
# _all_tool_calls decouples indexing from mutations
# temp_plan decouples return value from original
# both prevent silent failures on subsequent replays

Testing

All 20 ReWOO reasoning tests pass, including:

  • test_plan_with_remaining_tool_calls() verifies correct tool is returned
  • test_aplan_with_remaining_tool_calls() async path works correctly
  • test_remaining_tool_calls_decrement() index calculation across multiple scenarios

Tests confirm that each replay returns the correct tool from the full plan without
corrupting self.current_plan.tool_calls.

Additional Notes

This is a silent correctness bug in ReWOOReasoning, the multi-step planning strategy.
Any agent using ReWOO with plans containing 2+ tool calls is affected. The failure is
invisible no exception is raised, the agent simply replays wrong tool steps, causing
task failures that are hard to debug.

No API changes. No new dependencies. The fix adds one import copy and decouples state
management in two methods. Fixes both sync and async paths with identical logic.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87517bf0-2b28-4055-b508-0289e496e439

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan for PR comments
  • Generate coding plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

ReWOOReasoning.aplan() calls sync add_to_memory instead of async aadd_to_memory

2 participants