Skip to content

refactor!: install only bub.core by default and streamline the agent kernel - #361

Merged
frostming merged 11 commits into
devfrom
refactor/core-boundary
Oct 16, 2026
Merged

frostming merged 11 commits into
devfrom
refactor/core-boundary

Conversation

@frostming

@frostming frostming commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

What changed and why

Users who only embed the agent loop had to install all of Bub: the CLI, the Telegram channel, the plugin system, the TUI prompts and their dependencies. Importing the kernel also loaded the framework. This PR makes bub.core installable and importable on its own, inside the single bub package, and tidies the kernel's API while it is still in alpha.

Packaging: pip install bub installs only the kernel (breaking)

  • dependencies now holds only republic, pydantic, loguru and typing-extensions. Everything else moves to a new app extra: pydantic-settings, pyyaml, pluggy, inquirer-textual, typer, rich, prompt-toolkit, python-telegram-bot, rapidfuzz, aiohttp and httpx[socks]. The CLI, channels and plugins need bub[app], and logfire implies it.
  • The bub script is bub.__main__:main. It reads bub's own metadata, and if any app requirement is missing it exits with missing: … and an install hint instead of an ImportError. bub.__main__ imports nothing from the extra at import time.
  • The dev group depends on bub[app], so uv sync, CI and tox keep a full environment. The Dockerfile (--extra app), install.sh / install.ps1 and bub install (uv add --extra app) request the extra.
  • bub/__init__.py exports BubFramework, Settings, config, ensure_config, hookimpl and tool lazily through one _EXPORTS table, so import bub.core works without the extra. .env is now loaded by bub.configure, where settings read the environment, instead of bub.framework. bub.home still honors a BUB_HOME from .env; bub.hookimpl no longer loads .env.
  • A new core-only-install CI job builds the wheel and checks two things: a plain install imports bub.core with no app dependencies, and the bub command asks for the extra.

Kernel layout (breaking, versus 0.6.0a1 and 0.5.x)

  • bub.core holds the agent concepts: agent, commands, context, events, model, recorder, system_prompt, tape, tools, store and sidecars. The top-level aliases bub.tape, bub.tools, bub.store and bub.sidecars are removed; import from bub.core.*. bub.tool is unchanged.
  • errors, prompt, streaming, tracing and turn stay top-level modules; bub.core imports them, and they need only the kernel's dependencies.
  • The newest-first, deduplicated, rapidfuzz-backed tape search moves unchanged from FileTapeStore into bub.builtin.store.FuzzyFileTapeStore, which provide_tape_store returns. The kernel's FileTapeStore uses the plain substring query, so bub.core needs neither yaml nor rapidfuzz, and bub users see no change.

Agent events

  • New RunFinished with status (ok, error or cancelled), steps, elapsed_ms and error. It follows every RunStarted when the run's stream closes, including a stream closed before it is iterated, and is recorded on the tape as loop.finish. Command prompts emit neither, as before.
  • Events are renamed to *Started / *Finished: BeforeLlmCall → LLMCallStarted, AfterLlmCall → LLMCallFinished, BeforeToolCall → ToolCallStarted, AfterToolCall → ToolCallFinished and ChatCompleted → ChatFinished. The payloads LlmCallRequest, LlmCallResult and LlmCallDecision become LLMCall*. These payloads existed in 0.5.x, so plugins implementing before_llm_call that import LlmCallDecision must update. Hook names such as before_llm_call are unchanged.

Fewer layers in a run

A model run used to pass each event through AsyncStreamEvents(start) → _trace_events → AsyncStreamEvents(run_on) → _run_steps → AsyncStreamEvents(run_once) → _run_once → _stream_events. It is now AsyncStreamEvents(run_on) → _run_steps → run_once → _stream_events.

  • start hands its invoke_agent span and tape-closing callback to run_on (new optional span / on_close arguments), which builds the run's only stream. A small _RunTracker records the span's output messages and how the run ended.
  • run_once is an async generator that writes usage into the given StreamState, replacing the per-step stream whose usage / error were copied into the run's state. It remains the per-step override point.

Impacted modules

pyproject.toml, uv.lock, bub/__init__.py, bub.__main__, bub.configure, bub.framework, bub.core.* (now including tape, tools, store, sidecars and events), bub.builtin.store (new), bub.builtin.*, bub.hooks.*, bub.channels.cli, Dockerfile, website/public/install.{sh,ps1}, .github/workflows/main.yml, AGENTS.md, README and docs (install, SDK, tools, hooks, types, plugins, observability, deploy, distribution, tape-and-context; en + zh-cn).

Verification

  • make check (lock, prek/ruff, mypy): passed
  • uv run python -m pytest -q: passed. With the trace extra installed, 645 passed and 1 skipped (dev baseline: 616 passed, 20 skipped without OpenTelemetry). New tests:
    • core boundary: importing bub.core loads no app dependencies.
    • CLI entry: the app-extra check.
    • RunFinished: ok, error, early exit, never iterated and commands.
    • core vs builtin file store search.
  • Built wheel, plain install: only the kernel's dependencies are installed. bub.core and the top-level modules work, and no app dependency is importable. bub and python -m bub exit 1 with the bub[app] hint. With bub[app] installed, bub --help works.
  • Docs build (pinned pnpm 10.34.6): passed

