Distribution
This page shows how to package Bub as the runtime kernel of your own product. The case study throughout is visual-base — a distribution that pins bub, ships its own visual-base CLI, and bundles two plugins (bub_kimi and bub_eye) plus a skills package.
Before you begin
Section titled “Before you begin”- A plugin or two ready to publish (see Plugins).
- A
pyproject.toml-based build backend (hatchling,uv_build, orpdm-backend).
1. Pin the Bub kernel
Section titled “1. Pin the Bub kernel”A distribution is, first, a Python package whose dependencies include Bub. Pin a tight range so a new minor release of Bub does not silently change behavior for your users:
[project]
name = "visual-base"
version = "0.2.0"
dependencies = [
"bub>=0.3.6,<0.4",
"loguru>=0.7",
]
Bub follows the stability promise — kernel hardened, plugins loose. The >=0.3.6,<0.4 range opts into patch-level fixes within the 0.3 minor line.
2. Ship a custom CLI
Section titled “2. Ship a custom CLI”The cleanest way to brand a Bub-based product is a thin CLI that defers to Bub’s own Typer app. Visual-base’s cli.py is the reference pattern:
# src/visual_base/cli.py
from __future__ import annotations
import os
import sys
from visual_base.settings import VisualBaseSettings
def _has_explicit_workspace_flag(argv: list[str]) -> bool:
return any(
arg == "--workspace" or arg == "-w" or arg.startswith("--workspace=")
for arg in argv
)
def main() -> None:
if not _has_explicit_workspace_flag(sys.argv[1:]):
workspace = VisualBaseSettings().resolve_workspace()
workspace.mkdir(parents=True, exist_ok=True)
os.chdir(workspace)
from bub.__main__ import app
app()
Two points worth calling out:
- The product resolves a sticky workspace before importing Bub, then
chdirs into it.BubFrameworksnapshotsPath.cwd()as its workspace at construction time, so thechdirmust happen first. - After that, the product hands off to
bub.__main__.app— the same Typer app the barebubcommand uses. Every plugin contributingregister_cli_commandslights up automatically.
Wire the CLI to a script entry point:
[project.scripts]
visual-base = "visual_base.cli:main"
End users now run visual-base chat instead of bub chat, but get the full pipeline.
3. Ship multiple plugins from one distribution
Section titled “3. Ship multiple plugins from one distribution”Nothing forces a one-plugin-per-package layout. A distribution can host several plugin modules under one wheel and register each one through its own entry-point key. From visual-base’s pyproject.toml:
[project.entry-points.bub]
kimi = "bub_kimi.plugin"
eye = "bub_eye.plugin:EyeImpl"
[tool.hatch.build.targets.wheel]
packages = ["src/visual_base", "src/bub_kimi", "src/bub_eye", "src/skills"]
bub_kimi is a module-shaped plugin (a run_model hook that delegates to the Kimi CLI). bub_eye is a callable-class plugin that holds a framework reference (a background channel that records the screen). One uv pip install visual-base registers both with Bub.
4. Ship skills with the distribution
Section titled “4. Ship skills with the distribution”Listing src/skills in [tool.hatch.build.targets.wheel] packages puts the skills directory on Bub’s builtin discovery root. See Skills → Ship skills with your package for the full mechanics.
5. Programmatic embedding
Section titled “5. Programmatic embedding”If your product hosts Bub inside its own process — an HTTP API, a worker, a notebook — skip the CLI and drive BubFramework directly. An envelope is the dict (or message object) that carries one inbound message through the pipeline.
import asyncio
from bub import BubFramework
async def run_one_turn() -> None:
framework = BubFramework()
framework.load_hooks()
inbound = {
"session_id": "embedded:demo",
"channel": "embedded",
"chat_id": "demo",
"content": "hello from a host process",
}
async with framework.running():
result = await framework.process_inbound(inbound)
for outbound in result.outbounds:
print(outbound["content"])
asyncio.run(run_one_turn())
framework.running() is the async context that resolves provide_tape_store and any other lifecycle-bound hooks. Open it once around the lifetime of your service, not per turn, when running in long-lived processes.
Next steps
Section titled “Next steps”- Plugins — package layout for the plugins you bundle
- Skills — ship skills inside the distribution wheel
- Deploy — run your distribution in production
- Settings reference — environment variables consumed by the kernel