Bug Description
Two related issues in AgentTool affecting background task lifecycle management and concurrent execution correctness.
1. Background task not tracked — lifecycle uncontrollable
File: src/iac_code/agent/agent_tool.py:179-185
asyncio.create_task(self._run_background(...)) does not save the task handle, register a done callback, or provide a shutdown/cancel path. While _run_background() internally catches Exception, BaseException subclasses like KeyboardInterrupt or SystemExit would produce an "unhandled exception in Task" warning. More importantly, the task lifecycle cannot be awaited — on process exit or session close, in-flight background agents may lose state.
This directly relates to the /tasks stop issue : since the asyncio task is not retained, TaskManager.stop() can only set a status flag but cannot cancel the running coroutine. The agent continues executing tools and consuming tokens after the user believes it was stopped.
2. Shared _event_queue causes cross-talk between concurrent agent calls
Files: src/iac_code/agent/agent_tool.py:131,187-199,247-248, src/iac_code/tools/tool_executor.py:69-75,131-144
ToolExecutor writes tool._event_queue = call.event_queue on the shared registry tool instance, then concurrently executes tools marked as concurrency_safe. AgentTool.is_concurrency_safe() unconditionally returns True, so two concurrent agent tool calls in the same turn can overwrite each other's _event_queue.
Verified: Two concurrent calls to a shared tool instance with different queues — both calls end up reading from the second queue's ID. AgentTool.execute() passes self._event_queue to run_sub_agent() and writes the end sentinel to it.
Impact: Sub-agent tool progress events may go to the wrong call's queue, or the wrong queue may receive a premature None end marker. CLI/ACP/A2A progress streams will cross-talk, lose events, or cause the parent agent to wait for events that never arrive.
Expected Behavior
- Background tasks should be tracked and cancellable via
TaskManager.
- Per-call state (event queue) should not be stored on shared tool instances.
Actual Behavior
- Background tasks are fire-and-forget with no cancel path.
- Concurrent agent calls overwrite each other's event queue on the shared instance.
Suggested Fix
- Store the
asyncio.Task in TaskManager; stop()/cancel() should call task.cancel() and handle CancelledError in _run_background().
- Move event queue to
ToolContext instead of storing on the tool instance. Until fixed, AgentTool.is_concurrency_safe() should return False.
Bug Description
Two related issues in
AgentToolaffecting background task lifecycle management and concurrent execution correctness.1. Background task not tracked — lifecycle uncontrollable
File:
src/iac_code/agent/agent_tool.py:179-185asyncio.create_task(self._run_background(...))does not save the task handle, register a done callback, or provide a shutdown/cancel path. While_run_background()internally catchesException,BaseExceptionsubclasses likeKeyboardInterruptorSystemExitwould produce an "unhandled exception in Task" warning. More importantly, the task lifecycle cannot be awaited — on process exit or session close, in-flight background agents may lose state.This directly relates to the
/tasks stopissue : since the asyncio task is not retained,TaskManager.stop()can only set a status flag but cannot cancel the running coroutine. The agent continues executing tools and consuming tokens after the user believes it was stopped.2. Shared
_event_queuecauses cross-talk between concurrent agent callsFiles:
src/iac_code/agent/agent_tool.py:131,187-199,247-248,src/iac_code/tools/tool_executor.py:69-75,131-144ToolExecutorwritestool._event_queue = call.event_queueon the shared registry tool instance, then concurrently executes tools marked asconcurrency_safe.AgentTool.is_concurrency_safe()unconditionally returnsTrue, so two concurrent agent tool calls in the same turn can overwrite each other's_event_queue.Verified: Two concurrent calls to a shared tool instance with different queues — both calls end up reading from the second queue's ID.
AgentTool.execute()passesself._event_queuetorun_sub_agent()and writes the end sentinel to it.Impact: Sub-agent tool progress events may go to the wrong call's queue, or the wrong queue may receive a premature
Noneend marker. CLI/ACP/A2A progress streams will cross-talk, lose events, or cause the parent agent to wait for events that never arrive.Expected Behavior
TaskManager.Actual Behavior
Suggested Fix
asyncio.TaskinTaskManager;stop()/cancel()should calltask.cancel()and handleCancelledErrorin_run_background().ToolContextinstead of storing on the tool instance. Until fixed,AgentTool.is_concurrency_safe()should returnFalse.