Description
TuiLiveView (strix/interface/tui/live_view.py) keeps track of tool call/output events using self._tool_event_by_call_id, which is currently keyed only by call_id. This leads to two issues when multiple agents are active in the same session.
Bug 1: Tool events collide between agents
Since _tool_event_by_call_id is shared across all agents, two different agents can end up using the same call_id. When that happens, the second tool call reuses the existing event instead of creating a new one. Its data is merged into the first event through existing["data"].update(...).
As a result:
The original event is overwritten with the second agent's tool_name and args.
The event's agent_id never changes, so the second agent doesn't get its own event.
events_for_agent() filters by agent_id, meaning the second agent's tool call never appears in its timeline while the first agent's event becomes corrupted.
Bug 2: Replayed tool-call resets completed tools back to "running"
_record_tool_call_data() always creates tool_data with:
"status": "running"
When an existing event is found, it blindly updates the event with:
existing["data"].update(tool_data)
This becomes a problem if the tool has already finished and _record_tool_output_data() has already updated the event with:
status = "completed" or "failed"
result
If the same tool_call_item is processed again (for example, because of a duplicate stream event or hydration replay), the event's status is changed back to "running" while the previous result remains. This leaves the event in an inconsistent state (status == "running" but result is already populated).
Steps to Reproduce
Bug 1
Call _record_tool_call("agent-A", item_with_call_id("shared-id"))
Call _record_tool_call("agent-B", item_with_call_id("shared-id"))
Observe that:
events_for_agent("agent-A") contains incorrect data, or
events_for_agent("agent-B") is missing the tool event entirely.
Bug 2
Call _record_tool_call("agent-A", call_id="id-1")
Call _record_tool_output("agent-A", call_id="id-1", output={"success": True})
Replay _record_tool_call("agent-A", call_id="id-1")
Observe that the event status changes back to "running" even though the tool has already completed and still contains a result.
Expected Behavior
Tool events should be isolated per agent, even if different agents happen to use the same call_id.
Replayed or duplicate tool_call_item events should not overwrite the final state of an already completed or failed tool execution.
Actual Behavior
Tool events are indexed only by call_id, allowing events from different agents to overwrite one another.
_record_tool_call_data() always resets the status to "running" when updating an existing event, even if the tool has already completed, resulting in inconsistent event state.
Additional Context
self._tool_event_by_call_id is defined and used in strix/interface/tui/live_view.py, specifically in _record_tool_call_data() and _record_tool_output_data().
Currently, the lookup key only includes call_id, so there is no separation between agents. Additionally, updates to existing tool events do not preserve terminal states (completed or failed), allowing replayed tool-call events to unintentionally revert completed executions back to "running".
Description
TuiLiveView (strix/interface/tui/live_view.py) keeps track of tool call/output events using self._tool_event_by_call_id, which is currently keyed only by call_id. This leads to two issues when multiple agents are active in the same session.
Bug 1: Tool events collide between agents
Since _tool_event_by_call_id is shared across all agents, two different agents can end up using the same call_id. When that happens, the second tool call reuses the existing event instead of creating a new one. Its data is merged into the first event through existing["data"].update(...).
As a result:
The original event is overwritten with the second agent's tool_name and args.
The event's agent_id never changes, so the second agent doesn't get its own event.
events_for_agent() filters by agent_id, meaning the second agent's tool call never appears in its timeline while the first agent's event becomes corrupted.
Bug 2: Replayed tool-call resets completed tools back to "running"
_record_tool_call_data() always creates tool_data with:
"status": "running"
When an existing event is found, it blindly updates the event with:
existing["data"].update(tool_data)
This becomes a problem if the tool has already finished and _record_tool_output_data() has already updated the event with:
status = "completed" or "failed"
result
If the same tool_call_item is processed again (for example, because of a duplicate stream event or hydration replay), the event's status is changed back to "running" while the previous result remains. This leaves the event in an inconsistent state (status == "running" but result is already populated).
Steps to Reproduce
Bug 1
Call _record_tool_call("agent-A", item_with_call_id("shared-id"))
Call _record_tool_call("agent-B", item_with_call_id("shared-id"))
Observe that:
events_for_agent("agent-A") contains incorrect data, or
events_for_agent("agent-B") is missing the tool event entirely.
Bug 2
Call _record_tool_call("agent-A", call_id="id-1")
Call _record_tool_output("agent-A", call_id="id-1", output={"success": True})
Replay _record_tool_call("agent-A", call_id="id-1")
Observe that the event status changes back to "running" even though the tool has already completed and still contains a result.
Expected Behavior
Tool events should be isolated per agent, even if different agents happen to use the same call_id.
Replayed or duplicate tool_call_item events should not overwrite the final state of an already completed or failed tool execution.
Actual Behavior
Tool events are indexed only by call_id, allowing events from different agents to overwrite one another.
_record_tool_call_data() always resets the status to "running" when updating an existing event, even if the tool has already completed, resulting in inconsistent event state.
Additional Context
self._tool_event_by_call_id is defined and used in strix/interface/tui/live_view.py, specifically in _record_tool_call_data() and _record_tool_output_data().
Currently, the lookup key only includes call_id, so there is no separation between agents. Additionally, updates to existing tool events do not preserve terminal states (completed or failed), allowing replayed tool-call events to unintentionally revert completed executions back to "running".