Follow-up (not in this PR)

  • The fuzzy search in the file store hasn't actually worked since feat: replace republic with any-llm-sdk #229. FileTapeStore.fetch_all calls super().fetch_all() with the query still attached, so the mixin's JSON substring filter removes typo'd matches before rapidfuzz sees them. That JSON is dumped with ensure_ascii=True, so non-ASCII queries never match either.
  • StreamState.error is never assigned by the agent.

🤖 Generated with Claude Code

- Import bub's top-level names lazily so `import bub.core` no longer loads
  the framework, CLI or settings; accessing them still imports
  bub.framework first so `.env` is loaded as before.
- Move errors, prompt, tracing, streaming and TurnState into bub.core;
  the old top-level modules remain aliases of the core modules.
- Move FileTapeStore's newest-first, deduplicated fuzzy search into
  bub.builtin.store.FuzzyFileTapeStore, so bub.core no longer needs yaml
  or rapidfuzz. The builtin tape store keeps the existing behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bub 75797dc Commit Preview URL

Branch Preview URL
Oct 15 2026, 10:48 AM

Add import-linter contracts that forbid bub.core from importing the rest
of bub or application-only dependencies, and run them in `make check`
and CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frostming
frostming changed the base branch from dev to main October 15, 2026 07:29
@frostming
frostming added this pull request to stack #363 October 15, 2026 07:29
@frostming
frostming removed this pull request from stack #363 October 15, 2026 07:30
@frostming
frostming changed the base branch from main to dev October 15, 2026 07:30
@frostming
frostming added this pull request to stack #364 October 15, 2026 07:30
Run the import contracts from .pre-commit-config.yaml instead of a
separate `make check` / CI step, so they run on commit and in the
existing prek CI job. The hook installs only import-linter and finds the
sources through PYTHONPATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frostming
frostming removed this pull request from stack #364 October 15, 2026 08:09
`pip install bub` now installs only the agent kernel's dependencies
(republic, pydantic, loguru, typing-extensions). The framework, CLI,
channels, hooks, skills and builtin agent need the new `app` extra.

BREAKING CHANGE: install `bub[app]` (e.g. `uv tool install 'bub[app]'`)
to get the `bub` command and runtime. Without it, the `bub` script exits
with an install hint instead of an ImportError. Plugins should depend on
`bub[app]`.

- The `bub` script moves to `bub._cli:main`, which checks the app
  extra's requirements from bub's metadata before importing the CLI.
- The dev group depends on `bub[app]`, so `uv sync` keeps a full
  environment; the Dockerfile, install scripts and `bub install` request
  the extra, and `logfire` implies it.
- CI builds the wheel and checks that a plain install imports bub.core
  without app dependencies and that `bub` asks for the extra.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frostming frostming changed the title refactor: make bub.core independent of the rest of bub refactor!: make bub.core independent and install only it by default Oct 15, 2026
frostming and others added 3 commits October 15, 2026 16:29
The `bub` script is `bub.__main__:main` again. The module no longer
imports typer or the framework at import time: `main` checks the app
extra first, and `bub.__main__.app` is created on first access, so
`from bub.__main__ import app` keeps working for distributions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the lazily created `bub.__main__.app`; `main` checks the app extra
and runs `create_cli_app()` directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rename the agent kernel package `bub.core` to `bub.agent`, without a
  compatibility alias. `bub.store`, `bub.tape`, `bub.tools` and
  `bub.sidecars` stay as aliases of the `bub.agent` modules.
- Move `errors`, `prompt`, `tracing`, `streaming` and `TurnState` back to
  their original top-level modules and drop the aliases added for them;
  they need only the kernel's dependencies.
- Remove the import-linter hook and contracts. A test still checks that
  importing `bub.agent` loads none of the `app` extra's dependencies.

BREAKING CHANGE: `bub.core` (only in 0.6.0a1) is now `bub.agent`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frostming frostming changed the title refactor!: make bub.core independent and install only it by default refactor!: rename bub.core to bub.agent and install only it by default Oct 15, 2026
frostming and others added 4 commits October 15, 2026 17:07
Import these modules from `bub.agent` instead.

BREAKING CHANGE: `bub.store`, `bub.tape`, `bub.tools` and `bub.sidecars`
are removed; use `bub.agent.store`, `bub.agent.tape`, `bub.agent.tools`
and `bub.agent.sidecars`. `bub.tool` is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore `bub.store`, `bub.tape`, `bub.tools` and `bub.sidecars` as real
modules at their original location, and have `bub.agent` import them,
so only the new agent kernel lives in `bub.agent`. `bub.agent` exports
its names lazily: `bub.tools` imports `bub.agent.events`, and loading the
whole package there would import the agent loop back into `bub.tools`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`bub.agent` no longer re-exports `Tape`, `TapeContext`,
`session_tape_name`, `Tool`, `ToolContext` and `tool`; import them from
`bub.tape` and `bub.tools`, where they are defined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rename `bub.agent` back to `bub.core` and move the agent concepts into
  it: `tape`, `tools`, `store`, `sidecars` and `events`. The top-level
  aliases `bub.tape`, `bub.tools`, `bub.store` and `bub.sidecars` are
  removed; `errors`, `prompt`, `streaming`, `tracing` and `turn` stay at
  the top level.
