Surfaces
This page explains the three extension surfaces Bub exposes and why each one is decoupled from the others.
Most agent frameworks conflate “talks to a chat platform”, “knows how to do a procedure”, and “calls an action on the model’s behalf” into a single concept. Bub keeps them separate so each can evolve and be replaced independently.
The three surfaces
Section titled “The three surfaces” outward I/O operator procedures model-callable actions
╭─────────────────────╮ ╭─────────────────────╮ ╭─────────────────────╮
│ Channels │ │ Skills │ │ Tools │
╰──────────┬──────────╯ ╰──────────┬──────────╯ ╰──────────┬──────────╯
│ │ │
▼ ▼ ▼
Envelope Envelope State
(in & out of the (loaded into the (per-turn dict
turn pipeline) prompt as text) with `_runtime_*`)
All three surfaces meet on two shared types from bub.types:
Envelope— the duck-typed payload moving through the turn pipeline. It is intentionally weakly typed (Any) and accessed through helpers (field_of,content_of).State— a per-turn dict that hooks contribute to viaload_state, and that survives untilsave_statefinishes.
Channels — outward I/O
Section titled “Channels — outward I/O”A channel is anything that delivers inbound messages into the turn pipeline and accepts outbound messages from it. The contract is bub.channels.base.Channel:
start(stop_event)— start the listener loop.stop()— clean up.send(message)— deliver one outbound envelope.stream_events(message, stream)— optionally wrap the model’s stream for incremental rendering.
Why decoupled: the kernel does not care whether messages arrive over the CLI, Telegram, a webhook, or a hardware device. New channels are added by implementing this class and registering them via the provide_channels hook.
For specific built-in channels and how to enable them, see Operate Bub: channels.
Skills — operator procedures
Section titled “Skills — operator procedures”A skill is a directory containing SKILL.md (frontmatter + body) plus optional scripts and assets. A skill is invokable by any operator — human or agent — by name.
The contract is intentionally not a Python interface: skills are markdown procedures. Bub discovers them from three roots in priority order: project (.agents/skills/), user (~/.agents/skills/), and built-in.
Why decoupled: skills migrate between agents (Bub follows the Agent Skills format). They are not tied to a specific model, channel, or runtime. The same SKILL.md works whether the operator is a teammate reading the file in code review or an agent calling it during a turn.
Tools — model-callable actions
Section titled “Tools — model-callable actions”A tool is a typed Python function the model can call during a turn. Tools are registered via bub.tool and exposed to the model through the agent loop.
Why decoupled: tools are the only surface that the model itself invokes. Skills tell the operator what to do; tools are what the agent can actually do. Keeping them separate makes it possible to add a skill without giving the model new powers, or to add a tool that humans never invoke directly.
Why these three, not more
Section titled “Why these three, not more”The split mirrors three different control flows:
- A channel is invoked by the outside world.
- A skill is invoked by an operator naming it.
- A tool is invoked by the model on its own initiative.
Conflating them makes it impossible to reason about authority. Keeping them apart lets each surface have its own contract: channels have a lifecycle, skills have a discovery and naming convention, tools have a typed signature and registry.
Where they meet
Section titled “Where they meet”The pipeline is the only place these surfaces interact directly:
- A channel produces an
Envelopethat entersprocess_inbound. load_state(and the built-in agent) loads applicable skills into the prompt and the active tool set.- The model may call tools, whose results land back on the tape.
render_outboundproduces envelopes, anddispatch_outboundhands them to channels.
Nothing else couples the three. This is what lets a plugin add a Discord channel without touching skills, or a workspace add a repo-map skill without touching channels or tools.
Next steps
Section titled “Next steps”- Build channels — implement and register a channel via
provide_channels. - Build skills — author skills, including bundled assets.
- Build tools — register typed tools the model can call.
- Types reference —
Envelope,State, and accessor helpers.