Bub: A Hook-First Python Runtime Where Every Turn Stage Is Replaceable
Bub it. Build it. A hook-first runtime for agents that live alongside people.
At a glance
- What is it?
- Bub is a small Python agent runtime built around pluggy hooks, an append-only tape for context, and one inbound pipeline shared by CLI, Telegram and custom channels. It is aimed at teams whose agents share conversations with humans, and its extension model is its main reason to exist.
- Who is it for?
- Adopt Bub if you need to override a specific turn stage without forking a runtime, or if your agents and humans share a conversation and you want the same evidence trail for both. Do not adopt it if you want a visual graph editor, prebuilt retrieval components, or a large integration catalogue; the repository does not present itself as that.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem Bub was built around: shared conversations with no hidden operator class
Most agent frameworks assume a single operator talking to a single assistant. Bub starts from the opposite case. The README says it began in group chats, where multiple humans and agents had to work in the same conversation without hidden state, hand-wavy memory, or framework-specific magic. That origin explains the two features the project leads with: operator equivalence and tape context.
Operator equivalence means humans and agents run inside the same runtime boundaries, with the same evidence trail and handoff model. There is no privileged internal path that a human participant cannot see. Tape context means context is rebuilt from append-only records rather than carried as mutable session state, which the README frames as easier to inspect, replay and hand off.
Who is this for? Python engineers building agents that live in a chat surface shared with people, and who expect to modify the runtime's behavior rather than only configure it. If your agent is a single-user CLI toy, the shared-environment framing buys you less than the hook machinery costs.
The turn pipeline: seven named stages, one hook each
Every inbound message goes through one turn pipeline, and each stage is a pluggy hook. The README prints the flow:
resolve_session, load_state, build_prompt, run_model, then save_state, render_outbound, dispatch_outbound.
Builtins are registered first, external plugins load after them, and at runtime later plugins take precedence. That ordering rule is the whole extension model in one sentence: you do not patch the framework, you register a hook implementation that loads later and therefore wins. The README states plainly that there are no framework-only shortcuts.
The source layout backs this up. The turn orchestrator is src/bub/framework.py, the hook contract is src/bub/hooks/specs.py, builtin hook implementations are in src/bub/builtin/hook_impl.py, and skill discovery is in src/bub/skills.py. If you want to know whether a stage is genuinely replaceable, specs.py is the file to read first, and it is the file the README points you to for the hook contract.
The same runtime drives CLI, Telegram and any channel you add. Adapters change the surface, not the runtime model. That is a real architectural commitment: a Telegram message and a CLI message enter the same pipeline and produce the same kind of tape records.
Writing a plugin: a two-method class and an entry point
The README's extension example is short enough to quote in structure. You define a class with methods decorated by @hookimpl imported from bub, one of which overrides build_prompt and returns a string, and another that overrides run_model as an async method. The example plugin returns the prompt unchanged, which makes it a useful template rather than a useful agent.
Registration is through a Python entry point group named bub, declared in pyproject.toml as [project.entry-points."bub"]. The README shows a single line mapping a name to a module attribute. This is standard packaging, not a Bub-specific plugin registry, which means plugin discovery follows whatever your installed distribution exposes.
Two consequences follow from that choice. First, a plugin is an installed Python package, so distributing one means publishing to an index or a path. Second, because later plugins take precedence, two plugins that both override run_model will not merge; one wins by load order. The README does not describe a conflict resolution mechanism beyond that ordering rule, so treat hook collisions as something you manage by naming and packaging discipline rather than something the runtime arbitrates.
Install and first run: the commands the README gives
On macOS and Linux the documented path is a shell installer: curl -fsSL https://bub.build/install.sh | bash. On Windows PowerShell it is a one-liner invoking irm https://bub.build/install.ps1. The README states the interactive installer uses a colored preset picker, accepts additional plugin dependencies, and runs bub onboard after installation. For automation, select a preset explicitly, because non-interactive installs skip onboarding: the documented form is bash -s -- --preset recommended --dependency extra-plugin on Unix, with a scriptblock equivalent on PowerShell.
From source, the README gives git clone, cd bub, then uv sync, which it says is enough to run Bub from source. For local development it recommends make install instead, so the website toolchain and prek hooks are installed too. That is a meaningful distinction: uv sync is the runtime, make install is the contributor environment.
Day to day there are three entry commands. bub chat opens an interactive REPL, bub run MESSAGE performs a one-shot turn, and bub gateway runs channel listener mode for Telegram and similar surfaces. Lines starting with a comma enter internal command mode, with ,help, ,skill name=my-skill and ,fs.read path=README.md given as examples. bub hooks still exists for diagnostics but is hidden from top-level help, which suggests the maintainers consider it an inspection tool rather than a supported interface.
Configuration surface: environment variables and a separate plugin project
Configuration is environment-variable driven. BUB_MODEL defaults to openrouter:openrouter/free, BUB_API_KEY is optional if you use bub login openai, and BUB_API_BASE covers a custom provider endpoint. BUB_CLIENT_ARGS and BUB_COMPLETION_ARGS are JSON objects forwarded to the underlying model client and to each completion call respectively. BUB_MAX_STEPS is unlimited by default and must be a positive integer when set; BUB_MAX_TOKENS defaults to 16384; BUB_MODEL_TIMEOUT_SECONDS has no default; BUB_SPILL_THRESHOLD defaults to 4096 estimated tokens and can be set to 0 to disable spilling.
That spill threshold is the most operationally interesting key. Tool output above the threshold is spilled rather than kept inline, which is the mechanism that keeps long tool results from occupying the model context. The README does not describe where spilled output goes or how it is retrieved, so that is something to confirm in the source before relying on it in a pipeline that produces large tool results.
Plugin dependencies live in a separate uv project managed by bub install and bub update, defaulting to ~/.bub/bub-project or the path in BUB_PROJECT. Keeping plugin dependencies out of the runtime's own environment is a deliberate separation and it means upgrading Bub and upgrading your plugins are two different operations.
Where the design costs you: tape growth, hook collisions, and a young surface
The tape is the trade-off at the center of Bub. Rebuilding context from append-only records is inspectable and replayable, and the README leans on that. Append-only also means records accumulate. The material does not describe compaction, retention windows, or a trimming policy, so if you run long-lived group sessions, the growth behavior of the tape is the first thing to measure rather than assume.
The second cost is precedence. Later plugins win. With one plugin this is convenient. With several plugins from different authors, an override of build_prompt or run_model silently shadows another, and the README offers no arbitration beyond load order.
The third is maturity. The release list in the supplied material shows 0.4.1 through 0.4.3 between late July and mid August 2026, with the last push in September 2026. That cadence is fast, which is normal for a pre-1.0 project and also means the hook contract in src/bub/hooks/specs.py can move. Pin your Bub version and read the release notes before upgrading if you have written plugins.
Finally, Bub is the wrong tool if you want a graph-oriented orchestration layer with a visual editor, or a batteries-included retrieval stack. The README describes CLI, Telegram, tools, skills and model execution as the included set. Anything beyond that is a plugin you write.
How it differs from LangGraph-style graph runtimes
The natural comparison is a graph-based agent framework such as LangGraph, where you declare nodes and edges and the framework executes the resulting graph. Bub inverts that. There is no graph to declare. There is a fixed seven-stage turn pipeline, and your extension point is a pluggy hook implementation that overrides a stage.
The difference in practice: in a graph framework you add behavior by adding nodes and routing between them, and the topology is the artifact you maintain. In Bub you add behavior by registering a later-loading plugin that replaces a stage, and the precedence order is the artifact you maintain. The first model suits workflows with genuinely branching control flow. The second suits runtimes where the pipeline is stable and you want to swap one stage's implementation, which is what the CLI, Telegram and custom-channel story requires.
A second difference is context handling. Bub's tape is append-only records from which context is rebuilt, and the project links to tape.systems for the underlying idea. Graph frameworks typically pass a mutable state object between nodes. If you have been debugging agents by inspecting a state object mid-run, the tape model changes how you debug: you read records rather than snapshot a dict.
Bub is also explicitly built on agents.md and Agent Skills, and the README says it stays intentionally small. That is a positioning statement about scope, not a benchmark, and it should be read as such.
Maintenance, licensing, and what to check before you commit
Bub is Apache-2.0 licensed, which permits commercial use and modification and includes an explicit patent grant. That is a permissive licence and it is the same licence family many Python infrastructure projects use. This is not legal advice; if you redistribute Bub inside a product or modify it, have your own counsel read the NOTICE and attribution requirements rather than relying on a summary.
Maintenance has two layers. The runtime itself tracks the release cadence above. Your plugins are a separate uv project under ~/.bub/bub-project or BUB_PROJECT, upgraded with bub update, and they are your code to maintain. A plugin that overrides run_model is coupled to the signature in src/bub/hooks/specs.py, so a minor release that changes that signature is a breaking change for you even if the version number suggests otherwise.
Before adopting, verify three concrete things. Read src/bub/hooks/specs.py and confirm the stages you need to override are represented there. Check how BUB_SPILL_THRESHOLD behaves when your tools return large payloads, since the README documents the threshold but not the retrieval path. And confirm that your provider works through BUB_MODEL plus BUB_CLIENT_ARGS and BUB_COMPLETION_ARGS, because the default points at a free OpenRouter model and a free tier is not a production configuration.
Editorial conclusion
Adopt Bub if you need to override a specific turn stage without forking a runtime, or if your agents and humans share a conversation and you want the same evidence trail for both. Do not adopt it if you want a visual graph editor, prebuilt retrieval components, or a large integration catalogue; the repository does not present itself as that. Before committing, verify three things against your own deployment: that the pluggy hook signatures in src/bub/hooks/specs.py cover the stages you intend to replace, that the tape's append-only growth is acceptable for your session volumes, and that your provider works with BUB_MODEL and BUB_COMPLETION_ARGS as the configuration table describes them.
Community notes