- Load `.env` in `bub.configure`, where settings read the environment,
  instead of `bub.framework`. All lazy `bub.*` exports, including
  `bub.tool`, resolve through `_EXPORTS`; only `bub.home` is computed.
  `bub.hookimpl` no longer loads `.env`.
- Add `RunFinished` (status `ok`, `error` or `cancelled`, steps, elapsed
  time, error). It follows every `RunStarted` when the run's stream
  closes, even if it is never iterated, and is recorded as `loop.finish`.
- Rename events to `*Started` / `*Finished`: `LLMCallStarted`,
  `LLMCallFinished`, `ToolCallStarted`, `ToolCallFinished` and
  `ChatFinished`; `LlmCallRequest`, `LlmCallResult` and `LlmCallDecision`
  become `LLMCall*`. Hook names such as `before_llm_call` are unchanged.
- Build one stream per run: `run_on` takes the `invoke_agent` span and a
  close callback from `start`, so the `_trace_events` wrapper and the
  second `AsyncStreamEvents` are gone. `run_once` is now an async
  generator that writes usage into the run's `StreamState` instead of
  returning its own stream to be copied.

BREAKING CHANGE: import `bub.core.tape`, `bub.core.tools`,
`bub.core.store` and `bub.core.sidecars` instead of the top-level
modules; the event classes and `LlmCall*` payloads are renamed; and
`Agent.run_once` returns an async generator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frostming frostming changed the title refactor!: rename bub.core to bub.agent and install only it by default refactor!: install only bub.core by default and streamline the agent kernel Oct 15, 2026
@frostming
frostming merged commit c705b24 into dev Oct 16, 2026
7 checks passed
@frostming
frostming deleted the refactor/core-boundary branch October 16, 2026 01:29
frostming added a commit that referenced this pull request Oct 18, 2026
…kernel (#361)

Users who only embed the agent loop had to install all of Bub, and
importing the kernel loaded the framework. Make `bub.core` installable and
importable on its own, and tidy the kernel's API while it is in alpha.

Packaging:
- `pip install bub` installs only the kernel's dependencies (republic,
  pydantic, loguru, typing-extensions). The framework, CLI, channels,
  hooks, skills and builtin agent need the new `app` extra; `logfire`
  implies it.
- The `bub` script (`bub.__main__:main`) checks the `app` extra's
  requirements from bub's metadata and exits with an install hint instead
  of an ImportError. `bub.__main__` imports nothing from the extra at
  import time.
- The dev group depends on `bub[app]`, so `uv sync` keeps a full
  environment. The Dockerfile, install scripts and `bub install` request
  the extra. A new CI job checks that a plain wheel install imports
  `bub.core` without app dependencies and that `bub` asks for the extra.
- `bub` exports its top-level names lazily through one `_EXPORTS` table,
  and `.env` is loaded by `bub.configure`, where settings read the
  environment, instead of `bub.framework`.

Kernel layout:
- `bub.core` holds the agent concepts: agent, commands, context, events,
  model, recorder, system_prompt, tape, tools, store and sidecars.
  `bub.tape`, `bub.tools`, `bub.store` and `bub.sidecars` remain aliases
  of the `bub.core` modules and need only the kernel's dependencies.
- `errors`, `prompt`, `streaming`, `tracing` and `turn` stay top-level
  modules that need only the kernel's dependencies.
- The newest-first, deduplicated fuzzy tape search moves from
  `FileTapeStore` into `bub.builtin.store.FuzzyFileTapeStore`, which
  `provide_tape_store` returns. The kernel's `FileTapeStore` uses the plain
  substring query, so `bub.core` needs neither yaml nor rapidfuzz.

Agent events:
- New `RunFinished` (status `ok`, `error` or `cancelled`, steps, elapsed
  time, error). It follows every `RunStarted` when the run's stream
  closes, even if it is never iterated, and is recorded as `loop.finish`.
- Events are renamed to `*Started` / `*Finished`: `LLMCallStarted`,
  `LLMCallFinished`, `ToolCallStarted`, `ToolCallFinished` and
  `ChatFinished`. The payloads become `LLMCallRequest`, `LLMCallResult`
  and `LLMCallDecision`; `bub.hooks.interception` keeps the `LlmCall*`
  names as aliases. Hook names such as `before_llm_call` are unchanged.

Runs:
- `start` hands its `invoke_agent` span and close callback to `run_on`,
  which builds the run's only stream.
- `run_once` is an async generator that writes usage into the run's
  `StreamState`.

BREAKING CHANGE: install `bub[app]` (e.g. `uv tool install 'bub[app]'`) to
get the `bub` command and runtime; plugins should depend on `bub[app]`.
The core event classes are renamed, and `Agent.run_once` returns an async
generator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant