Skip to main content
Tools come from two places. Built-in tools ship with the SDK and are enabled by name. Custom tools are JSON schemas you define with async handlers you write.

Built-in tools

Each name enables one or more tools and brings its own prompt guidance, so enabling it is the only switch you need.
Runs inside a bubblewrap sandbox with the user’s workspace bound at /workspace and a sanitized environment. The working directory is the workspace, scratch belongs in .tmp/, and /tmp is per-command and discarded.
Network access is on by default so curl, pip and git work. A prompt injection can exfiltrate anything the sandbox can read, so turn it off when the agent does not need it:
A user can switch a tool off in Settings. LLM.run() drops those tools for the turn. A switch can never enable a tool the operator did not allow.

Custom tools

A tool schema is a plain dict. A handler is an async function. The return value goes to the UI stream and to the model as the tool_result, so one return covers both destinations.

Handler signature

A handler takes the tool input, and optionally a second argument with request context.

Presentation

.on() controls how the call appears in the UI.

Returning UI components

A handler can return a component dict instead of text, and it renders in the conversation.

Reserved names

skill and find_tools are reserved by the loop. skill loads a skill on demand, and find_tools loads a connector’s tools when the model needs them.

Next steps

Custom tool guide

A complete tool with a handler, a label and a UI return.

Connectors

OAuth grants, API keys, MCP servers and per-tool approvals.