Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
2. Initialize state with `_runtime_workspace` from `BubFramework.workspace`.
3. Merge all `load_state(message, session_id)` dicts.
4. Build prompt via `build_prompt(message, session_id, state)` (fallback to inbound `content` if empty).
5. Execute `run_model_stream(prompt, session_id, state)`.
6. For each stream event, call `OutboundChannelRouter.dispatch_event(...)`, which forwards to `channel.on_event(event, message)` when the target channel exists.
5. Execute `run_model(prompt, session_id, state)` by default, or `run_model_stream(prompt, session_id, state)` when the caller opts into streaming.
6. In streaming mode, forward each stream event through the outbound router before collecting final text.
7. Always execute `save_state(...)` in a `finally` block.
8. Render outbound batches via `render_outbound(...)`, then flatten them.
9. If no outbound exists, emit one fallback outbound.
10. Dispatch each outbound via `dispatch_outbound(message)`.

If no plugin implements `run_model_stream`, `HookRuntime` falls back to `run_model(prompt, session_id, state)` and adapts the returned text into a stream with a single text chunk.
`HookRuntime` keeps both directions compatible: `run_model()` can consume a streaming plugin by concatenating text chunks, and `run_model_stream()` can consume a plain `run_model()` plugin by adapting its text into a single-chunk stream.

## Hook Priority Semantics

Expand All @@ -50,7 +50,8 @@ If no plugin implements `run_model_stream`, `HookRuntime` falls back to `run_mod
Builtin `BuiltinImpl` behavior includes:

- `build_prompt`: supports comma command mode; non-command text may include `context_str`.
- `run_model_stream`: delegates to `Agent.run()`.
- `run_model`: delegates to `Agent.run()`.
- `run_model_stream`: delegates to `Agent.run_stream()`.
- `system_prompt`: combines a default prompt with workspace `AGENTS.md`.
- `register_cli_commands`: installs `run`, `gateway`, `chat`, plus hidden diagnostic commands.
- `provide_channels`: returns `telegram` and `cli` channel adapters.
Expand All @@ -67,6 +68,8 @@ Channels have two different outbound surfaces:

If a channel does not implement any special event behavior, it can ignore `on_event` and rely entirely on `send()`.

Channel streaming is opt-in through `BUB_STREAM_OUTPUT=true` (used by `ChannelManager`). When disabled, channels only receive the final rendered outbound message.

## Boundaries

- `Envelope` stays intentionally weakly typed (`Any` + accessor helpers).
Expand Down
6 changes: 6 additions & 0 deletions docs/channels/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ Enable only selected channels:
uv run bub gateway --enable-channel telegram
```

Forward streaming model events to channel adapters instead of waiting for the final rendered message:

```bash
BUB_STREAM_OUTPUT=true uv run bub gateway --enable-channel telegram
```

## `bub chat`

Start an interactive REPL session via the `cli` channel.
Expand Down
8 changes: 7 additions & 1 deletion docs/channels/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Enable only Telegram:
uv run bub gateway --enable-channel telegram
```

Enable streaming event delivery for channel listeners:

```bash
BUB_STREAM_OUTPUT=true uv run bub gateway --enable-channel telegram
```

## Session Semantics

- `run` command default session id: `<channel>:<chat_id>`
Expand All @@ -43,7 +49,7 @@ Channel adapters can receive outbound data in two forms:

Use `on_event` for incremental UX such as live text updates, typing indicators, progress bars, or chunk-level logging. Use `send` for the final durable outbound payload.

`on_event` is optional. A channel that does not need streaming behavior can ignore it and only implement `send`.
`on_event` is optional. A channel that does not need streaming behavior can ignore it and only implement `send`. `ChannelManager` only forwards stream events when `BUB_STREAM_OUTPUT=true`; otherwise channels receive final outbounds only.

## Debounce Behavior

Expand Down
14 changes: 7 additions & 7 deletions docs/extension-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ Current `process_inbound()` hook usage:
1. `resolve_session` (`call_first`)
2. `load_state` (`call_many`, then merged by framework)
3. `build_prompt` (`call_first`)
4. `run_model_stream` (`call_first`)
4. `run_model` / `run_model_stream` (`call_first`)
5. `save_state` (`call_many`, always executed in `finally`)
6. `render_outbound` (`call_many`)
7. `dispatch_outbound` (`call_many`, per outbound)

Compatibility note:

- `run_model_stream` is the primary model hook.
- If no plugin implements `run_model_stream`, Bub falls back to `run_model`.
- The `run_model` return value is wrapped into a stream with exactly one text chunk.
- Bub can execute either `run_model` or `run_model_stream`, depending on whether the caller requests streaming.
- `HookRuntime.run_model()` can consume a streaming plugin by concatenating text chunks.
- `HookRuntime.run_model_stream()` can consume a plain `run_model()` implementation by wrapping it into a one-chunk stream.
- A plugin should implement one of these hooks, not both.

Other hook consumers:
Expand Down Expand Up @@ -182,8 +182,8 @@ uv run bub hooks
uv run bub run "hello"
```

