Skip to content

bug: parallel async tool execution races on shared ToolContext #36

@G9000

Description

@G9000

Summary

execute_parallel (executor.py:187-203) runs async tools via asyncio.gather sharing the same ToolContext contextvar. Unlike sync tools (which get a contextvars.copy_context()), async tools all operate on the same ToolContext object.

Race Conditions

  1. current_tool_call_id: Set at line 69/140 before each execution — whichever tool runs last wins. PendingMemoryOps created by concurrent tools may reference the wrong tool call ID.
  2. memory_modified: _check_memory_modified (line 255-263) reads the flag and resets it to False. If tool A sets it and tool B's check clears it before tool A's result is processed, the memory refresh callback is skipped.

Fix

For parallel async execution, either:

  • Scope current_tool_call_id per-call (pass it as an argument rather than via ToolContext)
  • Create a context copy for each async tool invocation
  • Track memory_modified per-result rather than via shared mutable state

Files

  • apps/server/src/anima_server/services/agent/executor.py:187-203, 206-252

Severity

Medium — incorrect tool call ID attribution and missed memory refreshes under parallel tool execution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions