Tool execution model
Tools in Omnia don’t all execute in the same place. Where a tool runs determines whether you can see its calls in the WebSocket stream. Two questions follow when a tool misbehaves: where did the call run, and was the model offered the tool at all?
Where each tool runs
Section titled “Where each tool runs”Client tools run in the browser. Every other tool runs in the runtime container. The facade sits between the two and forwards only the calls the browser has to execute.
flowchart LR Browser <-->|"WebSocket: tool_call, tool_result"| Facade["Facade (cmd/agent)"] Facade <-->|"gRPC Converse"| Runtime["Runtime (cmd/runtime)"] Runtime -->|"http, grpc, mcp, openapi handlers"| Backends["Tool backends"] Runtime -->|"platform handlers"| Operator["Operator REST API"] Runtime -->|"tool_calls records"| SessionAPI["Session API"]Client tools run in the browser
Section titled “Client tools run in the browser”A handler with type: client (endpoint client://browser) is forwarded to
the connected browser over the WebSocket facade as a tool_call message. The
browser executes the tool locally and sends back a tool_result message.
Client tools are the only tools visible in the WebSocket stream. If you inspect WebSocket traffic to debug a tool call and don’t see it, the tool most likely isn’t a client tool.
The facade forwards only calls the runtime marks as client-executed, so the
arguments and results of server-side tools never reach the browser. A
tool_call message has no field that says where the tool runs, because every
tool call on the WebSocket is client-side. See the
WebSocket protocol for the message
shapes and WebSocket message flows
for the order of messages in a tool call.
Server and platform tools run in the runtime
Section titled “Server and platform tools run in the runtime”Everything else executes in the runtime container, server-side, and is never forwarded over the WebSocket:
- Server tools:
http,grpc,mcpandopenapihandlers. - Platform tools: capabilities registered through the PromptKit SDK, such as memory and skills.
Declaring a tool and executing it
Section titled “Declaring a tool and executing it”A ToolRegistry binds a tool name to an executor. It does not decide what the
model is offered. A PromptPack prompt does that, through its tools list.
The runtime reads the union of the tools lists across the pack’s prompts and
compares it with the tools the ToolRegistry serves. A tool the registry serves
but no prompt declares is never offered to the model. A tool a prompt declares
but the registry doesn’t serve is never offered either. In both cases the
model is offered fewer tools than you expect, and nothing fails: the only
symptom is an answer that doesn’t use the tool.
The runtime logs each mismatch at startup, naming the tools and the fix: add
the name to the prompt’s tools list and republish the pack, or expose the
tool from the attached ToolRegistry. Tools in the memory__, skill__, a2a__, workflow__ and
mcp__ namespaces are the exception: PromptKit offers them without a tools
entry, so the runtime doesn’t flag their absence from the pack.
The split keeps the pack author in charge of what a prompt may do. If the registry’s contents were merged into every prompt’s allow-list, attaching a registry would silently widen every prompt, and the pack the runtime executes would differ from the pack you published.
Debugging: use the right signal
Section titled “Debugging: use the right signal”The runtime records every tool call to session-api, and the session’s
/tool-calls endpoint returns them. That covers client and server tools
alike, so it is the signal that always exists.
- A client tool also appears on the WebSocket stream.
- A server or platform tool never appears on the WebSocket. Read the runtime
logs or the session’s
/tool-callsendpoint. - A tool the model never calls may not have been offered. Check the runtime’s startup log for tools the pack doesn’t declare.
Because the tool_calls row is the only record of a server-side call, a
policy denial of one is visible there and not on the WebSocket.
Trade-offs
Section titled “Trade-offs”Forwarding only client calls keeps tool arguments and results that the browser doesn’t need off the client data-plane channel. The cost is that a live view of the conversation can’t render those calls from the stream. It learns that a call was recorded from the session activity stream, then reads the row through the authorised session-api.
Making the pack the source of truth for offered tools costs one extra step when you add a tool, because you edit the registry and the pack. The alternative, offering whatever the registry serves, is shorter to author and gives up the pack as the record of what a prompt can do.