wooden-umbrella-84098
05/18/2026, 4:42 PMagent_canvas_ui
Once we cut a stable release we'll need to be careful about breaking changes. The frontend should not make assumptions about what agent-server version is running, and should remain backward-compatible with old agent-servers. There is already support for detecting the backend version. We may want to have an e2e test matrix to ensure this stays stablecalm-artist-7206
05/18/2026, 5:12 PMcalm-artist-7206
05/18/2026, 5:12 PMagent_canvas_ui tool about? Describe. And tell me why does the backend know about frontend, can you think of another way?nutritious-balloon-65098
05/18/2026, 5:13 PMnutritious-balloon-65098
05/18/2026, 5:15 PMagent_canvas_ui and related capability/version-detection code.
• I did not find a real tool named agent_canvas_ui defined in this codebase.
• I found that the backend exposes available tools via /server_info -> usable_tools, so the frontend is likely treating agent_canvas_ui as a capability indicator rather than a true executable tool.
• I also found explicit backend references to the canvas frontend in auth/workspace embedding code. That coupling exists because browser constraints around iframes/img/top-level navigation require cookie-based auth for embedded workspace artifacts.
Restated answer to your question:
• What is agent_canvas_ui about?
It is most likely a UI capability marker meaning something like “this backend supports canvas-style UI/artifact rendering,” not a normal agent tool like terminal or file_editor.
• Why does the backend know about the frontend?
Because some frontend behaviors, especially embedding workspace artifacts in iframes/images, require backend support due to browser auth/security limitations. So some coupling is understandable.
• Can I think of another way?
Yes. A better design would be to use:
a. explicit backend capabilities (for example server_info.capabilities.canvas_ui = true) instead of a fake tool name,
b. version detection only as a fallback, and/or
c. a structured artifact/rendering protocol so the frontend can enhance when supported and degrade gracefully otherwise.
• My recommendation:
For stable releases, the frontend should avoid assuming a tool called agent_canvas_ui exists. Prefer an explicit capabilities contract plus backward-compatible fallback behavior.nutritious-balloon-65098
05/18/2026, 5:17 PMOpenHands/agent-canvas repo and checked the actual implementation instead of guessing.
• I verified that the relevant thing is not `agent_canvas_ui`; the real names are:
◦ tool name: canvas_ui
◦ injected module: canvas_ui_tool
Re-stated answer:
• canvas_ui is a frontend-control tool used by Agent Canvas so the agent can drive the UI, specifically the right-side panel.
• It supports actions like:
◦ navigate_to_file
◦ open_tab
◦ show_preview
• The frontend injects this tool into conversation startup payloads and also sends:
◦ tool_module_qualnames = { canvas_ui: "canvas_ui_tool" }
• The agent-server imports that module, which registers the tool.
• The server-side executor is intentionally a no-op acknowledgment.
• The real UI effect happens in the frontend: it listens for websocket `ActionEvent`s with tool_name === "canvas_ui" and then switches tabs / selects files / shows previews client-side.
Why the backend “knows about frontend”:
• In this design, the backend does not intrinsically hardcode Agent Canvas in the SDK repo.
• Instead, Agent Canvas uses the backend’s custom tool injection mechanism to load a frontend-specific tool into the server at runtime.
• So the coupling is real, but it is implemented via an extension path rather than hardcoded backend logic.
Can I think of another way?
• Yes. A cleaner architecture would use a dedicated UI event/artifact protocol instead of modeling UI control as a tool.
• Example alternatives:
◦ a first-class ui_action event type
◦ richer artifact metadata like “open this file in preview”
◦ explicit frontend/backend capability negotiation
• That would make the boundary cleaner and likely improve backward compatibility.
Checklist:
• agent-canvascalm-artist-7206
05/18/2026, 5:22 PMnutritious-balloon-65098
05/18/2026, 5:22 PMnutritious-balloon-65098
05/18/2026, 5:23 PMnutritious-balloon-65098
05/18/2026, 5:40 PMnutritious-balloon-65098
05/18/2026, 11:11 PMOpenHands/agent-canvas repo and tracing the actual canvas_ui mechanism.
• I confirmed the canonical names are:
◦ tool name: canvas_ui
◦ injected module: canvas_ui_tool
◦ not agent_canvas_ui
• I explained what the tool does:
◦ it lets the agent drive Agent Canvas UI actions like navigate_to_file, open_tab, and show_preview.
• I traced the execution path:
◦ frontend injects canvas_ui into conversation tool config
◦ agent-server imports canvas_ui_tool via tool_module_qualnames
◦ the tool registers with the backend
◦ frontend listens for `ActionEvent`s where tool_name === "canvas_ui"
◦ frontend performs the UI effect client-side
◦ backend executor returns a no-op acknowledgment observation
◦ that observation is wrapped as ObservationEvent and sent back to the LLM as a standard tool result
• I described the observation format with snippets:
◦ CanvasUIObservation.from_text(...)
◦ ObservationEvent(...)
◦ LLM-facing Message(role="tool", ...)
◦ chat-completions and Responses API serialization shapes
• I answered your architecture question about the WebSocket handling:
◦ current code is a hard-coded central dispatcher, not a formal listener pattern
◦ that is acceptable today, but structurally it wants to become a listener-style design if more frontend-side tool effects are added
• I proposed the concrete design I’d use:
◦ not a full event bus
◦ a small typed static handler registry
◦ extract canvas_ui first into its own handler module
◦ let conversation-websocket-context.tsx own socket lifecycle/parsing only
Checklist:
• agent-canvas source instead of guessingnutritious-balloon-65098
05/18/2026, 11:17 PMOpenHands/agent-canvas locally and verified that the feature is canvas_ui (module canvas_ui_tool), not agent_canvas_ui.
• I traced how it works:
◦ Agent Canvas injects canvas_ui into conversation tools and sends tool_module_qualnames.
◦ The agent-server imports canvas_ui_tool, which registers the tool.
◦ The tool executor is a server-side no-op that returns an acknowledgment observation.
◦ The frontend listens for `ActionEvent`s with tool_name === "canvas_ui" and performs the real UI effect client-side.
• I explained the observation/result flow:
◦ CanvasUIObservation.from_text(...)
◦ wrapped into ObservationEvent
◦ converted to Message(role="tool", name="canvas_ui", tool_call_id=...)
◦ then serialized back to the LLM as a standard tool result / function_call_output.
• I explained the execution path in short:
◦ LLM emits tool call → frontend reacts to action event → backend returns acknowledgment observation → observation is included in next LLM history.
• I evaluated the frontend WebSocket handling and explained that it is currently a hard-coded dispatcher, not a formal listener pattern.
• I proposed a cleaner design:
◦ not a full event bus,
◦ but a static typed handler registry / listener-style dispatcher,
◦ with canvas_ui as a good first extracted handler.
• I explained what the existing side effects do:
◦ terminal side effect: mirrors command input/output into the terminal store so the Terminal panel shows the transcript.
◦ browser side effect: updates URL and screenshot in the browser store so the Browser panel shows what the agent navigated to and saw.
Status / completeness:
• canvas_ui purpose, execution path, observation format, and frontend event handling.nutritious-balloon-65098
05/18/2026, 11:20 PMOpenHands/agent-canvas and verified the implementation instead of guessing.
What I established:
• The feature is canvas_ui (not agent_canvas_ui), defined in agent-canvas/tools/canvas_ui_tool.py.
• It is a frontend-control tool that lets the agent:
◦ navigate to a file,
◦ open a tab,
◦ show a preview.
• The tool is injected by the frontend via:
◦ tools: [..., { name: "canvas_ui", params: {} }]
◦ tool_module_qualnames: { canvas_ui: "canvas_ui_tool" }
• The server-side executor is a *no-op acknowledgment*; the real UI change happens client-side when the frontend sees `ActionEvent`s with tool_name === "canvas_ui".
I also explained the observation/result path:
• The executor returns CanvasUIObservation.from_text(...).
• That becomes an ObservationEvent.
• That is converted to a Message(role="tool", ...).
• The LLM then receives it as a normal tool result / function_call_output.
On the frontend event architecture:
• I explained that the current implementation is a hard-coded dispatcher, not a formal listener pattern.
• I recommended a typed handler registry as the best next step, instead of a full event bus.
On terminal and browser side effects:
• Terminal side effect: mirrors command input/output into a terminal transcript store.
• Browser side effect: updates browser URL + screenshot store so the Browser panel reflects what the agent browsed.
On user-extensible side effects:
• I recommended a more general design:
◦ one generic tool like client_action / ui_effect,
◦ a frontend side-effect registry,
◦ a constrained frontend API for handlers,
◦ and a prompt-exposed list of registered effects.
• I also noted that if “users” are untrusted end users, this should be declarative, not arbitrary JS.
Checklist:
• agent-canvascanvas_ui is and how it worksnutritious-balloon-65098
05/18/2026, 11:36 PMOpenHands/agent-canvas repo to stop guessing and answer from source.
• I confirmed there is no agent_canvas_ui tool in the code I checked; the actual Agent Canvas custom tool is canvas_ui, with module name canvas_ui_tool.
• I traced how canvas_ui works:
◦ Agent Canvas injects it into conversation startup via tools plus tool_module_qualnames.
◦ The Python tool registers server-side and returns a no-op acknowledgment observation.
◦ The frontend watches WebSocket ActionEvents with tool_name === "canvas_ui" and performs the real UI change client-side.
• I described the observation/result path in short:
◦ executor returns CanvasUIObservation.from_text(...)
◦ SDK wraps that in an ObservationEvent
◦ that becomes a Message(role="tool", name="canvas_ui", tool_call_id=..., content=...)
◦ which is then serialized back to the LLM as a normal tool result / function_call_output.
• I reviewed the current frontend event handling and explained that it is not a formal listener pattern today; it is a centralized WebSocket dispatcher with inline event-specific side effects.
• I proposed a cleaner direction:
◦ use a typed handler registry / listener-style dispatcher
◦ and, if user-extensible side effects are desired, evolve toward a generic client-action tool + frontend side-effect registry + constrained API.
• I explained the current built-in side effects:
◦ terminal side effect mirrors command input/output into the terminal transcript store
◦ browser side effect updates browser URL and latest screenshot in the browser store
◦ canvas_ui side effect changes the visible right-side panel / selected file.
• I answered what handleCanvasUIAction does today, from source:
◦ navigate_to_file → opens Files tab and selects the path
◦ show_preview → currently does the same thing
◦ open_tab → switches to a validated tab
◦ all tab navigation also opens the right-side panel if it was collapsed.
• I also provided GitHub source links for the relevant agent-canvas files:
◦ src/services/canvas-ui.ts
◦ src/contexts/conversation-websocket-context.tsx
No code changes were made in either repository; this was analysis and explanation only.nutritious-balloon-65098
05/18/2026, 11:44 PMOpenHands/agent-canvas to stop guessing and verify how canvas_ui actually works.
• I confirmed the real names are:
◦ tool name: canvas_ui
◦ injected module: canvas_ui_tool
• I explained the end-to-end flow:
◦ frontend injects the tool via tool_module_qualnames
◦ agent-server imports/registers it
◦ frontend reacts to `ActionEvent`s with tool_name === "canvas_ui"
◦ backend returns a normal acknowledgment observation
◦ that observation is sent back to the LLM as a standard tool result
• I described the observation format and execution path, including:
◦ CanvasUIObservation.from_text(...)
◦ wrapping into ObservationEvent
◦ conversion to Message(role="tool", ...)
◦ serialization for chat-style and Responses-style LLM APIs
• I explained that the current WebSocket handling is a hard-coded dispatcher, not a formal listener system, and suggested a better architecture:
◦ a typed static handler registry as the pragmatic “listener pattern”
◦ and, for user-extensible side effects, a generic `client_action`/`ui_effect` tool plus a frontend handler registry
• I explained the current terminal and browser side effects:
◦ terminal appends command input and output into a transcript store
◦ browser updates URL and screenshot state for the Browser panel
• I described exactly what handleCanvasUIAction does today:
◦ navigate_to_file → open Files tab and select file
◦ show_preview → currently same behavior
◦ open_tab → switch to a validated tab and reveal the right panel if needed
• I answered the detour about `console.warn(...)`:
◦ yes, feeding actual frontend outcome back to the LLM would be useful
◦ but the current architecture cannot truthfully put frontend success/failure into the Observation because the observation is authored server-side before the frontend reports back
◦ for invalid tab names specifically, backend schema validation should already reject them earlier
Completion status:
• agent-canvas repo rather than guessing