Skip to content

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.

You should have:

  • Bub installed in a uv-managed environment
  • a free directory outside your workspace where you can scaffold the plugin 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"]

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.

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 ..

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.