Add application tools
Give the Bub app an action it can perform for your work. The shared tools guide explains function schemas, results, and tool context. This page covers application registration and the policies applied by BubAgent.
Register a tool
Section titled “Register a tool”Define the tool in your plugin’s tools.py:
from bub import tool
@tool(name="orders.get")
def get_order(order_id: str) -> dict[str, str]:
"""Look up an order by id."""
return {"id": order_id, "status": "shipped"}
The decorator only creates a Tool. Provide it from your plugin with the provide_tools hook:
from bub import hookimpl
from bub.core.tools import Tool
from .tools import get_order
class OrdersPlugin:
@hookimpl
def provide_tools(self) -> list[Tool]:
return [get_order]
BubAgent(tools=None) collects these tools when constructed. For a duplicate name, the tool of the higher-priority plugin wins. To add tools later, update that agent’s tools dictionary explicitly.
Run bub hooks to check that your plugin implements provide_tools. The model-facing name of orders.get is orders_get.
Choose which tools a turn can use
Section titled “Choose which tools a turn can use”When calling BubAgent.run_stream, pass allowed_tools to limit the model’s tools for that turn. It accepts runtime names or model-facing names. None allows all instance tools; an empty collection allows none. Unknown names raise ValueError.
This filter applies to model and code-mode tools. Comma commands use the agent’s instance tools directly. Use tool-call decisions when a rule must also govern command execution.
Load tools when needed
Section titled “Load tools when needed”Mark a tool deferred=True to keep its full definition out of the initial model request:
from bub import tool
@tool(name="orders.get", deferred=True)
def get_order(order_id: str) -> dict[str, str]:
"""Look up an order by id."""
return {"id": order_id, "status": "shipped"}
BubAgent lists allowed deferred tools in the system prompt and supplies tool_describe when at least one remains. The model calls tool_describe to load a definition. The application records tool.loaded entries, so loaded tools remain available in later steps and turns until the tape is reset.
The turn’s allowlist controls both listing and loading. In code mode, allowed auto and code tools are available to Python immediately; deferral applies to the tools still offered directly to the model.
Use code mode in the app
Section titled “Use code mode in the app”Enable code mode for the current session:
,code_mode enable=true
The change applies on the next turn and is recorded for recovery after a restart. Use ,code_mode enable=false to switch back. When calling BubAgent directly with your own state, set state["code_mode"] = True for that run.
When run_code is allowed, the app selects tools according to exposure:
exposure |
Code mode off | Code mode on |
|---|---|---|
auto |
Offered directly to the model | Available through run_code |
direct |
Offered directly to the model | Offered directly; unavailable under tools.* |
code |
Unavailable to the model | Available through run_code |
command |
Comma command only | Comma command only |
If run_code is excluded, the turn uses the code-mode-off selection. The app writes a Python tool stub under <BUB_HOME>/codemode/ and includes its path in the system prompt. Python calls such as await tools.orders_get(order_id="123") use the selected tools and pass through the application’s interception hooks.
The app connects CodeMode to the session’s environment and model settings. Code can use the Republic bridge and media attachment tools described in CodeMode. Model credentials remain on the host when using the bridge; the default local execution environment still runs code on that host.
BubAgent interprets state["code_mode"] and the deferred flag to apply these selection rules. With a plain core.Agent, compose CodeMode explicitly and supply your own deferred-loading policy if you need one.
Choose the execution environment
Section titled “Choose the execution environment”Builtin bash, bash.output, bash.kill, fs.*, and run_code use the session’s Environment. The default is LocalEnvironment, which runs on the host.
Provide another environment through a plugin’s provide_environment(session_id, workspace) hook. The framework caches one environment per session and closes it when framework.running() exits. The shared tools guide describes the environment interface.
A custom application tool can access that environment with environment_from_state(context.state) from bub.builtin.environment. The Python tool runs on the host and explicitly delegates operations to the environment when needed.
Add instructions alongside the tool
Section titled “Add instructions alongside the tool”Put workspace-wide instructions in AGENTS.md. Use skills for procedures the model can load for a task. A skill can tell the model when to call your tool; the Python function defines what that tool does.
Comma commands let a person invoke registered tools without asking the model. See text commands for command syntax.