From 4b17b5769655451b7721c1912e2e58f0679ddf70 Mon Sep 17 00:00:00 2001 From: Matthew Flanagan <188046+mattimustang@users.noreply.github.com> Date: Thu, 21 May 2026 17:35:11 +1000 Subject: [PATCH] perf(tui): cache event renders and eliminate redundant work during running state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three more performance issues in the running state hot path: Per-event render cache in _get_rendered_events_content: every 350ms tick during active streaming caused a full re-render of all events in the conversation — every chat message through AgentMessageRenderer (including Pygments syntax highlighting for code blocks) and every tool event. Chat messages and completed/failed tool events are now cached by (event_id, status) and only re-rendered when their status changes. Running tool events are re-rendered each tick as their content may still update. Skip duplicate _update_agent_status_display in _update_ui_from_tracer when the dot animation timer is active: _animate_dots (60ms) already calls it for "running" agents, so the unconditional call from _update_ui_from_tracer (350ms) was redundant, doubling the widget update rate during active scans. Fix _get_agent_name_for_vulnerability to use per-agent tool execution index instead of scanning all tool_executions, consistent with the other O(N) scan fixes from the previous commit. --- strix/interface/tui.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/strix/interface/tui.py b/strix/interface/tui.py index 7b9932215..086701bbe 100644 --- a/strix/interface/tui.py +++ b/strix/interface/tui.py @@ -715,6 +715,7 @@ class StrixTUIApp(App): # type: ignore[misc] self._streaming_render_cache: dict[str, tuple[int, Any]] = {} self._last_streaming_len: dict[str, int] = {} + self._event_render_cache: dict[str, Any] = {} self._scan_thread: threading.Thread | None = None self._scan_stop_event = threading.Event() @@ -925,7 +926,8 @@ class StrixTUIApp(App): # type: ignore[misc] self._update_chat_view() - self._update_agent_status_display() + if self._dot_animation_timer is None: + self._update_agent_status_display() self._update_stats_display() @@ -1091,11 +1093,24 @@ class StrixTUIApp(App): # type: ignore[misc] for event in events: content: Any = None + event_id = event["id"] if event["type"] == "chat": - content = self._render_chat_content(event["data"]) + if event_id in self._event_render_cache: + content = self._event_render_cache[event_id] + else: + content = self._render_chat_content(event["data"]) + if content is not None: + self._event_render_cache[event_id] = content elif event["type"] == "tool": - content = self._render_tool_content_simple(event["data"]) + status = event["data"].get("status", "") + cache_key = f"{event_id}_{status}" + if cache_key in self._event_render_cache: + content = self._event_render_cache[cache_key] + else: + content = self._render_tool_content_simple(event["data"]) + if content is not None and status in ("completed", "failed", "error"): + self._event_render_cache[cache_key] = content if content: if renderables: @@ -1344,13 +1359,13 @@ class StrixTUIApp(App): # type: ignore[misc] def _get_agent_name_for_vulnerability(self, report_id: str) -> str | None: """Find the agent name that created a vulnerability report.""" - for _exec_id, tool_data in list(self.tracer.tool_executions.items()): - if tool_data.get("tool_name") == "create_vulnerability_report": - result = tool_data.get("result", {}) - if isinstance(result, dict) and result.get("report_id") == report_id: - agent_id = tool_data.get("agent_id") - if agent_id and agent_id in self.tracer.agents: - name: str = self.tracer.agents[agent_id].get("name", "Unknown Agent") + for agent_id, agent_data in self.tracer.agents.items(): + for exec_id in agent_data.get("tool_executions", []): + tool_data = self.tracer.tool_executions.get(exec_id) + if tool_data and tool_data.get("tool_name") == "create_vulnerability_report": + result = tool_data.get("result", {}) + if isinstance(result, dict) and result.get("report_id") == report_id: + name: str = agent_data.get("name", "Unknown Agent") return name return None @@ -1494,6 +1509,7 @@ class StrixTUIApp(App): # type: ignore[misc] self._displayed_events.clear() self._streaming_render_cache.clear() self._last_streaming_len.clear() + self._event_render_cache.clear() self.call_later(self._update_chat_view) self._update_agent_status_display()