How the app handles a message
BubFramework connects the agent to application concerns: session identity, turn state, prompt preparation, and message delivery. Each stage exposes hooks so a plugin can change that part of the application.
This pipeline belongs to bub[app]. A direct core.Agent.run() starts the agent loop with the prompt, state, and tape you provide.
Follow one message
Section titled “Follow one message”framework.process_inbound(message) runs these stages:
| Stage | What it does |
|---|---|
resolve_session |
Select the session ID for the inbound message. |
load_state |
Collect the current turn’s state from hook implementations. |
build_prompt |
Turn inbound content into a prompt. |
run_model / run_model_stream |
Produce the model output, through either a final result or a stream. |
save_state |
Finish work associated with the model stage. Runs in its finally block. |
render_outbound |
Turn the result into outbound envelopes. |
dispatch_outbound |
Deliver each outbound envelope. |
The built-in plugin supplies the application defaults. Plugins can contribute or replace behavior according to each hook’s contract; Hooks reference defines first-result hooks, combined results, and priority rules.
Where the agent runs
Section titled “Where the agent runs”The built-in run_model_stream hook calls BubAgent.run_stream(). BubAgent uses the core model and tool loop, adding settings, skills, commands, steering, and application tool policies.
The framework adapts between streaming and non-streaming hook results. process_inbound(..., stream_output=True) lets the channel router consume incremental output and returns a completed TurnResult after consumption.
Replacing run_model with a plugin changes this behavior. For example, an echo plugin can return text without ever creating an agent run. Core events and tape recording occur when the chosen implementation actually uses the core agent.
Resource ownership and failures
Section titled “Resource ownership and failures”Use async with framework.running(): when hooks provide stores or other application resources. It opens those resources and releases them on exit. The built-in per-message lifespan opens during state loading and closes in save_state.
save_state runs once execution reaches the model stage, including when that stage raises. A failure in session resolution, state loading, or prompt construction happens before this finally block and does not trigger it. Plugins that acquire resources in those earlier stages must account for that boundary.
The built-in save_state closes the per-message lifespan. The application records selected settings separately; saving other state requires an explicit persistence policy.
If the model stage raises an unhandled exception, the framework runs save_state, then skips normal rendering and dispatch. The outer error path calls on_error(stage="turn", ...) and re-raises the exception. The built-in error observer can send an error response through outbound dispatch. Failures in individual on_error observers are isolated so other observers can run.
Extend the app
Section titled “Extend the app”Use plugin setup to register your implementation, hook recipes for specific changes, and the hooks reference for exact signatures and ordering.