Check that your plugin is listed for `build_prompt` / `run_model_stream`, and output reflects your override.
If you intentionally use the legacy compatibility hook, check for `run_model`.
Check that your plugin is listed for `build_prompt` plus whichever model hook you implement, and output reflects your override.
If you intentionally use the non-streaming path, check for `run_model`; if you need incremental output, check for `run_model_stream`.

## 10) Listen To Parent Stream

Expand Down Expand Up @@ -229,7 +229,7 @@ class StreamTapPlugin:

Use this when you need to log chunks, redact text, inject extra events, or measure stream timing without reimplementing the underlying model call.

If you also need to support parents that only implement legacy `run_model`, add your own fallback path and wrap that text result into a one-chunk stream.
If you also need to support parents that only implement `run_model`, add your own fallback path and wrap that text result into a one-chunk stream.

## 11) Common Pitfalls

Expand Down
5 changes: 3 additions & 2 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
Every turn stage is a [pluggy](https://pluggy.readthedocs.io/) hook.
Builtins are ordinary plugins — override any stage by registering your own.
Both first-result hooks (override) and broadcast hooks (observer) are supported.
`run_model_stream` is the primary model hook.
Legacy `run_model` hooks still work and are adapted into a single text chunk stream.
`run_model` is the default model hook for turn execution.
`run_model_stream` remains available for incremental channel output, and either hook shape can be adapted to the other.
Safe fallback to prompt text when no model hook returns a value (with `on_error` notification).
Automatic fallback outbound when `render_outbound` produces nothing.

Expand All @@ -21,6 +21,7 @@ Context is reconstructed from tape records, not accumulated in session state.
- **Model runtime**: agent loop with tool use, backed by [Republic](https://github.com/bubbuild/republic).
- **Comma commands**: `,help`, `,skill`, `,fs.read`, etc. Unknown commands fall back to shell.
- **Channels**: `cli` and `telegram` ship as defaults.
- **Streaming toggle**: channel event streaming is controlled by `BUB_STREAM_OUTPUT` and is off by default.

All of these are hook implementations. Replace what you need.

Expand Down
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ uv run bub gateway # channel listener mode
Every inbound message goes through one turn pipeline. Each stage is a hook.

```text
resolve_session → load_state → build_prompt → run_model_stream
dispatch_outbound ← render_outbound ← save_state
resolve_session → load_state → build_prompt → run_model / run_model_stream
dispatch_outbound ← render_outbound ← save_state
```

`run_model` remains supported as a compatibility hook and is adapted into a single-chunk stream when `run_model_stream` is absent.
By default Bub executes `run_model` and expects plain text. Streaming remains available through `run_model_stream`, and `HookRuntime` adapts either hook shape to the other for compatibility.

Builtins are plugins registered first. Later plugins override earlier ones. No special cases.

Expand Down
Loading
Loading