I noticed an error when running the frontend today...
# proj-agent-canvas
w
I noticed an error when running the frontend today--it complained about the backend not having a tool like
agent_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 stable
c
Yeah…
@nutritious-balloon-65098 what even is
agent_canvas_ui
tool about? Describe. And tell me why does the backend know about frontend, can you think of another way?
n
I'm on it! can track my progress here.
Summary of my work: • I inspected the repo for
agent_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.
Summary of my work: • I cloned the
OpenHands/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: • Investigated the real implementation in
agent-canvas
• Avoided guessing • Identified the actual tool/module names • Explained what the tool does • Explained why/how the backend is aware of frontend behavior • Proposed cleaner alternative designs No code changes were made. No commits were created.
c
@nutritious-balloon-65098 Hmm that's actually nice… how does the observation look like and how does it get back to the agent’s llm? Describe format with snippets and execution path in short.
n
OpenHands encountered an error: **Request timeout after 30 seconds to https://jrwxkfklyeawclzz.prod-runtime.all-hands.dev/api/conversations/17b52bbf-d179-47a5-946c-2e9088534eca/ask_agent** See the conversation for more information.
OpenHands encountered an error: **Request timeout after 30 seconds to https://jrwxkfklyeawclzz.prod-runtime.all-hands.dev/api/conversations/17b52bbf-d179-47a5-946c-2e9088534eca/ask_agent** See the conversation for more information.
Summary of my work: • I investigated the real implementation instead of guessing by cloning the
OpenHands/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: • Re-stated the answer to the question • Used the actual
agent-canvas
source instead of guessing
• Explained both behavior and execution path • Covered the observation format and how it reaches the LLM • Addressed the listener-pattern design question • No code changes were made Conciseness: • No repository files were modified. • No extraneous changes were introduced. • Work was limited to investigation, explanation, and design recommendation.
Summary of my work: • I answered your questions by inspecting both repos rather than guessing. • I cloned
OpenHands/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: • Answered the questions directly. • Used repo inspection instead of guessing. • Covered
canvas_ui
purpose, execution path, observation format, and frontend event handling.
• Explained terminal and browser side effects. • No code changes were made. Conciseness: • No repository changes were made, so there are no extraneous edits to revert. • The work was limited to investigation and explanation only.
Summary of my work: • I *did not make code changes*; I investigated and answered your architectural questions. • I cloned
OpenHands/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: • Investigated actual implementation in
agent-canvas
• Answered what
canvas_ui
is and how it works
• Described observation format and LLM execution path • Evaluated whether frontend handling should use a listener pattern • Proposed a clean listener-style architecture • Explained terminal/browser side effects • Proposed a design for user-defined side effects • Made no unnecessary code changes Conciseness: • No repository files were modified. • No extraneous changes were introduced.
Summary of my work: • I inspected both this repo and a freshly cloned
OpenHands/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.
Summary: I only answered questions; I did not make code changes. • I cloned and inspected
OpenHands/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: • Answered the user’s architectural and behavior questions • Verified behavior from the actual
agent-canvas
repo rather than guessing
• Provided GitHub source links when requested • Made no code changes Conciseness: • No repository changes were made • No extraneous edits or artifacts were introduced