Skip to content

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?

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"]

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, mcp and openapi handlers.
  • Platform tools: capabilities registered through the PromptKit SDK, such as memory and skills.

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.

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-calls endpoint.
  • 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.

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.