Build Your First Plugin
This tutorial walks through writing a minimal Bub plugin that overrides the model stage with an echo response, installs it into the active environment, and verifies the framework picked it up.
The plugin needs no model credentials, so it is useful as both a smoke test and a starting template for real plugins.
Before you begin
Section titled “Before you begin”You should have:
- Bub installed in a
uv-managed environment - a free directory outside your workspace where you can scaffold the plugin package
1. Scaffold the package
Section titled “1. Scaffold the package”Create the package layout:
mkdir bub-echo-plugin
cd bub-echo-plugin
mkdir -p src/bub_echo_plugin
touch src/bub_echo_plugin/__init__.py
Create pyproject.toml. The [project.entry-points."bub"] block is what makes the plugin discoverable:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "bub-echo-plugin"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bub>=0.1"]
[project.entry-points."bub"]
echo = "bub_echo_plugin.plugin:echo_plugin"
[tool.hatch.build.targets.wheel]
packages = ["src/bub_echo_plugin"]
2. Implement the plugin
Section titled “2. Implement the plugin”Create src/bub_echo_plugin/plugin.py. A plugin is any object whose methods are decorated with @hookimpl:
from __future__ import annotations
from bub import hookimpl
class EchoPlugin:
@hookimpl
def build_prompt(self, message, session_id, state):
if hasattr(message, "content"):
return str(message.content)
if isinstance(message, dict):
return str(message.get("content", ""))
return str(message)
@hookimpl
def run_model(self, prompt, session_id, state):
text = prompt if isinstance(prompt, str) else str(prompt)
return f"[echo:{session_id}] {text}"
echo_plugin = EchoPlugin()
build_prompt is a firstresult hook — the first non-None return value wins. run_model is also firstresult, so this implementation overrides the built-in agent entirely.
3. Create the plugin environment
Section titled “3. Create the plugin environment”From the plugin directory, sync the package and its dependencies into the plugin project’s .venv:
uv sync
uv sync installs the plugin package editable by default. The uv run bub ... commands below then run Bub from the same environment that contains the plugin entry point.
If you instead want to install the plugin into an existing Bub checkout environment, activate that target virtualenv first, then run uv pip install -e ..
4. Verify Bub loaded it
Section titled “4. Verify Bub loaded it”Ask Bub for its hook report:
uv run bub hooks
You should see echo listed under the build_prompt and run_model hook entries alongside builtin.
Now run a one-shot turn:
uv run bub run "hello from plugin tutorial"
The outbound message should contain:
[echo:cli:local] hello from plugin tutorial
If the response came from a real model instead, the plugin did not load — re-run uv sync in the plugin project, or ensure you installed it into the virtualenv that runs bub, then re-check bub hooks.
Next steps
Section titled “Next steps”- Build plugins — package layout, lifecycle, and discovery details.
- Hooks reference — every hookspec, with signatures.
- Distribution — ship plugins plus skills as one package.